diff --git a/eslint-report.json b/eslint-report.json new file mode 100644 index 000000000..70b7112e1 --- /dev/null +++ b/eslint-report.json @@ -0,0 +1 @@ +[{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/gui/gui.jsx","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'AddonHooks' is defined but never used. Allowed unused vars must match /^_/u.","line":45,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":45,"endColumn":18},{"ruleId":"no-unused-vars","severity":2,"message":"'Prompt' is defined but never used. Allowed unused vars must match /^_/u.","line":47,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":47,"endColumn":14},{"ruleId":"no-unused-vars","severity":2,"message":"'SpinnerComponent' is defined but never used. Allowed unused vars must match /^_/u.","line":58,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":58,"endColumn":24},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":431,"column":49,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":433,"endColumn":76},{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":432,"column":53,"nodeType":"CallExpression","messageId":"unexpected","endLine":432,"endColumn":114}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import classNames from 'classnames';\nimport omit from 'lodash.omit';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';\nimport {connect} from 'react-redux';\nimport MediaQuery from 'react-responsive';\nimport {Tab, Tabs, TabList, TabPanel} from 'react-tabs';\nimport tabStyles from 'react-tabs/style/react-tabs.css';\nimport VM from 'scratch-vm';\n\nimport Blocks from '../../containers/blocks.jsx';\nimport CostumeTab from '../../containers/costume-tab.jsx';\nimport TargetPane from '../../containers/target-pane.jsx';\nimport SoundTab from '../../containers/sound-tab.jsx';\nimport StageWrapper from '../../containers/stage-wrapper.jsx';\nimport Loader from '../loader/loader.jsx';\nimport Box from '../box/box.jsx';\nimport MenuBar from '../menu-bar/menu-bar.jsx';\nimport CostumeLibrary from '../../containers/costume-library.jsx';\nimport BackdropLibrary from '../../containers/backdrop-library.jsx';\nimport Watermark from '../../containers/watermark.jsx';\nimport SongsTab from '../../containers/songs-tab.jsx';\nimport Backpack from '../../containers/backpack.jsx';\nimport BrowserModal from '../browser-modal/browser-modal.jsx';\nimport TipsLibrary from '../../containers/tips-library.jsx';\nimport Cards from '../../containers/cards.jsx';\nimport Alerts from '../../containers/alerts.jsx';\nimport DragLayer from '../../containers/drag-layer.jsx';\nimport ConnectionModal from '../../containers/connection-modal.jsx';\nimport TelemetryModal from '../telemetry-modal/telemetry-modal.jsx';\nimport TWUsernameModal from '../../containers/tw-username-modal.jsx';\nimport TWSettingsModal from '../../containers/tw-settings-modal.jsx';\nimport TWSecurityManager from '../../containers/tw-security-manager.jsx';\nimport TWCustomExtensionModal from '../../containers/tw-custom-extension-modal.jsx';\nimport TWRestorePointManager from '../../containers/tw-restore-point-manager.jsx';\nimport TWFontsModal from '../../containers/tw-fonts-modal.jsx';\nimport TWUnknownPlatformModal from '../../containers/tw-unknown-platform-modal.jsx';\nimport TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx';\nimport NanoscriptEditor from '../ob-nanoscript-editor/nanoscript-editor.jsx';\n\nimport {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';\nimport {resolveStageSize} from '../../lib/screen-utils';\nimport {Theme} from '../../lib/themes';\nimport AddonHooks from '../../addons/hooks.js';\nimport ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';\nimport Prompt from '../../containers/prompt.jsx';\nimport nanoscriptIcon from '!../../lib/tw-recolor/build!./nanoscriptIcon.svg';\n\nimport {isRendererSupported, isBrowserSupported} from '../../lib/tw-environment-support-prober';\n\nimport styles from './gui.css';\nimport addExtensionIcon from './icon--extensions.svg';\nimport codeIcon from '!../../lib/tw-recolor/build!./icon--code.svg';\nimport costumesIcon from '!../../lib/tw-recolor/build!./icon--costumes.svg';\nimport soundsIcon from '!../../lib/tw-recolor/build!./icon--sounds.svg';\nimport songsIcon from '!../../lib/tw-recolor/build!./icon--songs.svg';\nimport SpinnerComponent from '../tw-loading-spinner/spinner.jsx';\nconst messages = defineMessages({\n addExtension: {\n id: 'gui.gui.addExtension',\n description: 'Button to add an extension in the target pane',\n defaultMessage: 'Add Extension'\n }\n});\n\nconst getFullscreenBackgroundColor = () => {\n const params = new URLSearchParams(location.search);\n if (params.has('fullscreen-background')) {\n return params.get('fullscreen-background');\n }\n if (window.matchMedia('(prefers-color-scheme: dark)').matches) {\n return '#111';\n }\n return 'white';\n};\n\nconst fullscreenBackgroundColor = getFullscreenBackgroundColor();\n\nconst GUIComponent = props => {\n const {\n accountNavOpen,\n activeTabIndex,\n alertsVisible,\n authorId,\n authorThumbnailUrl,\n authorUsername,\n basePath,\n backdropLibraryVisible,\n backpackHost,\n backpackVisible,\n blocksId,\n blocksTabVisible,\n cardsVisible,\n canChangeLanguage,\n canChangeTheme,\n canCreateNew,\n canEditTitle,\n canManageFiles,\n canRemix,\n canSave,\n canCreateCopy,\n canShare,\n canUseCloud,\n children,\n connectionModalVisible,\n costumeLibraryVisible,\n costumesTabVisible,\n customStageSize,\n enableCommunity,\n intl,\n isCreating,\n isEmbedded,\n isFullScreen,\n isPlayerOnly,\n isRtl,\n isShared,\n isWindowFullScreen,\n isTelemetryEnabled,\n isTotallyNormal,\n loading,\n logo,\n renderLogin,\n onClickAbout,\n onClickAccountNav,\n onCloseAccountNav,\n onClickAddonSettings,\n onClickDesktopSettings,\n onClickNewWindow,\n onClickPackager,\n onLogOut,\n onOpenRegistration,\n onToggleLoginOpen,\n onActivateCostumesTab,\n onActivateSoundsTab,\n onActivateSongsTab,\n onActivateTab,\n onClickLogo,\n onExtensionButtonClick,\n onOpenCustomExtensionModal,\n onProjectTelemetryEvent,\n onRequestCloseBackdropLibrary,\n onRequestCloseCostumeLibrary,\n onRequestCloseTelemetryModal,\n onSeeCommunity,\n onShare,\n onShowPrivacyPolicy,\n onStartSelectingFileUpload,\n onTelemetryModalCancel,\n onTelemetryModalOptIn,\n onTelemetryModalOptOut,\n securityManager,\n showComingSoon,\n showOpenFilePicker,\n showSaveFilePicker,\n soundsTabVisible,\n songsTabVisible,\n stageSizeMode,\n targetIsStage,\n telemetryModalVisible,\n theme,\n tipsLibraryVisible,\n usernameModalVisible,\n settingsModalVisible,\n customExtensionModalVisible,\n fontsModalVisible,\n unknownPlatformModalVisible,\n invalidProjectModalVisible,\n vm,\n ...componentProps\n } = omit(props, 'dispatch');\n if (children) {\n return {children};\n }\n\n const tabClassNames = {\n tabs: styles.tabs,\n tab: classNames(tabStyles.reactTabsTab, styles.tab),\n tabList: classNames(tabStyles.reactTabsTabList, styles.tabList),\n tabPanel: classNames(tabStyles.reactTabsTabPanel, styles.tabPanel),\n tabPanelSelected: classNames(tabStyles.reactTabsTabPanelSelected, styles.isSelected),\n tabSelected: classNames(tabStyles.reactTabsTabSelected, styles.isSelected)\n };\n\n const [isNano, setNano] = React.useState(false);\n\n const unconstrainedWidth = (\n UNCONSTRAINED_NON_STAGE_WIDTH +\n FIXED_WIDTH +\n Math.max(0, customStageSize.width - FIXED_WIDTH)\n );\n return ({isUnconstrained => {\n const stageSize = resolveStageSize(stageSizeMode, isUnconstrained);\n\n const alwaysEnabledModals = (\n \n \n \n {usernameModalVisible && }\n {settingsModalVisible && }\n {customExtensionModalVisible && }\n {fontsModalVisible && }\n {unknownPlatformModalVisible && }\n {invalidProjectModalVisible && }\n \n );\n\n return isPlayerOnly ? (\n \n {/* TW: When the window is fullscreen, use an element to display the background color */}\n {/* The default color for transparency is inconsistent between browsers and there isn't an existing */}\n {/* element for us to style that fills the entire screen. */}\n {isWindowFullScreen ? (\n \n ) : null}\n \n {alertsVisible ? (\n \n ) : null}\n \n {alwaysEnabledModals}\n \n ) : (\n \n {alwaysEnabledModals}\n {telemetryModalVisible ? (\n \n ) : null}\n {loading ? (\n \n ) : null}\n {isCreating ? (\n \n ) : null}\n {isBrowserSupported() ? null : (\n \n )}\n {tipsLibraryVisible ? (\n \n ) : null}\n {cardsVisible ? (\n \n ) : null}\n {alertsVisible ? (\n \n ) : null}\n {connectionModalVisible ? (\n \n ) : null}\n {costumeLibraryVisible ? (\n \n ) : null}\n {backdropLibraryVisible ? (\n \n ) : null}\n \n \n \n \n \n \n \n \n \n \n \n \n {targetIsStage ? (\n \n ) : (\n \n )}\n \n \n \n \n \n \n \n \n \n \n \n {isNano ? blocksTabVisible && : <>\n \n \n {\n alert('Adding extensions in NanoScript is not available yet');\n } : onExtensionButtonClick}\n >\n {isNano && intl.formatMessage(messages.addExtension)}\n \n \n }\n
\n {!isNano && {\n window.blocklyWorkspace.zoomCenter(1);\n },\n isSelected: false,\n children: '+'\n },\n {\n handleClick: () => {\n window.blocklyWorkspace.zoomCenter(-1);\n },\n isSelected: false,\n children: '-'\n },\n {\n handleClick: () => {\n window.blocklyWorkspace.setScale(0.675);\n },\n isSelected: false,\n children: '='\n }\n ]}\n />}\n setNano(false),\n icon: codeIcon,\n isSelected: !isNano,\n title: 'Block-based'\n },\n {\n handleClick: () => setNano(true),\n icon: nanoscriptIcon,\n isSelected: isNano,\n title: 'Text-based'\n }\n ]}\n />\n
\n \n \n \n
\n \n {costumesTabVisible ? : null}\n \n \n {soundsTabVisible ? : null}\n \n \n {songsTabVisible ? : null}\n \n \n {backpackVisible ? (\n \n ) : null}\n
\n\n \n \n \n \n \n \n
\n
\n \n \n );\n }}
);\n};\n\nGUIComponent.propTypes = {\n accountNavOpen: PropTypes.bool,\n activeTabIndex: PropTypes.number,\n authorId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), // can be false\n authorThumbnailUrl: PropTypes.string,\n authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), // can be false\n backdropLibraryVisible: PropTypes.bool,\n backpackHost: PropTypes.string,\n backpackVisible: PropTypes.bool,\n basePath: PropTypes.string,\n blocksTabVisible: PropTypes.bool,\n blocksId: PropTypes.string,\n canChangeLanguage: PropTypes.bool,\n canChangeTheme: PropTypes.bool,\n canCreateCopy: PropTypes.bool,\n canCreateNew: PropTypes.bool,\n canEditTitle: PropTypes.bool,\n canManageFiles: PropTypes.bool,\n canRemix: PropTypes.bool,\n canSave: PropTypes.bool,\n canShare: PropTypes.bool,\n canUseCloud: PropTypes.bool,\n cardsVisible: PropTypes.bool,\n children: PropTypes.node,\n costumeLibraryVisible: PropTypes.bool,\n costumesTabVisible: PropTypes.bool,\n customStageSize: PropTypes.shape({\n width: PropTypes.number,\n height: PropTypes.number\n }),\n enableCommunity: PropTypes.bool,\n intl: intlShape.isRequired,\n isCreating: PropTypes.bool,\n isEmbedded: PropTypes.bool,\n isFullScreen: PropTypes.bool,\n isPlayerOnly: PropTypes.bool,\n isRtl: PropTypes.bool,\n isShared: PropTypes.bool,\n isWindowFullScreen: PropTypes.bool,\n isTotallyNormal: PropTypes.bool,\n loading: PropTypes.bool,\n logo: PropTypes.string,\n onActivateCostumesTab: PropTypes.func,\n onActivateSoundsTab: PropTypes.func,\n onActivateTab: PropTypes.func,\n onClickAccountNav: PropTypes.func,\n onClickAddonSettings: PropTypes.func,\n onClickDesktopSettings: PropTypes.func,\n onClickNewWindow: PropTypes.func,\n onClickPackager: PropTypes.func,\n onClickLogo: PropTypes.func,\n onCloseAccountNav: PropTypes.func,\n onExtensionButtonClick: PropTypes.func,\n onOpenCustomExtensionModal: PropTypes.func,\n onLogOut: PropTypes.func,\n onOpenRegistration: PropTypes.func,\n onRequestCloseBackdropLibrary: PropTypes.func,\n onRequestCloseCostumeLibrary: PropTypes.func,\n onRequestCloseTelemetryModal: PropTypes.func,\n onSeeCommunity: PropTypes.func,\n onShare: PropTypes.func,\n onShowPrivacyPolicy: PropTypes.func,\n onStartSelectingFileUpload: PropTypes.func,\n onTabSelect: PropTypes.func,\n onTelemetryModalCancel: PropTypes.func,\n onTelemetryModalOptIn: PropTypes.func,\n onTelemetryModalOptOut: PropTypes.func,\n onToggleLoginOpen: PropTypes.func,\n renderLogin: PropTypes.func,\n securityManager: PropTypes.shape({}),\n showComingSoon: PropTypes.bool,\n showOpenFilePicker: PropTypes.func,\n showSaveFilePicker: PropTypes.func,\n soundsTabVisible: PropTypes.bool,\n stageSizeMode: PropTypes.oneOf(Object.keys(STAGE_SIZE_MODES)),\n targetIsStage: PropTypes.bool,\n telemetryModalVisible: PropTypes.bool,\n theme: PropTypes.instanceOf(Theme),\n tipsLibraryVisible: PropTypes.bool,\n usernameModalVisible: PropTypes.bool,\n settingsModalVisible: PropTypes.bool,\n customExtensionModalVisible: PropTypes.bool,\n fontsModalVisible: PropTypes.bool,\n unknownPlatformModalVisible: PropTypes.bool,\n invalidProjectModalVisible: PropTypes.bool,\n vm: PropTypes.instanceOf(VM).isRequired\n};\nGUIComponent.defaultProps = {\n backpackHost: null,\n backpackVisible: false,\n basePath: './',\n blocksId: 'original',\n canChangeLanguage: true,\n canChangeTheme: true,\n canCreateNew: false,\n canEditTitle: false,\n canManageFiles: true,\n canRemix: false,\n canSave: false,\n canCreateCopy: false,\n canShare: false,\n canUseCloud: false,\n enableCommunity: false,\n isCreating: false,\n isShared: false,\n isTotallyNormal: false,\n loading: false,\n showComingSoon: false,\n stageSizeMode: STAGE_SIZE_MODES.large\n};\n\nconst mapStateToProps = state => ({\n customStageSize: state.scratchGui.customStageSize,\n isWindowFullScreen: state.scratchGui.tw.isWindowFullScreen,\n // This is the button's mode, as opposed to the actual current state\n blocksId: state.scratchGui.timeTravel.year.toString(),\n stageSizeMode: state.scratchGui.stageSize.stageSize,\n theme: state.scratchGui.theme.theme\n});\n\nexport default injectIntl(connect(\n mapStateToProps\n)(GUIComponent));\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/gui/ob-codemirror-imports.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"export {EditorView} from '@codemirror/view';\nexport {basicSetup} from 'codemirror';\nexport {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language';\nexport {styleTags, tags} from '@lezer/highlight';\nexport {autocompletion, completeFromList} from '@codemirror/autocomplete';\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/ob-nanoscript-editor/nanoscript-editor.jsx","messages":[{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":9,"column":1,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":469,"endColumn":2},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":9,"column":1,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":469,"endColumn":2},{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":21,"column":9,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":225,"endColumn":10},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":21,"column":9,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":225,"endColumn":10},{"ruleId":"no-unused-vars","severity":2,"message":"'completeFromList' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":29,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":29,"endColumn":33},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":50,"column":61,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":50,"endColumn":62,"suggestions":[{"messageId":"removeEscape","fix":{"range":[2144,2145],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[2144,2144],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}]},{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":162,"column":13,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":179,"endColumn":14},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":162,"column":13,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":179,"endColumn":14},{"ruleId":"max-len","severity":2,"message":"This line has a length of 123. Maximum allowed is 120.","line":168,"column":1,"nodeType":"Program","messageId":"max","endLine":168,"endColumn":124},{"ruleId":"no-dupe-keys","severity":2,"message":"Duplicate key 'height'.","line":191,"column":21,"nodeType":"ObjectExpression","messageId":"unexpected","endLine":191,"endColumn":27},{"ruleId":"no-undefined","severity":2,"message":"Unexpected use of undefined.","line":239,"column":25,"nodeType":"Identifier","messageId":"unexpectedUndefined","endLine":239,"endColumn":34},{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":241,"column":9,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":285,"endColumn":10},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":241,"column":9,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":285,"endColumn":10},{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":301,"column":13,"nodeType":"CallExpression","messageId":"unexpected","endLine":301,"endColumn":38},{"ruleId":"no-unused-vars","severity":2,"message":"'isStage' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":307,"column":15,"nodeType":"Identifier","messageId":"unusedVar","endLine":307,"endColumn":22},{"ruleId":"max-len","severity":2,"message":"This line has a length of 169. Maximum allowed is 120.","line":313,"column":1,"nodeType":"Program","messageId":"max","endLine":313,"endColumn":170},{"ruleId":"max-len","severity":2,"message":"This line has a length of 127. Maximum allowed is 120.","line":351,"column":1,"nodeType":"Program","messageId":"max","endLine":351,"endColumn":128},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":392,"column":17,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":392,"endColumn":46},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":393,"column":17,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":393,"endColumn":38},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Variables\"","line":397,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":397,"endColumn":26},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For all sprites\"","line":399,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":399,"endColumn":32},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":405,"column":25,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":405,"endColumn":60},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For this sprite only\"","line":411,"column":49,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":411,"endColumn":69},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":417,"column":33,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":417,"endColumn":68},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":426,"column":21,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":426,"endColumn":53},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Make a Variable\"","line":427,"column":18,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":427,"endColumn":33},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Lists\"","line":430,"column":41,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":430,"endColumn":46},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For all sprites\"","line":432,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":432,"endColumn":32},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":438,"column":25,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":438,"endColumn":60},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For this sprite only\"","line":444,"column":49,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":444,"endColumn":69},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":450,"column":33,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":450,"endColumn":68},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":459,"column":21,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":459,"endColumn":57},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Make a List\"","line":460,"column":18,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":460,"endColumn":29}],"suppressedMessages":[],"errorCount":33,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport PropTypes from 'prop-types';\nimport VM from 'scratch-vm';\nimport Prompt from '../../containers/prompt.jsx';\nimport AddonHooks from '../../addons/hooks.js';\nimport {Theme} from '../../lib/themes';\nimport styles from './nanoscript-editor.css';\n\nfunction NanoscriptEditor ({theme, vm}) {\n const el = React.useRef(null);\n const editorRef = React.useRef(null);\n const variableRef = React.useRef([]);\n const listsRef = React.useRef([]);\n const spriteOnlyVariablesRef = React.useRef([]);\n const spriteOnlyListsRef = React.useRef([]);\n const [, setVarsState] = React.useState([]); // used to trigger renders\n\n React.useEffect(() => {\n let disposed = false;\n\n async function loadEditor () {\n const {\n EditorView,\n basicSetup,\n HighlightStyle,\n syntaxHighlighting,\n tags,\n autocompletion,\n completeFromList\n } = await import(/* webpackChunkName: \"nanoscript-editor\"*/ './ob-codemirror-imports.js');\n\n // Define hghlight rules using CSS vars\n const scratchHighlight = HighlightStyle.define([\n {tag: tags.variableName, color: 'var(--data-primary)'},\n {tag: tags.keyword, color: 'var(--pen-primary)'},\n {tag: tags.string, color: 'var(--cm-string)'},\n {tag: tags.number, color: 'var(--cm-number)'},\n {tag: tags.function, color: 'var(--cm-function)'},\n {tag: tags.operator, color: 'var(--cm-operator)'}\n ]);\n\n const {StreamLanguage} = await import('@codemirror/language');\n // helper to escape regex\n const escapeRegExp = s => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const scratchSyntax = StreamLanguage.define({\n token (stream) {\n // Keywords\n if (stream.match(/\\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\\b/)) return 'keyword';\n // Operators\n if (stream.match(/\\b(and|or|not|join|\\+|\\-|\\*|\\/|(abs|sin|cos) of .*)\\b/)) return 'operator';\n // Functions\n if (stream.match(/\\b(join|pick random|length of)\\b/)) return 'function';\n\n // Try to match variable or list names (take current refs)\n const names = (variableRef.current || []).concat(listsRef.current || []);\n if (names && names.length) {\n // sort by length to match longest first\n const sorted = names.slice().sort((a, b) => b.length - a.length)\n .map(escapeRegExp);\n const rx = new RegExp(`^(${sorted.join('|')})`, 'i');\n const m = stream.match(rx, true);\n if (m) {\n return 'variableName';\n }\n }\n stream.next();\n return 'variable';\n }\n });\n\n // NanoScript autocomplete suggestions (static)\n const staticCompletions = [\n // Control flow keywords\n {label: 'when flag clicked', type: 'keyword'},\n {label: 'when key pressed', type: 'keyword'},\n {label: 'when this sprite clicked', type: 'keyword'},\n {label: 'when I start as a clone', type: 'keyword'},\n {label: 'forever', type: 'keyword'},\n {label: 'repeat', type: 'keyword'},\n {label: 'if', type: 'keyword'},\n {label: 'else', type: 'keyword'},\n {label: 'end', type: 'keyword'},\n {label: 'wait', type: 'keyword'},\n {label: 'stop', type: 'keyword'},\n \n // Motion blocks\n {label: 'move', type: 'function'},\n {label: 'turn right', type: 'function'},\n {label: 'turn left', type: 'function'},\n {label: 'go to', type: 'function'},\n {label: 'glide to', type: 'function'},\n {label: 'point in direction', type: 'function'},\n {label: 'point towards', type: 'function'},\n {label: 'change x by', type: 'function'},\n {label: 'set x to', type: 'function'},\n {label: 'change y by', type: 'function'},\n {label: 'set y to', type: 'function'},\n \n // Looks blocks\n {label: 'say', type: 'function'},\n {label: 'think', type: 'function'},\n {label: 'show', type: 'function'},\n {label: 'hide', type: 'function'},\n {label: 'switch costume to', type: 'function'},\n {label: 'next costume', type: 'function'},\n {label: 'change size by', type: 'function'},\n {label: 'set size to', type: 'function'},\n {label: 'change color effect by', type: 'function'},\n {label: 'set color effect to', type: 'function'},\n {label: 'clear graphic effects', type: 'function'},\n \n // Sound blocks\n {label: 'play sound', type: 'function'},\n {label: 'stop all sounds', type: 'function'},\n {label: 'change volume by', type: 'function'},\n {label: 'set volume to', type: 'function'},\n \n // Events\n {label: 'broadcast', type: 'function'},\n {label: 'broadcast and wait', type: 'function'},\n {label: 'when I receive', type: 'keyword'},\n \n // Variables and lists\n {label: 'set variable to', type: 'function'},\n {label: 'change variable by', type: 'function'},\n {label: 'add to list', type: 'function'},\n {label: 'delete from list', type: 'function'},\n {label: 'insert into list', type: 'function'},\n {label: 'replace list item', type: 'function'},\n \n // Operators\n {label: 'and', type: 'operator'},\n {label: 'or', type: 'operator'},\n {label: 'not', type: 'operator'},\n {label: 'join', type: 'function'},\n {label: 'letter of', type: 'function'},\n {label: 'length of', type: 'function'},\n {label: 'round', type: 'function'},\n {label: 'abs of', type: 'function'},\n {label: 'floor of', type: 'function'},\n {label: 'ceiling of', type: 'function'},\n {label: 'sqrt of', type: 'function'},\n {label: 'sin of', type: 'function'},\n {label: 'cos of', type: 'function'},\n {label: 'tan of', type: 'function'},\n {label: 'asin of', type: 'function'},\n {label: 'acos of', type: 'function'},\n {label: 'atan of', type: 'function'},\n {label: 'pick random', type: 'function'},\n \n // Sensing blocks\n {label: 'touching', type: 'function'},\n {label: 'touching color', type: 'function'},\n {label: 'color is touching', type: 'function'},\n {label: 'ask', type: 'function'},\n {label: 'key pressed', type: 'function'},\n {label: 'mouse down', type: 'function'},\n {label: 'distance to', type: 'function'}\n ];\n\n // Function to get completions with prefix matching (dynamic includes variables & lists)\n function scratchCompletions (context) {\n const word = context.matchBefore(/\\w*/);\n if (!word || (word.from === word.to && !context.explicit)) {\n return null;\n }\n\n const dynamicVars = (variableRef.current || []).map(v => ({label: v, type: 'variable', info: 'Variable'}));\n const dynamicLists = (listsRef.current || []).map(l => ({label: l, type: 'variable', info: 'List'}));\n const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);\n\n return {\n from: word.from,\n options: allOptions.filter(option =>\n option.label.toLowerCase().startsWith(word.text.toLowerCase())\n ),\n validFor: /\\w*/\n };\n }\n\n if (!el.current || disposed) return;\n\n const cmTheme = EditorView.theme({\n '&': {\n backgroundColor: 'var(--ui-white)',\n color: 'var(--text-primary)',\n height: '100%',\n borderTopRightRadius: 'var(--space)',\n borderBottomRightRadius: 'var(--space)',\n border: '1px solid var(--ui-black-transparent)',\n height: '100%'\n },\n '.cm-scroller': {\n maxHeight: '100%',\n overflow: 'auto'\n },\n '.cm-content': {caretColor: 'var(--looks-secondary)'},\n '.cm-cursor': {borderLeft: '2px solid var(--looks-secondary)'},\n '.cm-focused': {outline: 'none'},\n '.cm-selectionBackground, ::selection': {backgroundColor: 'rgba(255, 140, 26, 0.3)'},\n '.cm-gutters': {\n backgroundColor: 'var(--ui-tertiary)',\n borderRight: '1px solid var(--ui-black-transparent)'\n },\n '.cm-completionLabel': {\n fontSize: '13px'\n }\n }, {dark: theme.isDark() ?? false});\n\n editorRef.current = new EditorView({\n doc: `when flag clicked\nsay \"Hello World!\"\nrepeat 10\n say (join \"hi \" \"there\")\nend`,\n extensions: [\n basicSetup,\n scratchSyntax,\n syntaxHighlighting(scratchHighlight),\n autocompletion({override: [scratchCompletions]}),\n cmTheme\n ],\n parent: el.current\n });\n }\n\n loadEditor();\n\n return () => {\n disposed = true;\n if (editorRef.current) {\n editorRef.current.destroy();\n }\n };\n }, [theme]);\n\n // Update variable/list refs from VM and listen for changes\n React.useEffect(() => {\n if (!vm) return undefined;\n\n function updateVarsLists () {\n try {\n const editing = vm.editingTarget || vm.runtime.getTargetForStage();\n if (!editing) {\n variableRef.current = [];\n listsRef.current = [];\n spriteOnlyVariablesRef.current = [];\n spriteOnlyListsRef.current = [];\n setVarsState([]);\n return;\n }\n\n const isStage = editing.isStage;\n const spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);\n const spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);\n let stageVars = [];\n let stageLists = [];\n \n // Get stage variables\n const stage = vm.runtime.getTargetForStage();\n if (stage) {\n stageVars = stage.getAllVariableNamesInScopeByType('') || [];\n stageLists = stage.getAllVariableNamesInScopeByType('list') || [];\n }\n \n // For stage: only show stage variables\n // For sprite: show stage variables in \"For all sprites\", sprite-only in \"For this sprite only\"\n if (isStage) {\n variableRef.current = stageVars;\n listsRef.current = stageLists;\n spriteOnlyVariablesRef.current = [];\n spriteOnlyListsRef.current = [];\n } else {\n variableRef.current = stageVars;\n listsRef.current = stageLists;\n spriteOnlyVariablesRef.current = spriteVars;\n spriteOnlyListsRef.current = spriteLists;\n }\n \n // trigger render\n setVarsState(variableRef.current.slice());\n } catch (e) {\n // ignore\n }\n }\n\n updateVarsLists();\n vm.on('targetsUpdate', updateVarsLists);\n vm.on('PROJECT_CHANGED', updateVarsLists);\n return () => {\n vm.off('targetsUpdate', updateVarsLists);\n vm.off('PROJECT_CHANGED', updateVarsLists);\n };\n }, [vm]);\n\n // handlers for make variable/list and inserting into editor\n const [promptProps, setPromptProps] = React.useState(null);\n\n const makeVariable = (type = '') => {\n if (!vm) {\n alert('VM not available');\n return;\n }\n\n // Determine editing target and stage status\n const editing = vm.editingTarget || vm.runtime.getTargetForStage();\n const isStage = editing && editing.isStage;\n\n // Compute props for Prompt component similar to Blocks.handlePromptStart\n const title = type === 'list' ? 'Make a List' : 'Make a Variable';\n const varTypeConst = type === 'list' ? 'list' : '';\n const showListMessage = type === 'list';\n const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);\n\n setPromptProps({\n defaultValue: '',\n isStage: !!(editing && editing.isStage),\n showListMessage,\n label: type === 'list' ? 'List name' : 'Variable name',\n showCloudOption,\n showVariableOptions: true,\n title,\n varType: varTypeConst\n });\n };\n\n const insertIntoEditor = name => {\n if (!editorRef.current) return;\n try {\n const view = editorRef.current;\n const pos = view.state.selection.main.head;\n view.dispatch({changes: {from: pos, insert: name}});\n view.focus();\n } catch (e) {\n // ignore\n }\n };\n\n const handlePromptCancel = () => setPromptProps(null);\n const handlePromptOk = (input, variableOptions) => {\n try {\n const varType = (promptProps && promptProps.varType) || '';\n let allVarNames = [];\n if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {\n try {\n allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];\n } catch (e) {\n allVarNames = [];\n }\n }\n const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());\n if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {\n try {\n const stage = vm.runtime.getTargetForStage();\n if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {\n const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];\n for (const s of stageVars) {\n if (!allVarNames.includes(s)) allVarNames.push(s);\n }\n }\n } catch (e) {\n // ignore\n }\n }\n\n const ws = AddonHooks.blocklyWorkspace;\n const isLocal = variableOptions && variableOptions.scope === 'local';\n const isCloud = !!(variableOptions && variableOptions.isCloud);\n if (ws && typeof ws.createVariable === 'function') {\n try {\n ws.createVariable(input, varType, null, !!isLocal, !!isCloud);\n } catch (e) {\n // ignore\n }\n }\n } finally {\n setPromptProps(null);\n }\n };\n\n return (
\n {promptProps ? (\n \n ) : null}\n
\n

Variables

\n \n

For all sprites

\n
\n {(variableRef.current || []).map(v => (\n insertIntoEditor(v)}\n >{v}\n ))}\n
\n {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (\n <>\n

For this sprite only

\n
\n {spriteOnlyVariablesRef.current.map(v => (\n insertIntoEditor(v)}\n >{v}\n ))}\n
\n \n )}\n
\n makeVariable('')}\n >Make a Variable\n
\n\n

Lists

\n \n

For all sprites

\n
\n {(listsRef.current || []).map(l => (\n insertIntoEditor(l)}\n >{l}\n ))}\n
\n {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (\n <>\n

For this sprite only

\n
\n {spriteOnlyListsRef.current.map(l => (\n insertIntoEditor(l)}\n >{l}\n ))}\n
\n \n )}\n
\n makeVariable('list')}\n >Make a List\n
\n
\n \n
);\n}\n\nNanoscriptEditor.propTypes = {\n theme: PropTypes.instanceOf(Theme),\n vm: PropTypes.instanceOf(VM).isRequired\n};\n\nexport default NanoscriptEditor;\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/ob-nanoscript-editor/ob-codemirror-imports.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"export {EditorView} from '@codemirror/view';\nexport {basicSetup} from 'codemirror';\nexport {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language';\nexport {styleTags, tags} from '@lezer/highlight';\nexport {autocompletion, completeFromList} from '@codemirror/autocomplete';\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/toggle-buttons/toggle-buttons.jsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/containers/blocks.jsx","messages":[],"suppressedMessages":[{"ruleId":"max-len","severity":2,"message":"This line has a length of 138. Maximum allowed is 120.","line":53,"column":1,"nodeType":"Program","messageId":"max","endLine":53,"endColumn":139,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-len","severity":2,"message":"This line has a length of 138. Maximum allowed is 120.","line":59,"column":1,"nodeType":"Program","messageId":"max","endLine":59,"endColumn":139,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-len","severity":2,"message":"This line has a length of 148. Maximum allowed is 120.","line":65,"column":1,"nodeType":"Program","messageId":"max","endLine":65,"endColumn":149,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-len","severity":2,"message":"This line has a length of 124. Maximum allowed is 120.","line":71,"column":1,"nodeType":"Program","messageId":"max","endLine":71,"endColumn":125,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'anyModalVisible' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":669,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":669,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'canUseCloud' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":670,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":670,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'customStageSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":671,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":671,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'stageSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":675,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":675,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isRtl' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":677,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":677,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isVisible' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":678,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":678,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onActivateColorPicker' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":679,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":679,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onOpenConnectionModal' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":680,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":680,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onOpenSoundRecorder' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":681,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":681,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'updateToolboxState' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":684,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":684,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onActivateCustomProcedures' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":685,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":685,"endColumn":39,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onRequestCloseCustomProcedures' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":687,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":687,"endColumn":43,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'toolboxXML' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":688,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":688,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'updateMetricsProp' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":689,"column":28,"nodeType":"Identifier","messageId":"unusedVar","endLine":689,"endColumn":45,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'useCatBlocks' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":690,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":690,"endColumn":25,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'workspaceMetrics' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":691,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":691,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/containers/extension-library.jsx","messages":[{"ruleId":"react/no-unused-prop-types","severity":2,"message":"'onEnableProcedureReturns' PropType is defined but prop is never used","line":192,"column":5,"nodeType":"Identifier","messageId":"unusedPropType","endLine":192,"endColumn":29}],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":148,"column":25,"nodeType":"CallExpression","messageId":"unexpected","endLine":148,"endColumn":35,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import bindAll from 'lodash.bindall';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport VM from 'scratch-vm';\nimport {defineMessages, injectIntl, intlShape} from 'react-intl';\nimport log from '../lib/log';\n\nimport extensionLibraryContent, {\n galleryError,\n galleryLoading,\n galleryMore\n} from '../lib/libraries/extensions/index.jsx';\nimport extensionTags from '../lib/libraries/tw-extension-tags';\n\nimport LibraryComponent from '../components/library/library.jsx';\nimport extensionIcon from '../components/action-menu/icon--sprite.svg';\n\nconst messages = defineMessages({\n extensionTitle: {\n defaultMessage: 'Choose an Extension',\n description: 'Heading for the extension library',\n id: 'gui.extensionLibrary.chooseAnExtension'\n }\n});\n\nconst toLibraryItem = extension => {\n if (typeof extension === 'object') {\n return ({\n rawURL: extension.iconURL || extensionIcon,\n ...extension\n });\n }\n return extension;\n};\n\nconst translateGalleryItem = (extension, locale) => ({\n ...extension,\n name: extension.nameTranslations[locale] || extension.name,\n description: extension.descriptionTranslations[locale] || extension.description\n});\n\nlet cachedGallery = null;\n\nconst fetchLibrary = async () => {\n const res = await fetch('https://omniblocks.github.io/extensions/generated-metadata/extensions-v0.json');\n if (!res.ok) {\n throw new Error(`HTTP status ${res.status}`);\n }\n const data = await res.json();\n return data.extensions.map(extension => ({\n name: extension.name,\n nameTranslations: extension.nameTranslations || {},\n description: extension.description,\n descriptionTranslations: extension.descriptionTranslations || {},\n extensionId: extension.id,\n extensionURL: `https://omniblocks.github.io/extensions/${extension.slug}.js`,\n iconURL: `https://omniblocks.github.io/extensions/${extension.image || 'images/unknown.svg'}`,\n tags: ['tw'],\n credits: [\n ...(extension.original || []),\n ...(extension.by || [])\n ].map(credit => {\n if (credit.link) {\n return (\n \n {credit.name}\n \n );\n }\n return credit.name;\n }),\n docsURI: extension.docs ? `https://omniblocks.github.io/extensions/${extension.slug}` : null,\n samples: extension.samples ? extension.samples.map(sample => ({\n href: `${process.env.ROOT}editor?project_url=https://omniblocks.github.io/extensions/samples/${encodeURIComponent(sample)}.sb3`,\n text: sample\n })) : null,\n incompatibleWithScratch: !extension.scratchCompatible,\n featured: true\n }));\n};\n\nclass ExtensionLibrary extends React.PureComponent {\n constructor (props) {\n super(props);\n bindAll(this, [\n 'handleItemSelect'\n ]);\n this.state = {\n gallery: cachedGallery,\n galleryError: null,\n galleryTimedOut: false\n };\n }\n componentDidMount () {\n if (!this.state.gallery) {\n const timeout = setTimeout(() => {\n this.setState({\n galleryTimedOut: true\n });\n }, 750);\n\n fetchLibrary()\n .then(gallery => {\n cachedGallery = gallery;\n this.setState({\n gallery\n });\n clearTimeout(timeout);\n })\n .catch(error => {\n log.error(error);\n this.setState({\n galleryError: error\n });\n clearTimeout(timeout);\n });\n }\n }\n handleItemSelect (item) {\n if (item.href) {\n return;\n }\n\n const extensionId = item.extensionId;\n\n if (extensionId === 'custom_extension') {\n this.props.onOpenCustomExtensionModal();\n return;\n }\n\n const url = item.extensionURL ? item.extensionURL : extensionId;\n if (!item.disabled) {\n if (this.props.vm.extensionManager.isExtensionLoaded(extensionId)) {\n this.props.onCategorySelected(extensionId);\n } else {\n this.props.vm.extensionManager.loadExtensionURL(url)\n .then(() => {\n this.props.onCategorySelected(extensionId);\n })\n .catch(err => {\n log.error(err);\n // eslint-disable-next-line no-alert\n alert(err);\n });\n }\n }\n }\n render () {\n let library = null;\n if (this.state.gallery || this.state.galleryError || this.state.galleryTimedOut) {\n library = extensionLibraryContent.map(toLibraryItem);\n library.push('---');\n if (this.state.gallery) {\n library.push(toLibraryItem(galleryMore));\n const locale = this.props.intl.locale;\n library.push(\n ...this.state.gallery\n .map(i => translateGalleryItem(i, locale))\n .map(toLibraryItem)\n );\n } else if (this.state.galleryError) {\n library.push(toLibraryItem(galleryError));\n } else {\n library.push(toLibraryItem(galleryLoading));\n }\n }\n\n return (\n \n );\n }\n}\n\nExtensionLibrary.propTypes = {\n intl: intlShape.isRequired,\n onCategorySelected: PropTypes.func,\n onEnableProcedureReturns: PropTypes.func,\n onOpenCustomExtensionModal: PropTypes.func,\n onRequestClose: PropTypes.func,\n visible: PropTypes.bool,\n vm: PropTypes.instanceOf(VM).isRequired // eslint-disable-line react/no-unused-prop-types\n};\n\nexport default injectIntl(ExtensionLibrary);\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/containers/gui.jsx","messages":[],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'assetHost' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":84,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":84,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'cloudHost' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":85,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":85,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'error' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":86,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":86,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isError' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":87,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":87,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isScratchDesktop' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":88,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":88,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isShowingProject' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":89,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":89,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onProjectLoaded' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":90,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":90,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onStorageInit' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":91,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":91,"endColumn":26,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onUpdateProjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":92,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":92,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onVmInit' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":93,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":93,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'projectHost' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":94,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":94,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'projectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":95,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":95,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/brand.js","messages":[],"suppressedMessages":[{"ruleId":"import/no-commonjs","severity":2,"message":"Expected \"export\" or \"export default\"","line":3,"column":1,"nodeType":"MemberExpression","endLine":3,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"// Legacy export format because this is used by some build-time scripts stuck in the past.\n// eslint-disable-next-line import/no-commonjs\nmodule.exports = {\n APP_NAME: 'Visual IDE', // the name of the Scratch mod\n APP_NAMES: {\n PROJECT: 'OmniBlocks'\n },\n APP_VERSION: process.env.APP_VERSION || 'v0.5.8-alpha' // Dynamically injected at build time from git tags\n};\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/libraries/extensions/index.jsx","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'returnIcon' is defined but never used. Allowed unused vars must match /^_/u.","line":51,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":51,"endColumn":18}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from 'react';\nimport {FormattedMessage} from 'react-intl';\n\nimport musicIconURL from './music/music.png';\nimport musicInsetIconURL from './music/music-small.svg';\n\nimport penIconURL from './pen/pen.png';\nimport penInsetIconURL from './pen/pen-small.svg';\n\nimport videoSensingIconURL from './videoSensing/video-sensing.png';\nimport videoSensingInsetIconURL from './videoSensing/video-sensing-small.svg';\n\nimport text2speechIconURL from './text2speech/text2speech.png';\nimport text2speechInsetIconURL from './text2speech/text2speech-small.svg';\n\nimport translateIconURL from './translate/translate.png';\nimport translateInsetIconURL from './translate/translate-small.png';\n\nimport makeymakeyIconURL from './makeymakey/makeymakey.png';\nimport makeymakeyInsetIconURL from './makeymakey/makeymakey-small.svg';\n\nimport microbitIconURL from './microbit/microbit.png';\nimport microbitInsetIconURL from './microbit/microbit-small.svg';\nimport microbitConnectionIconURL from './microbit/microbit-illustration.svg';\nimport microbitConnectionSmallIconURL from './microbit/microbit-small.svg';\n\nimport ev3IconURL from './ev3/ev3.png';\nimport ev3InsetIconURL from './ev3/ev3-small.svg';\nimport ev3ConnectionIconURL from './ev3/ev3-hub-illustration.svg';\nimport ev3ConnectionSmallIconURL from './ev3/ev3-small.svg';\n\nimport wedo2IconURL from './wedo2/wedo.png'; // TODO: Rename file names to match variable/prop names?\nimport wedo2InsetIconURL from './wedo2/wedo-small.svg';\nimport wedo2ConnectionIconURL from './wedo2/wedo-illustration.svg';\nimport wedo2ConnectionSmallIconURL from './wedo2/wedo-small.svg';\nimport wedo2ConnectionTipIconURL from './wedo2/wedo-button-illustration.svg';\n\nimport boostIconURL from './boost/boost.png';\nimport boostInsetIconURL from './boost/boost-small.svg';\nimport boostConnectionIconURL from './boost/boost-illustration.svg';\nimport boostConnectionSmallIconURL from './boost/boost-small.svg';\nimport boostConnectionTipIconURL from './boost/boost-button-illustration.svg';\n\nimport gdxforIconURL from './gdxfor/gdxfor.png';\nimport gdxforInsetIconURL from './gdxfor/gdxfor-small.svg';\nimport gdxforConnectionIconURL from './gdxfor/gdxfor-illustration.svg';\nimport gdxforConnectionSmallIconURL from './gdxfor/gdxfor-small.svg';\n\nimport twIcon from './tw/tw.svg';\nimport customExtensionIcon from './custom/custom.svg';\nimport returnIcon from './custom/return.svg';\nimport galleryIcon from './gallery/gallery.svg';\nimport {APP_NAME} from '../../brand';\n\nexport default [\n {\n name: (\n \n ),\n extensionId: 'music',\n iconURL: musicIconURL,\n insetIconURL: musicInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'pen',\n iconURL: penIconURL,\n insetIconURL: penInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'videoSensing',\n iconURL: videoSensingIconURL,\n insetIconURL: videoSensingInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'text2speech',\n collaborator: 'Amazon Web Services',\n iconURL: text2speechIconURL,\n insetIconURL: text2speechInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n internetConnectionRequired: true\n },\n {\n name: (\n \n ),\n extensionId: 'translate',\n collaborator: 'Google',\n iconURL: translateIconURL,\n insetIconURL: translateInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n internetConnectionRequired: true\n },\n {\n name: 'Makey Makey',\n extensionId: 'makeymakey',\n collaborator: 'JoyLabz',\n iconURL: makeymakeyIconURL,\n insetIconURL: makeymakeyInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: 'micro:bit',\n extensionId: 'microbit',\n collaborator: 'micro:bit',\n iconURL: microbitIconURL,\n insetIconURL: microbitInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: false,\n connectionIconURL: microbitConnectionIconURL,\n connectionSmallIconURL: microbitConnectionSmallIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/microbit'\n },\n {\n name: 'LEGO MINDSTORMS EV3',\n extensionId: 'ev3',\n collaborator: 'LEGO',\n iconURL: ev3IconURL,\n insetIconURL: ev3InsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: false,\n connectionIconURL: ev3ConnectionIconURL,\n connectionSmallIconURL: ev3ConnectionSmallIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/ev3'\n },\n {\n name: 'LEGO BOOST',\n extensionId: 'boost',\n collaborator: 'LEGO',\n iconURL: boostIconURL,\n insetIconURL: boostInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: true,\n connectionIconURL: boostConnectionIconURL,\n connectionSmallIconURL: boostConnectionSmallIconURL,\n connectionTipIconURL: boostConnectionTipIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/boost'\n },\n {\n name: 'LEGO Education WeDo 2.0',\n extensionId: 'wedo2',\n collaborator: 'LEGO',\n iconURL: wedo2IconURL,\n insetIconURL: wedo2InsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: true,\n connectionIconURL: wedo2ConnectionIconURL,\n connectionSmallIconURL: wedo2ConnectionSmallIconURL,\n connectionTipIconURL: wedo2ConnectionTipIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/wedo'\n },\n {\n name: 'Go Direct Force & Acceleration',\n extensionId: 'gdxfor',\n collaborator: 'Vernier',\n iconURL: gdxforIconURL,\n insetIconURL: gdxforInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: false,\n connectionIconURL: gdxforConnectionIconURL,\n connectionSmallIconURL: gdxforConnectionSmallIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/vernier'\n },\n {\n name: (\n \n ),\n extensionId: 'tw',\n iconURL: twIcon,\n description: (\n \n ),\n incompatibleWithScratch: true,\n tags: ['tw'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'custom_extension',\n iconURL: customExtensionIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n // Not marked as incompatible with Scratch so that clicking on it doesn't show a prompt\n }\n];\n\nexport const galleryLoading = {\n name: (\n \n ),\n href: 'https://extensions.turbowarp.org/',\n extensionId: 'gallery',\n iconURL: galleryIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n};\n\nexport const galleryMore = {\n name: (\n \n ),\n href: 'https://extensions.turbowarp.org/',\n extensionId: 'gallery',\n iconURL: galleryIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n};\n\nexport const galleryError = {\n name: (\n \n ),\n href: 'https://extensions.turbowarp.org/',\n extensionId: 'gallery',\n iconURL: galleryIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n};\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/libraries/tw-extension-tags.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import {APP_NAME} from '../brand';\n\n// Because these are all brand names, it is unnecessary for them to be translatable.\nexport default [\n {tag: 'scratch', intlLabel: 'Scratch'},\n {tag: 'tw', intlLabel: 'TurboWarp'},\n {tag: 'ob', intlLabel: APP_NAME}\n];\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/tw-state-manager-hoc.jsx","messages":[],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":292,"column":21,"nodeType":"CallExpression","messageId":"unexpected","endLine":292,"endColumn":78,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":345,"column":21,"nodeType":"CallExpression","messageId":"unexpected","endLine":345,"endColumn":81,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-alert","severity":2,"message":"Unexpected confirm.","line":499,"column":22,"nodeType":"CallExpression","messageId":"unexpected","endLine":499,"endColumn":73,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'intl' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":515,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":515,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'customStageSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":516,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":516,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isFullScreen' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":517,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":517,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isPlayerOnly' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":518,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":518,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isEmbedded' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":519,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":519,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'projectChanged' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":520,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":520,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'compilerOptions' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":521,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":521,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'runtimeOptions' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":522,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":522,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'highQualityPen' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":523,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":523,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'framerate' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":524,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":524,"endColumn":26,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'interpolation' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":525,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":525,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'turbo' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":526,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":526,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetIsFullScreen' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":527,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":527,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetIsPlayerOnly' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":528,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":528,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetProjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":529,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":529,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetUsername' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":530,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":530,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'reduxProjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":531,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":531,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'routingStyle' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":532,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":532,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'username' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":533,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":533,"endColumn":25,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'vm' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":534,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":534,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/playground/credits/credits.jsx","messages":[],"suppressedMessages":[{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Credits\"","line":62,"column":27,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":63,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"The\"","line":66,"column":16,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":67,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"project is made possible by the work of many volunteers.\"","line":67,"column":31,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":68,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"TurboWarp\"","line":73,"column":21,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":73,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is based on\"","line":75,"column":31,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":75,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"TurboWarp\"","line":75,"column":77,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":75,"endColumn":86,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \".\"","line":75,"column":90,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":76,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Scratch\"","line":80,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":80,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is based on the work of the\"","line":82,"column":27,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":82,"endColumn":56,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Scratch contributors\"","line":82,"column":98,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":82,"endColumn":118,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"but is not endorsed by Scratch in any way.\"","line":82,"column":122,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":83,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Donate to support Scratch.\"","line":85,"column":58,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":87,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Contributors\"","line":91,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":91,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Addons\"","line":95,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":95,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"TurboWarp Extension Gallery\"","line":99,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":99,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Documentation\"","line":103,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":103,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Translators\"","line":107,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":107,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"More than 100 people have helped translate\"","line":108,"column":16,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":109,"endColumn":60,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"and its addons into many languages\n — far more than we could hope to list here.\"","line":109,"column":70,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":111,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Individual contributors are listed in no particular order.\n The order is randomized each visit.\"","line":115,"column":20,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":118,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport PropTypes from 'prop-types';\nimport render from '../app-target';\nimport styles from '../info.css';\nimport Header from '../ws-components/header/header.jsx';\n\nimport {APP_NAMES} from '../../lib/brand';\n\nconst APP_NAME = APP_NAMES.PROJECT;\nimport {applyGuiColors} from '../../lib/themes/guiHelpers';\nimport {detectTheme} from '../../lib/themes/themePersistance';\nimport UserData from './users';\n\n/* eslint-disable react/jsx-no-literals */\n\napplyGuiColors(detectTheme());\ndocument.documentElement.lang = 'en';\n\nconst User = ({image, text, href}) => (\n \n \n
\n {text}\n
\n \n);\nUser.propTypes = {\n image: PropTypes.string.isRequired,\n text: PropTypes.string.isRequired,\n href: PropTypes.string\n};\n\nconst UserList = ({users}) => (\n
\n {users.map((data, index) => (\n \n ))}\n
\n);\nUserList.propTypes = {\n users: PropTypes.arrayOf(PropTypes.object)\n};\n\nconst Credits = () => (\n <>
\n
\n

\n {APP_NAME} Credits\n

\n
\n
\n

\n The {APP_NAME} project is made possible by the work of many volunteers.\n

\n
\n {APP_NAME !== 'TurboWarp' && (\n // Be kind and considerate. Don't remove this :)\n
\n

TurboWarp

\n

\n {APP_NAME} is based on TurboWarp.\n

\n
\n )}\n
\n

Scratch

\n

\n {APP_NAME} is based on the work of the Scratch contributors but is not endorsed by Scratch in any way.\n

\n

\n \n Donate to support Scratch.\n \n

\n
\n
\n

Contributors

\n \n
\n
\n

Addons

\n \n
\n
\n

TurboWarp Extension Gallery

\n \n
\n
\n

Documentation

\n \n
\n
\n

Translators

\n

\n More than 100 people have helped translate {APP_NAME} and its addons into many languages\n — far more than we could hope to list here.\n

\n
\n
\n

\n \n Individual contributors are listed in no particular order.\n The order is randomized each visit.\n \n

\n
\n
\n);\n\nrender();\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/playground/home/home.jsx","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'PropTypes' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":17},{"ruleId":"max-len","severity":2,"message":"This line has a length of 131. Maximum allowed is 120.","line":50,"column":1,"nodeType":"Program","messageId":"max","endLine":50,"endColumn":132}],"suppressedMessages":[{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is a project to develop simple IDEs for programming in the browser.\"","line":50,"column":60,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":50,"endColumn":128,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Current IDEs\"","line":54,"column":21,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":54,"endColumn":33,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"— Coming soon\"","line":64,"column":84,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":64,"endColumn":98,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Open\"","line":71,"column":34,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":71,"endColumn":38,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is free software under the AGPL 3.0 license\"","line":79,"column":31,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":79,"endColumn":75,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"You can view the source code on\"","line":80,"column":20,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":81,"endColumn":53,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"GitHub\"","line":81,"column":105,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":81,"endColumn":111,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \".\"","line":81,"column":115,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":82,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport PropTypes from 'prop-types';\nimport render from '../app-target.js';\n\nimport {APP_NAMES} from '../../lib/brand';\n\nconst APP_NAME = APP_NAMES.PROJECT;\nimport {applyGuiColors} from '../../lib/themes/guiHelpers.js';\nimport {detectTheme} from '../../lib/themes/themePersistance.js';\nimport styles from '../info.css';\nimport localStyles from './home.css';\nimport Header from '../ws-components/header/header.jsx';\n/* eslint-disable react/jsx-no-literals */\n\napplyGuiColors(detectTheme());\ndocument.documentElement.lang = 'en';\n\nconst IDE_CARDS = [\n {\n title: 'Visual IDE',\n href: 'editor.html',\n desc: 'A Scratch mod with text-based programming support!'\n },\n {\n title: 'PyVisual',\n href: null,\n desc: 'Write Python code in blocks!',\n coming: true\n },\n {\n title: 'OmniPython',\n href: null,\n desc: 'An advanced Python IDE right in your browser.',\n coming: true\n },\n {\n title: 'And more...',\n href: null,\n desc: 'IDEs for C and more!',\n coming: true\n }\n];\n\nconst Credits = () => (\n <>\n
\n
\n
\n

{APP_NAME}

\n

{APP_NAME} is a project to develop simple IDEs for programming in the browser.

\n
\n\n
\n

Current IDEs

\n\n
\n {IDE_CARDS.map(ide => (\n \n

\n {ide.title}\n {ide.coming ? — Coming soon : null}\n

\n

{ide.desc}

\n {ide.href ? (\n Open\n ) : null}\n \n ))}\n
\n
\n\n
\n

{APP_NAME} is free software under the AGPL 3.0 license

\n

\n You can view the source code on GitHub.\n

\n
\n
\n \n);\n\nrender();\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/playground/ws-components/header/header.jsx","messages":[{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Credits\"","line":23,"column":10,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":25,"endColumn":9}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport styles from './header.css';\nimport logo from './costume1.svg';\nimport {APP_NAMES} from '../../../lib/brand';\n\nconst APP_NAME = APP_NAMES.PROJECT;\nconst Header = () => (\n <>
\n \n {APP_NAME}\n \n \n Credits\n \n
\n);\n\nexport default Header;\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/webpack.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"const defaultsDeep = require('lodash.defaultsdeep');\nconst path = require('path');\nconst webpack = require('webpack');\n\n// Plugins\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\n// PostCss\nconst autoprefixer = require('autoprefixer');\nconst postcssVars = require('postcss-simple-vars');\nconst postcssImport = require('postcss-import');\n\nconst STATIC_PATH = process.env.STATIC_PATH || '/static';\nconst {APP_NAME} = require('./src/lib/brand');\nconst {version} = require('./package.json');\n\nconst root = process.env.ROOT || '';\nif (root.length > 0 && !root.endsWith('/')) {\n throw new Error('If ROOT is defined, it must have a trailing slash.');\n}\n\nconst htmlWebpackPluginCommon = {\n root: root,\n meta: JSON.parse(process.env.EXTRA_META || '{}'),\n APP_NAME\n};\n\n// When this changes, the path for all JS files will change, bypassing any HTTP caches\nconst CACHE_EPOCH = 'pentapod';\n\nconst base = {\n mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',\n devtool: process.env.SOURCEMAP || (process.env.NODE_ENV === 'production' ? false : 'cheap-module-source-map'),\n devServer: {\n contentBase: path.resolve(__dirname, 'build'),\n host: '0.0.0.0',\n disableHostCheck: true,\n compress: true,\n port: process.env.PORT || 8601,\n // allows ROUTING_STYLE=wildcard to work properly\n historyApiFallback: {\n rewrites: [\n {from: /^\\/\\d+\\/?$/, to: '/index.html'},\n {from: /^\\/\\d+\\/fullscreen\\/?$/, to: '/fullscreen.html'},\n {from: /^\\/\\d+\\/editor\\/?$/, to: '/editor.html'},\n {from: /^\\/\\d+\\/embed\\/?$/, to: '/embed.html'},\n {from: /^\\/addons\\/?$/, to: '/addons.html'}\n ]\n },\n hot: true\n },\n output: {\n library: 'GUI',\n filename: (\n process.env.NODE_ENV === 'production' ? `js/${CACHE_EPOCH}/[name].[contenthash].js` : 'js/[name].js'\n ),\n chunkFilename: (\n process.env.NODE_ENV === 'production' ? `js/${CACHE_EPOCH}/[name].[contenthash].js` : 'js/[name].js'\n ),\n publicPath: root\n },\n resolve: {\n symlinks: false,\n alias: {\n 'text-encoding$': path.resolve(__dirname, 'src/lib/tw-text-encoder'),\n 'scratch-render-fonts$': path.resolve(__dirname, 'src/lib/tw-scratch-render-fonts')\n }\n },\n module: {\n rules: [{\n test: /\\.jsx?$/,\n loader: 'babel-loader',\n include: [\n path.resolve(__dirname, 'src'),\n /node_modules[\\\\/]scratch-[^\\\\/]+[\\\\/]src/,\n /node_modules[\\\\/]pify/,\n /node_modules[\\\\/]@vernier[\\\\/]godirect/\n ],\n options: {\n // Explicitly disable babelrc so we don't catch various config\n // in much lower dependencies.\n babelrc: false,\n plugins: [\n ['react-intl', {\n messagesDir: './translations/messages/'\n }]],\n presets: ['@babel/preset-env', '@babel/preset-react']\n }\n },\n {\n test: /\\.css$/,\n use: [{\n loader: 'style-loader'\n }, {\n loader: 'css-loader',\n options: {\n modules: true,\n importLoaders: 1,\n localIdentName: '[name]_[local]_[hash:base64:5]',\n camelCase: true\n }\n }, {\n loader: 'postcss-loader',\n options: {\n ident: 'postcss',\n plugins: function () {\n return [\n postcssImport,\n postcssVars,\n autoprefixer\n ];\n }\n }\n }]\n }]\n },\n plugins: [\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'node_modules/scratch-blocks/media',\n to: 'static/blocks-media/default'\n },\n {\n from: 'node_modules/scratch-blocks/media',\n to: 'static/blocks-media/high-contrast'\n },\n {\n from: 'src/lib/themes/blocks/high-contrast-media/blocks-media',\n to: 'static/blocks-media/high-contrast',\n force: true\n }\n ]\n })\n ]\n};\n\nif (!process.env.CI) {\n base.plugins.push(new webpack.ProgressPlugin());\n base.plugins.push(new webpack.HotModuleReplacementPlugin());\n}\n\nmodule.exports = [\n // to run editor examples\n defaultsDeep({}, base, {\n entry: {\n 'editor': './src/playground/editor.jsx',\n 'home': './src/playground/home/home.jsx',\n 'player': './src/playground/player.jsx',\n 'fullscreen': './src/playground/fullscreen.jsx',\n 'embed': './src/playground/embed.jsx',\n 'addon-settings': './src/playground/addon-settings.jsx',\n 'credits': './src/playground/credits/credits.jsx'\n },\n output: {\n path: path.resolve(__dirname, 'build')\n },\n module: {\n rules: base.module.rules.concat([\n {\n test: /\\.(svg|png|wav|mp3|gif|jpg|woff2|hex)$/,\n loader: 'url-loader',\n options: {\n limit: 2048,\n outputPath: 'static/assets/',\n esModule: false\n }\n }\n ])\n },\n optimization: {\n splitChunks: {\n chunks: 'all',\n minChunks: 2,\n minSize: 50000,\n maxInitialRequests: 5\n }\n },\n plugins: base.plugins.concat([\n new webpack.DefinePlugin({\n 'process.env.NODE_ENV': `\"${process.env.NODE_ENV}\"`,\n 'process.env.DEBUG': Boolean(process.env.DEBUG),\n 'process.env.ENABLE_SERVICE_WORKER': JSON.stringify(process.env.ENABLE_SERVICE_WORKER || ''),\n 'process.env.ROOT': JSON.stringify(root),\n 'process.env.ROUTING_STYLE': JSON.stringify(process.env.ROUTING_STYLE || 'filehash'),\n 'process.env.APP_VERSION': JSON.stringify(version || '')\n }),\n new HtmlWebpackPlugin({\n chunks: ['editor'],\n template: 'src/playground/index.ejs',\n filename: 'editor.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE | Editor`,\n isEditor: true,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['home'],\n template: 'src/playground/simple.ejs',\n filename: 'index.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['player'],\n template: 'src/playground/index.ejs',\n filename: 'player.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['fullscreen'],\n template: 'src/playground/index.ejs',\n filename: 'fullscreen.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['embed'],\n template: 'src/playground/embed.ejs',\n filename: 'embed.html',\n title: `Embedded Project - ${APP_NAME}`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['addon-settings'],\n template: 'src/playground/simple.ejs',\n filename: 'addons.html',\n title: `Addon Settings - ${APP_NAME}`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['credits'],\n template: 'src/playground/simple.ejs',\n filename: 'credits.html',\n title: `${APP_NAME} - Credits`,\n ...htmlWebpackPluginCommon\n }),\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'static',\n to: ''\n }\n ]\n }),\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'extensions/**',\n to: 'static',\n context: 'src/examples'\n }\n ]\n })\n ])\n })\n].concat(\n process.env.NODE_ENV === 'production' || process.env.BUILD_MODE === 'dist' ? (\n // export as library\n defaultsDeep({}, base, {\n target: 'web',\n entry: {\n 'scratch-gui': './src/index.js'\n },\n output: {\n libraryTarget: 'umd',\n filename: 'js/[name].js',\n chunkFilename: 'js/[name].js',\n path: path.resolve('dist'),\n publicPath: `${STATIC_PATH}/`\n },\n externals: {\n 'react': 'react',\n 'react-dom': 'react-dom'\n },\n module: {\n rules: base.module.rules.concat([\n {\n test: /\\.(svg|png|wav|mp3|gif|jpg|woff2|hex)$/,\n loader: 'url-loader',\n options: {\n limit: 2048,\n outputPath: 'static/assets/',\n publicPath: `${STATIC_PATH}/assets/`,\n esModule: false\n }\n }\n ])\n },\n plugins: base.plugins.concat([\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'extension-worker.{js,js.map}',\n context: 'node_modules/scratch-vm/dist/web',\n noErrorOnMissing: true\n }\n ]\n }),\n // Include library JSON files for scratch-desktop to use for downloading\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'src/lib/libraries/*.json',\n to: 'libraries',\n flatten: true\n }\n ]\n })\n ])\n })) : []\n);\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"global-require","replacedBy":[]},{"ruleId":"handle-callback-err","replacedBy":[]},{"ruleId":"no-mixed-requires","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"no-path-concat","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]}] diff --git a/package-lock.json b/package-lock.json index c18bf3d64..c04863b3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,14 @@ "version": "0.6.0-alpha", "license": "AGPL-3.0", "dependencies": { + "@codemirror/autocomplete": "^6.14.0", + "@codemirror/language": "^6.11.3", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.38.8", "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", + "@lezer/highlight": "^1.2.3", + "@lezer/lr": "^1.4.4", "@microbit/microbit-universal-hex": "0.2.2", "@turbowarp/jszip": "^3.11.1", "@turbowarp/nanolog": "^0.2.0", @@ -24,6 +30,7 @@ "base64-loader": "1.0.0", "bowser": "1.9.4", "classnames": "2.2.6", + "codemirror": "^6.0.2", "computed-style-to-inline-style": "3.0.0", "cookie": "0.5.0", "copy-webpack-plugin": "6.4.1", @@ -1941,6 +1948,80 @@ "dev": true, "license": "MIT" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", + "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", + "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -2960,6 +3041,32 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", + "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.4.tgz", + "integrity": "sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" + }, "node_modules/@microbit/microbit-universal-hex": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz", @@ -5964,6 +6071,20 @@ "node": ">= 0.12.0" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -6523,6 +6644,11 @@ "node": ">=8" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -20401,6 +20527,11 @@ "node": ">= 4" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==" + }, "node_modules/supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -21983,6 +22114,11 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "license": "MIT" }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index a476c219d..bcaffa396 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,14 @@ } }, "dependencies": { + "@codemirror/autocomplete": "^6.14.0", + "@codemirror/language": "^6.11.3", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.38.8", "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", + "@lezer/highlight": "^1.2.3", + "@lezer/lr": "^1.4.4", "@microbit/microbit-universal-hex": "0.2.2", "@turbowarp/jszip": "^3.11.1", "@turbowarp/nanolog": "^0.2.0", @@ -47,6 +53,7 @@ "base64-loader": "1.0.0", "bowser": "1.9.4", "classnames": "2.2.6", + "codemirror": "^6.0.2", "computed-style-to-inline-style": "3.0.0", "cookie": "0.5.0", "copy-webpack-plugin": "6.4.1", diff --git a/src/components/blocks/blocks.css b/src/components/blocks/blocks.css index 583f587f7..99a8e1a79 100644 --- a/src/components/blocks/blocks.css +++ b/src/components/blocks/blocks.css @@ -97,3 +97,7 @@ min-width: auto; min-height: auto; } + +.blocks :global(.blocklyZoom) { + transform: scale(0) !important; +} diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css index b07752975..55a68c4dc 100644 --- a/src/components/gui/gui.css +++ b/src/components/gui/gui.css @@ -54,6 +54,7 @@ display: flex; flex-direction: column; + min-height: 0; } .tab-list { @@ -343,6 +344,22 @@ $fade-out-distance: 15px; background-color: white; } +.nanoscript-container { + position: absolute; + bottom: 0; + right: 0; + margin: 20px; + margin-right: 32px; + display: flex; +} +.nanoscript-container.not-nano { + gap: $space; + margin-right: 30px; +} +.button-row { + background: $ui-white; + border-radius: $form-radius; +} /* Mobile support */ @media (max-width: 768px) { .flex-wrapper { diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 524a7fe61..8022d1796 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -37,10 +37,15 @@ 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 NanoscriptEditor from '../ob-nanoscript-editor/nanoscript-editor.jsx'; import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants'; import {resolveStageSize} from '../../lib/screen-utils'; import {Theme} from '../../lib/themes'; +import AddonHooks from '../../addons/hooks.js'; +import ToggleButtons from '../toggle-buttons/toggle-buttons.jsx'; +import Prompt from '../../containers/prompt.jsx'; +import nanoscriptIcon from '!../../lib/tw-recolor/build!./nanoscriptIcon.svg'; import {isRendererSupported, isBrowserSupported} from '../../lib/tw-environment-support-prober'; @@ -50,6 +55,7 @@ import codeIcon from '!../../lib/tw-recolor/build!./icon--code.svg'; import costumesIcon from '!../../lib/tw-recolor/build!./icon--costumes.svg'; import soundsIcon from '!../../lib/tw-recolor/build!./icon--sounds.svg'; import songsIcon from '!../../lib/tw-recolor/build!./icon--songs.svg'; +import SpinnerComponent from '../tw-loading-spinner/spinner.jsx'; const messages = defineMessages({ addExtension: { id: 'gui.gui.addExtension', @@ -399,7 +405,10 @@ const GUIComponent = props => { - + {isNano ? blocksTabVisible && : <> { theme={theme} vm={vm} /> - - - - + + + + } +
+ {!isNano && { + window.blocklyWorkspace.zoomCenter(1); + }, + isSelected: false, + children: '+' + }, + { + handleClick: () => { + window.blocklyWorkspace.zoomCenter(-1); + }, + isSelected: false, + children: '-' + }, + { + handleClick: () => { + window.blocklyWorkspace.setScale(0.675); + }, + isSelected: false, + children: '=' + } + ]} + />} + setNano(false), + icon: codeIcon, + isSelected: !isNano, + title: 'Block-based' + }, + { + handleClick: () => setNano(true), + icon: nanoscriptIcon, + isSelected: isNano, + title: 'Text-based' + } + ]} + /> +
diff --git a/src/components/gui/nanoscriptIcon.svg b/src/components/gui/nanoscriptIcon.svg new file mode 100644 index 000000000..13835fda1 --- /dev/null +++ b/src/components/gui/nanoscriptIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/gui/ob-codemirror-imports.js b/src/components/gui/ob-codemirror-imports.js new file mode 100644 index 000000000..bcd14ed4a --- /dev/null +++ b/src/components/gui/ob-codemirror-imports.js @@ -0,0 +1,5 @@ +export {EditorView} from '@codemirror/view'; +export {basicSetup} from 'codemirror'; +export {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language'; +export {styleTags, tags} from '@lezer/highlight'; +export {autocompletion, completeFromList} from '@codemirror/autocomplete'; diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.css b/src/components/ob-nanoscript-editor/nanoscript-editor.css new file mode 100644 index 000000000..fd3429d7a --- /dev/null +++ b/src/components/ob-nanoscript-editor/nanoscript-editor.css @@ -0,0 +1,45 @@ +@import "../../css/units.css"; +@import "../../css/colors.css"; + +.sidebar { + background: $ui-white; + padding: $space; + width: 20rem; + color: $text-primary; + user-select: none; + font-size: 0.9rem; + overflow: auto; + border: 1px solid $ui-black-transparent; +} + +.sidebar h2, .sidebar h3 { + font-size: 1rem; +} + +.codemirror { + --space: $space; + overflow: auto; +} + +.button { + appearance: none; + background: transparent; + padding: 6px; + border: $ui-black-transparent 1px solid; + border-radius: $space; +} + +.vars-list { + display: flex; + flex-direction: column; +} + +.var-item { + font-family: monospace; + color: $data-primary; + appearance: none; + border: none; + padding: 0; + text-align: left; + background: transparent; +} diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx new file mode 100644 index 000000000..85f76ab41 --- /dev/null +++ b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx @@ -0,0 +1,476 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import VM from 'scratch-vm'; +import Prompt from '../../containers/prompt.jsx'; +import AddonHooks from '../../addons/hooks.js'; +import {Theme} from '../../lib/themes'; +import styles from './nanoscript-editor.css'; + +function NanoscriptEditor ({theme, vm}) { + const el = React.useRef(null); + const editorRef = React.useRef(null); + const variableRef = React.useRef([]); + const listsRef = React.useRef([]); + const spriteOnlyVariablesRef = React.useRef([]); + const spriteOnlyListsRef = React.useRef([]); + const [, setVarsState] = React.useState([]); // used to trigger renders + + React.useEffect(() => { + let disposed = false; + + async function loadEditor () { + const { + EditorView, + basicSetup, + HighlightStyle, + syntaxHighlighting, + tags, + autocompletion, + completeFromList + } = await import(/* webpackChunkName: "nanoscript-editor"*/ './ob-codemirror-imports.js'); + + // Define hghlight rules using CSS vars + const scratchHighlight = HighlightStyle.define([ + {tag: tags.variableName, color: 'var(--data-primary)'}, + {tag: tags.keyword, color: 'var(--pen-primary)'}, + {tag: tags.string, color: 'var(--cm-string)'}, + {tag: tags.number, color: 'var(--cm-number)'}, + {tag: tags.function, color: 'var(--cm-function)'}, + {tag: tags.operator, color: 'var(--cm-operator)'} + ]); + + const {StreamLanguage} = await import('@codemirror/language'); + // helper to escape regex + const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const scratchSyntax = StreamLanguage.define({ + token (stream) { + // Keywords + if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return 'keyword'; + // Operators + if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return 'operator'; + // Functions + if (stream.match(/\b(join|pick random|length of)\b/)) return 'function'; + + // Try to match variable or list names (take current refs) + const names = (variableRef.current || []).concat(listsRef.current || []); + if (names && names.length) { + // sort by length to match longest first + const sorted = names.slice().sort((a, b) => b.length - a.length) + .map(escapeRegExp); + const rx = new RegExp(`^(${sorted.join('|')})`, 'i'); + const m = stream.match(rx, true); + if (m) { + return 'variableName'; + } + } + stream.next(); + return 'variable'; + } + }); + + // NanoScript autocomplete suggestions (static) + const staticCompletions = [ + // Control flow keywords + {label: 'when flag clicked', type: 'keyword'}, + {label: 'when key pressed', type: 'keyword'}, + {label: 'when this sprite clicked', type: 'keyword'}, + {label: 'when I start as a clone', type: 'keyword'}, + {label: 'forever', type: 'keyword'}, + {label: 'repeat', type: 'keyword'}, + {label: 'if', type: 'keyword'}, + {label: 'else', type: 'keyword'}, + {label: 'end', type: 'keyword'}, + {label: 'wait', type: 'keyword'}, + {label: 'stop', type: 'keyword'}, + + // Motion blocks + {label: 'move', type: 'function'}, + {label: 'turn right', type: 'function'}, + {label: 'turn left', type: 'function'}, + {label: 'go to', type: 'function'}, + {label: 'glide to', type: 'function'}, + {label: 'point in direction', type: 'function'}, + {label: 'point towards', type: 'function'}, + {label: 'change x by', type: 'function'}, + {label: 'set x to', type: 'function'}, + {label: 'change y by', type: 'function'}, + {label: 'set y to', type: 'function'}, + + // Looks blocks + {label: 'say', type: 'function'}, + {label: 'think', type: 'function'}, + {label: 'show', type: 'function'}, + {label: 'hide', type: 'function'}, + {label: 'switch costume to', type: 'function'}, + {label: 'next costume', type: 'function'}, + {label: 'change size by', type: 'function'}, + {label: 'set size to', type: 'function'}, + {label: 'change color effect by', type: 'function'}, + {label: 'set color effect to', type: 'function'}, + {label: 'clear graphic effects', type: 'function'}, + + // Sound blocks + {label: 'play sound', type: 'function'}, + {label: 'stop all sounds', type: 'function'}, + {label: 'change volume by', type: 'function'}, + {label: 'set volume to', type: 'function'}, + + // Events + {label: 'broadcast', type: 'function'}, + {label: 'broadcast and wait', type: 'function'}, + {label: 'when I receive', type: 'keyword'}, + + // Variables and lists + {label: 'set variable to', type: 'function'}, + {label: 'change variable by', type: 'function'}, + {label: 'add to list', type: 'function'}, + {label: 'delete from list', type: 'function'}, + {label: 'insert into list', type: 'function'}, + {label: 'replace list item', type: 'function'}, + + // Operators + {label: 'and', type: 'operator'}, + {label: 'or', type: 'operator'}, + {label: 'not', type: 'operator'}, + {label: 'join', type: 'function'}, + {label: 'letter of', type: 'function'}, + {label: 'length of', type: 'function'}, + {label: 'round', type: 'function'}, + {label: 'abs of', type: 'function'}, + {label: 'floor of', type: 'function'}, + {label: 'ceiling of', type: 'function'}, + {label: 'sqrt of', type: 'function'}, + {label: 'sin of', type: 'function'}, + {label: 'cos of', type: 'function'}, + {label: 'tan of', type: 'function'}, + {label: 'asin of', type: 'function'}, + {label: 'acos of', type: 'function'}, + {label: 'atan of', type: 'function'}, + {label: 'pick random', type: 'function'}, + + // Sensing blocks + {label: 'touching', type: 'function'}, + {label: 'touching color', type: 'function'}, + {label: 'color is touching', type: 'function'}, + {label: 'ask', type: 'function'}, + {label: 'key pressed', type: 'function'}, + {label: 'mouse down', type: 'function'}, + {label: 'distance to', type: 'function'} + ]; + + // Function to get completions with prefix matching (dynamic includes variables & lists) + function scratchCompletions (context) { + const word = context.matchBefore(/\w*/); + if (!word || (word.from === word.to && !context.explicit)) { + return null; + } + + const dynamicVars = (variableRef.current || []).map(v => ({label: v, type: 'variable', info: 'Variable'})); + const dynamicLists = (listsRef.current || []).map(l => ({label: l, type: 'variable', info: 'List'})); + const allOptions = staticCompletions.concat(dynamicVars, dynamicLists); + + return { + from: word.from, + options: allOptions.filter(option => + option.label.toLowerCase().startsWith(word.text.toLowerCase()) + ), + validFor: /\w*/ + }; + } + + if (!el.current || disposed) return; + + const cmTheme = EditorView.theme({ + '&': { + backgroundColor: 'var(--ui-white)', + color: 'var(--text-primary)', + height: '100%', + borderTopRightRadius: 'var(--space)', + borderBottomRightRadius: 'var(--space)', + border: '1px solid var(--ui-black-transparent)', + height: '100%' + }, + '.cm-scroller': { + maxHeight: '100%', + overflow: 'auto' + }, + '.cm-content': {caretColor: 'var(--looks-secondary)'}, + '.cm-cursor': {borderLeft: '2px solid var(--looks-secondary)'}, + '.cm-focused': {outline: 'none'}, + '.cm-selectionBackground, ::selection': {backgroundColor: 'rgba(255, 140, 26, 0.3)'}, + '.cm-gutters': { + backgroundColor: 'var(--ui-tertiary)', + borderRight: '1px solid var(--ui-black-transparent)' + }, + '.cm-completionLabel': { + fontSize: '13px' + } + }, {dark: theme.isDark() ?? false}); + + editorRef.current = new EditorView({ + doc: `when flag clicked +say "Hello World!" +repeat 10 + say (join "hi " "there") +end`, + extensions: [ + basicSetup, + scratchSyntax, + syntaxHighlighting(scratchHighlight), + autocompletion({override: [scratchCompletions]}), + cmTheme + ], + parent: el.current + }); + } + + loadEditor(); + + return () => { + disposed = true; + if (editorRef.current) { + editorRef.current.destroy(); + } + }; + }, [theme]); + + // Update variable/list refs from VM and listen for changes + React.useEffect(() => { + if (!vm) return undefined; + + function updateVarsLists () { + try { + const editing = vm.editingTarget || vm.runtime.getTargetForStage(); + if (!editing) { + variableRef.current = []; + listsRef.current = []; + spriteOnlyVariablesRef.current = []; + spriteOnlyListsRef.current = []; + setVarsState([]); + return; + } + + const isStage = editing.isStage; + const spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []); + const spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []); + let stageVars = []; + let stageLists = []; + + // Get stage variables + const stage = vm.runtime.getTargetForStage(); + if (stage) { + stageVars = stage.getAllVariableNamesInScopeByType('') || []; + stageLists = stage.getAllVariableNamesInScopeByType('list') || []; + } + + // For stage: only show stage variables + // For sprite: show stage variables in "For all sprites", sprite-only in "For this sprite only" + if (isStage) { + variableRef.current = stageVars; + listsRef.current = stageLists; + spriteOnlyVariablesRef.current = []; + spriteOnlyListsRef.current = []; + } else { + variableRef.current = stageVars; + listsRef.current = stageLists; + spriteOnlyVariablesRef.current = spriteVars; + spriteOnlyListsRef.current = spriteLists; + } + + // trigger render + setVarsState(variableRef.current.slice()); + } catch (e) { + // ignore + } + } + + updateVarsLists(); + vm.on('targetsUpdate', updateVarsLists); + vm.on('PROJECT_CHANGED', updateVarsLists); + return () => { + vm.off('targetsUpdate', updateVarsLists); + vm.off('PROJECT_CHANGED', updateVarsLists); + }; + }, [vm]); + + // handlers for make variable/list and inserting into editor + const [promptProps, setPromptProps] = React.useState(null); + + const makeVariable = (type = '') => { + if (!vm) { + alert('VM not available'); + return; + } + + // Determine editing target and stage status + const editing = vm.editingTarget || vm.runtime.getTargetForStage(); + const isStage = editing && editing.isStage; + + // Compute props for Prompt component similar to Blocks.handlePromptStart + const title = type === 'list' ? 'Make a List' : 'Make a Variable'; + const varTypeConst = type === 'list' ? 'list' : ''; + const showListMessage = type === 'list'; + const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false); + + setPromptProps({ + defaultValue: '', + isStage: !!(editing && editing.isStage), + showListMessage, + label: type === 'list' ? 'List name' : 'Variable name', + showCloudOption, + showVariableOptions: true, + title, + varType: varTypeConst + }); + }; + + const insertIntoEditor = name => { + if (!editorRef.current) return; + try { + const view = editorRef.current; + const pos = view.state.selection.main.head; + view.dispatch({changes: {from: pos, insert: name}}); + view.focus(); + } catch (e) { + // ignore + } + }; + + const handlePromptCancel = () => setPromptProps(null); + const handlePromptOk = (input, variableOptions) => { + try { + const varType = (promptProps && promptProps.varType) || ''; + let allVarNames = []; + if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') { + try { + allVarNames = vm.runtime.getAllVarNamesOfType(varType) || []; + } catch (e) { + allVarNames = []; + } + } + const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage()); + if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') { + try { + const stage = vm.runtime.getTargetForStage(); + if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') { + const stageVars = stage.getAllVariableNamesInScopeByType(varType) || []; + for (const s of stageVars) { + if (!allVarNames.includes(s)) allVarNames.push(s); + } + } + } catch (e) { + // ignore + } + } + + const ws = AddonHooks.blocklyWorkspace; + const isLocal = variableOptions && variableOptions.scope === 'local'; + const isCloud = !!(variableOptions && variableOptions.isCloud); + if (ws && typeof ws.createVariable === 'function') { + try { + ws.createVariable(input, varType, null, !!isLocal, !!isCloud); + } catch (e) { + // ignore + } + } + } finally { + setPromptProps(null); + } + }; + + return (
+ {promptProps ? ( + + ) : null} +
+

Variables

+ +

For all sprites

+
+ {(variableRef.current || []).map(v => ( + + ))} +
+ {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && ( + <> +

For this sprite only

+
+ {spriteOnlyVariablesRef.current.map(v => ( + + ))} +
+ + )} +
+ +
+ +

Lists

+ +

For all sprites

+
+ {(listsRef.current || []).map(l => ( + + ))} +
+ {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && ( + <> +

For this sprite only

+
+ {spriteOnlyListsRef.current.map(l => ( + + ))} +
+ + )} +
+ +
+
+
+
); +} + +NanoscriptEditor.propTypes = { + theme: PropTypes.instanceOf(Theme), + vm: PropTypes.instanceOf(VM).isRequired +}; + +export default NanoscriptEditor; diff --git a/src/components/ob-nanoscript-editor/ob-codemirror-imports.js b/src/components/ob-nanoscript-editor/ob-codemirror-imports.js new file mode 100644 index 000000000..bcd14ed4a --- /dev/null +++ b/src/components/ob-nanoscript-editor/ob-codemirror-imports.js @@ -0,0 +1,5 @@ +export {EditorView} from '@codemirror/view'; +export {basicSetup} from 'codemirror'; +export {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language'; +export {styleTags, tags} from '@lezer/highlight'; +export {autocompletion, completeFromList} from '@codemirror/autocomplete'; diff --git a/src/components/toggle-buttons/toggle-buttons.jsx b/src/components/toggle-buttons/toggle-buttons.jsx index ab6f35229..473d38cc0 100644 --- a/src/components/toggle-buttons/toggle-buttons.jsx +++ b/src/components/toggle-buttons/toggle-buttons.jsx @@ -25,12 +25,12 @@ const ToggleButtons = ({buttons, className, disabled}) => ( onClick={button.handleClick} disabled={disabled} > -
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 1a9370850..71b770720 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -229,6 +229,7 @@ class Blocks extends React.Component { } gentlyRequestPersistentStorage(); + this.handleEnableProcedureReturns(); } shouldComponentUpdate (nextProps, nextState) { return ( @@ -716,7 +717,6 @@ class Blocks extends React.Component { diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 4f986767b..6bd8dc719 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -133,12 +133,6 @@ class ExtensionLibrary extends React.PureComponent { return; } - if (extensionId === 'procedures_enable_return') { - this.props.onEnableProcedureReturns(); - this.props.onCategorySelected('myBlocks'); - return; - } - const url = item.extensionURL ? item.extensionURL : extensionId; if (!item.disabled) { if (this.props.vm.extensionManager.isExtensionLoaded(extensionId)) { diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index d20dd26dd..4a4fdabd2 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -6,6 +6,7 @@ import ReactModal from 'react-modal'; import VM from 'scratch-vm'; import {injectIntl, intlShape} from 'react-intl'; + import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; import { getIsError, diff --git a/src/lib/brand.js b/src/lib/brand.js index 0cc68f1e2..07db3bb29 100644 --- a/src/lib/brand.js +++ b/src/lib/brand.js @@ -1,6 +1,9 @@ // Legacy export format because this is used by some build-time scripts stuck in the past. // eslint-disable-next-line import/no-commonjs module.exports = { - APP_NAME: 'OmniBlocks', // the name of the mod + APP_NAME: 'Visual IDE', // the name of the Scratch mod + APP_NAMES: { + PROJECT: 'OmniBlocks' + }, APP_VERSION: process.env.APP_VERSION || 'v0.5.8-alpha' // Dynamically injected at build time from git tags -}; \ No newline at end of file +}; diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index bbf273884..962db9808 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -335,28 +335,6 @@ export default [ ), helpLink: 'https://scratch.mit.edu/vernier' }, - { - // not really an extension, but it's easiest to present it as one - name: ( - - ), - extensionId: 'procedures_enable_return', - iconURL: returnIcon, - description: ( - - ), - tags: ['tw'], - incompatibleWithScratch: true, - featured: true - }, { name: ( ( -
+ <>

{APP_NAME} Credits @@ -115,7 +118,7 @@ const Credits = () => (

-

+
); render(); diff --git a/src/playground/home/home.css b/src/playground/home/home.css new file mode 100644 index 000000000..2fb874227 --- /dev/null +++ b/src/playground/home/home.css @@ -0,0 +1,35 @@ +/* Styles for the home IDE cards */ +@import '../../css/colors.css'; + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; + margin-top: 12px; +} +.card { + background: $badge-background; + border-radius: 8px; + padding: 16px; + border: 1px solid $badge-border; + transition: transform .12s ease, box-shadow .12s ease; +} +.cardTitle { + margin: 0 0 8px 0; + font-size: 1.1rem; +} +.cardDesc { + margin: 0 0 10px 0; + color: $text-primary; +} +.cardLink { + display: inline-block; + margin-top: 6px; + color: $link-color; + text-decoration: none; +} +.coming { + font-weight: 400; + font-size: 0.9rem; + margin-left: 6px; +} diff --git a/src/playground/home/home.jsx b/src/playground/home/home.jsx new file mode 100644 index 000000000..b774b2715 --- /dev/null +++ b/src/playground/home/home.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import render from '../app-target.js'; + +import {APP_NAMES} from '../../lib/brand'; + +const APP_NAME = APP_NAMES.PROJECT; +import {applyGuiColors} from '../../lib/themes/guiHelpers.js'; +import {detectTheme} from '../../lib/themes/themePersistance.js'; +import styles from '../info.css'; +import localStyles from './home.css'; +import Header from '../ws-components/header/header.jsx'; +/* eslint-disable react/jsx-no-literals */ + +applyGuiColors(detectTheme()); +document.documentElement.lang = 'en'; + +const IDE_CARDS = [ + { + title: 'Visual IDE', + href: 'editor.html', + desc: 'A Scratch mod with text-based programming support!' + }, + { + title: 'PyVisual', + href: null, + desc: 'Write Python code in blocks!', + coming: true + }, + { + title: 'OmniPython', + href: null, + desc: 'An advanced Python IDE right in your browser.', + coming: true + }, + { + title: 'And more...', + href: null, + desc: 'IDEs for C and more!', + coming: true + } +]; + +const Credits = () => ( + <> +
+
+
+

{APP_NAME}

+

{APP_NAME} is a project to develop simple IDEs for programming in the browser.

+
+ +
+

Current IDEs

+ +
+ {IDE_CARDS.map(ide => ( +
+

+ {ide.title} + {ide.coming ? — Coming soon : null} +

+

{ide.desc}

+ {ide.href ? ( + Open + ) : null} +
+ ))} +
+
+ +
+

{APP_NAME} is free software under the AGPL 3.0 license

+

+ You can view the source code on GitHub. +

+
+
+ +); + +render(); diff --git a/src/playground/credits/credits.css b/src/playground/info.css similarity index 79% rename from src/playground/credits/credits.css rename to src/playground/info.css index 46674143c..262698bf4 100644 --- a/src/playground/credits/credits.css +++ b/src/playground/info.css @@ -1,4 +1,4 @@ -@import "../../css/colors.css"; +@import "../css/colors.css"; * { box-sizing: border-box; @@ -24,13 +24,18 @@ a { .header-container { color: white; - background-color: $looks-secondary; - padding: 20px 0; + background-color: $motion-tertiary; + padding: 30px 0; text-align: center; margin-bottom: 30px; + display: flex; + align-items: center; + flex-direction: column; + gap: 4px; } .header-text { - + max-width: 900px; + margin: 0; } .users { diff --git a/src/playground/ws-components/header/costume1.svg b/src/playground/ws-components/header/costume1.svg new file mode 100644 index 000000000..c790cb82f --- /dev/null +++ b/src/playground/ws-components/header/costume1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/playground/ws-components/header/header.css b/src/playground/ws-components/header/header.css new file mode 100644 index 000000000..b5d3ab2b0 --- /dev/null +++ b/src/playground/ws-components/header/header.css @@ -0,0 +1,49 @@ +@import "../../../css/colors.css"; +@import "../../../css/units.css"; + +.header { + background: $menu-bar-background; + color: $menu-bar-foreground; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + gap: $space; + height: $menu-bar-height; + position: fixed; + top: 0; left: 0; + width: 100%; + z-index: 900000; + box-shadow: 0 2px 4px $ui-black-transparent; +} + +.header-link, .header-logo { + color: $menu-bar-foreground; + text-decoration: none; + font-weight: bold; + padding: 4px 8px; + height: 100%; + display: flex; + align-items: center; +} + +.header-logo { + font-size: 1.4em; + transition: transform 0.2s ease-in-out; +} + +.header-logo:hover { + transform: scale(1.1); +} + +.header-link { + transition: background 0.1s ease-in-out; +} + +.header-link:hover { + background: $ui-black-transparent; +} + +.header-spacing-hack { + margin-bottom: $menu-bar-height; +} \ No newline at end of file diff --git a/src/playground/ws-components/header/header.jsx b/src/playground/ws-components/header/header.jsx new file mode 100644 index 000000000..8d6e7070b --- /dev/null +++ b/src/playground/ws-components/header/header.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import styles from './header.css'; +import logo from './costume1.svg'; +import {APP_NAMES} from '../../../lib/brand'; + +const APP_NAME = APP_NAMES.PROJECT; +const Header = () => ( + <>
+ + OmniBlocks Logo{APP_NAME} + + + Credits + +
+); + +export default Header; diff --git a/static/credits.html b/static/ssongeditorcredits.html similarity index 100% rename from static/credits.html rename to static/ssongeditorcredits.html diff --git a/webpack.config.js b/webpack.config.js index 1702c875a..247ce5dee 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -48,7 +48,7 @@ const base = { {from: /^\/addons\/?$/, to: '/addons.html'} ] }, - hot: true + hot: true }, output: { library: 'GUI', @@ -138,7 +138,7 @@ const base = { if (!process.env.CI) { base.plugins.push(new webpack.ProgressPlugin()); - base.plugins.push(new webpack.HotModuleReplacementPlugin()); + base.plugins.push(new webpack.HotModuleReplacementPlugin()); } module.exports = [ @@ -146,6 +146,7 @@ module.exports = [ defaultsDeep({}, base, { entry: { 'editor': './src/playground/editor.jsx', + 'home': './src/playground/home/home.jsx', 'player': './src/playground/player.jsx', 'fullscreen': './src/playground/fullscreen.jsx', 'embed': './src/playground/embed.jsx', @@ -183,7 +184,7 @@ module.exports = [ 'process.env.ENABLE_SERVICE_WORKER': JSON.stringify(process.env.ENABLE_SERVICE_WORKER || ''), 'process.env.ROOT': JSON.stringify(root), 'process.env.ROUTING_STYLE': JSON.stringify(process.env.ROUTING_STYLE || 'filehash'), - 'process.env.APP_VERSION': JSON.stringify(version || '') + 'process.env.APP_VERSION': JSON.stringify(version || '') }), new HtmlWebpackPlugin({ chunks: ['editor'], @@ -193,10 +194,17 @@ module.exports = [ isEditor: true, ...htmlWebpackPluginCommon }), + new HtmlWebpackPlugin({ + chunks: ['home'], + template: 'src/playground/simple.ejs', + filename: 'index.html', + title: `${APP_NAME} - The Ultimate MultiLanguage IDE`, + ...htmlWebpackPluginCommon + }), new HtmlWebpackPlugin({ chunks: ['player'], template: 'src/playground/index.ejs', - filename: 'index.html', + filename: 'player.html', title: `${APP_NAME} - The Ultimate MultiLanguage IDE`, ...htmlWebpackPluginCommon }),