├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── LICENSE ├── README-zh_CN.md ├── README.md ├── favicon.ico ├── index.html ├── package.json ├── plopfile.js ├── plugin-template ├── plugin-index-js.hbs ├── plugin-index-react-js.hbs ├── plugin-index-react-ts.hbs ├── plugin-index-ts.hbs ├── plugin-manifest.hbs └── styles.hbs ├── pnpm-lock.yaml ├── postcss.config.js ├── scripts ├── build-single-plugin.js ├── func-plugin-wrapper.hbs └── rc-plugin-wrapper.hbs ├── src ├── assets │ ├── icon--BetterSpriteMenu.svg │ ├── icon--batch-select.svg │ ├── icon--block-sharing.svg │ ├── icon--clean-pro.svg │ ├── icon--clones.svg │ ├── icon--code-filter.svg │ ├── icon--code-find.svg │ ├── icon--costumepiskel.svg │ ├── icon--custom-css.svg │ ├── icon--custom-plugin.svg │ ├── icon--data-category-tweaks.svg │ ├── icon--dev-tools.svg │ ├── icon--down.svg │ ├── icon--draggable.svg │ ├── icon--dropdown-searchable.svg │ ├── icon--extension-manager.svg │ ├── icon--fast-input.svg │ ├── icon--inspiro-import.svg │ ├── icon--inspiro.svg │ ├── icon--kukemcbeautify.svg │ ├── icon--list.svg │ ├── icon--multiselect-box.svg │ ├── icon--plugins-manage.svg │ ├── icon--statistics.svg │ ├── icon--switchCode.svg │ ├── icon--tack.svg │ ├── icon--tacked.svg │ ├── icon--terminal.svg │ ├── icon--trashcan.svg │ ├── icon--variables.svg │ ├── icon--voice--downarrow.svg │ ├── icon--voice--expand.svg │ ├── icon--voice--microphone.svg │ ├── icon--voice--muted-microphone.svg │ ├── icon--voice--off-white.svg │ ├── icon--voice--off.svg │ ├── icon--voice--setting.svg │ ├── icon--voice--uparrow.svg │ ├── icon--voice.svg │ └── icon--witcat-blockinput.svg ├── components │ ├── Bubble │ │ ├── index.tsx │ │ └── styles.less │ ├── ExpansionBox │ │ ├── index.tsx │ │ └── styles.less │ ├── IF │ │ └── index.tsx │ ├── Tab │ │ ├── index.tsx │ │ └── styles.less │ └── Tooltip │ │ ├── index.tsx │ │ └── styles.less ├── hooks │ └── useStorageInfo.ts ├── index.tsx ├── l10n │ ├── en.json │ ├── es.json │ ├── ms.json │ ├── ru.json │ ├── uk.json │ └── zh-cn.json ├── lib │ ├── block-media.ts │ ├── client-info.ts │ └── code-hash.json ├── main.ts ├── plugins-controller.ts ├── plugins-entry.ts ├── plugins-l10n.ts ├── plugins-manifest.ts ├── plugins │ ├── better-sprite-menu │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── block-sharing │ │ ├── components │ │ │ ├── Article.tsx │ │ │ ├── ArticleList.tsx │ │ │ ├── BluePrint.tsx │ │ │ ├── BluePrintList.tsx │ │ │ ├── Demo.tsx │ │ │ ├── DemoList.tsx │ │ │ └── Home.tsx │ │ ├── hack.js │ │ ├── icons │ │ │ └── home.svg │ │ ├── index.tsx │ │ ├── manifest.ts │ │ ├── material-drag.ts │ │ └── styles.less │ ├── clean-pro │ │ ├── index.tsx │ │ └── manifest.ts │ ├── code-batch-select │ │ ├── index.tsx │ │ ├── manifest.ts │ │ ├── styles.less │ │ ├── useBatchSelect.ts │ │ ├── useCheeryPick.ts │ │ ├── useKeyDownOperate.ts │ │ └── useRightContextMenu.ts │ ├── code-filter │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── code-find │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── code-switch │ │ ├── const.ts │ │ ├── index.tsx │ │ └── manifest.ts │ ├── costume-piskel │ │ ├── index.tsx │ │ └── manifest.ts │ ├── custom-css │ │ ├── PresetThemesList.md │ │ ├── colorUtils.ts │ │ ├── index.ts │ │ ├── manifest.ts │ │ └── presetThemes.less │ ├── custom-plugin │ │ ├── index.tsx │ │ └── manifest.ts │ ├── data-category-tweaks │ │ ├── index.tsx │ │ └── manifest.ts │ ├── dev-tools │ │ ├── components │ │ │ ├── CollapsibleItemView │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── Content │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── Entrance │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── ListView │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── TackedVariableView │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── TackedVariables │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── TargetView │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ ├── VariableView │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ │ └── VariablesView │ │ │ │ ├── index.tsx │ │ │ │ └── styles.less │ │ ├── index.tsx │ │ ├── lib │ │ │ ├── context.ts │ │ │ ├── dev-tools-observer.ts │ │ │ ├── event-bus.ts │ │ │ └── proxy-variable.ts │ │ └── manifest.ts │ ├── dropdown-searchable │ │ ├── index.tsx │ │ └── manifest.ts │ ├── extension-manager │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── fast-input │ │ ├── BlockRenderer.js │ │ ├── BlockTypeInfo.js │ │ ├── VirtualScroller.ts │ │ ├── WorkspaceQuerier.js │ │ ├── compatibility.css │ │ ├── index.ts │ │ ├── manifest.ts │ │ ├── module.js │ │ ├── pinyin_dict_notone.js │ │ └── styles.css │ ├── folder │ │ ├── index.tsx │ │ └── manifest.ts │ ├── historical-version │ │ ├── index.tsx │ │ └── manifest.ts │ ├── inspiro │ │ ├── componet │ │ │ ├── Chat.tsx │ │ │ ├── Contact.ts │ │ │ └── Entrance.tsx │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── kukemc-beautify │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── mobile-code-batch-select │ │ ├── index.tsx │ │ ├── manifest.ts │ │ ├── styles.less │ │ ├── touchZoom.ts │ │ ├── useBatchSelect.ts │ │ ├── useCheeryPick.ts │ │ ├── useKeyDownOperate.ts │ │ └── useRightContextMenu.ts │ ├── plugins-manager │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── statistics │ │ ├── index.tsx │ │ └── manifest.ts │ ├── terminal │ │ ├── index.tsx │ │ ├── manifest.ts │ │ └── styles.less │ ├── voice-cooperation │ │ ├── components │ │ │ ├── MemberList │ │ │ │ ├── MemberListItem.less │ │ │ │ └── MemberListItem.tsx │ │ │ ├── VoiceFloating │ │ │ │ ├── VoiceFloating.less │ │ │ │ └── VoiceFloating.tsx │ │ │ └── VoiceFloatingNew │ │ │ │ ├── VoiceFloatingNew.less │ │ │ │ └── VoiceFloatingNew.tsx │ │ ├── config.ts │ │ ├── dots.less │ │ ├── index.tsx │ │ ├── lib │ │ │ └── livekit.ts │ │ ├── manifest.ts │ │ └── styles.less │ └── witcat-blockinput │ │ ├── index.ts │ │ ├── lineText.js │ │ ├── manifest.ts │ │ └── style.less ├── types.d.ts ├── types │ ├── blockly.d.ts │ ├── interface.d.ts │ ├── scratch.d.ts │ ├── teamwork.d.ts │ └── utils.d.ts └── utils │ ├── block-flasher.ts │ ├── block-helper.ts │ ├── blocks-keywords-parser.ts │ ├── color.ts │ ├── dom-helper.ts │ ├── hotkey-helper.ts │ ├── index.ts │ ├── name-helper.ts │ ├── workspace-utils.ts │ └── xml.ts ├── tsconfig.json ├── webpack.config.js └── webpackDevServer.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | tab_width = 2 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [Makefile] 17 | indent_style = tab -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | build/* 3 | dist/* 4 | temp-wrapper.jsx -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "node": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"], 9 | "overrides": [], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "plugins": ["react", "@typescript-eslint", "prettier"], 16 | "rules": { 17 | "prettier/prettier": "error", 18 | "brace-style": "error", 19 | "no-tabs": 0, 20 | "space-before-function-paren": 0, 21 | "eol-last": 0, 22 | "no-unused-expressions": 0, 23 | "@typescript-eslint/no-var-requires": "off", 24 | "arrow-body-style": "off", 25 | "prefer-arrow-callback": "off", 26 | "react/display-name": "off" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | 5 | # dependencies 6 | /node_modules 7 | 8 | # production 9 | /dist 10 | /lib 11 | 12 | # log 13 | *.log 14 | 15 | # style types 16 | *.less.d.ts 17 | 18 | temp-wrapper.jsx -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 2, 4 | "printWidth": 120, 5 | "trailingComma": "all", 6 | "bracketSpacing": true, 7 | "semi": true 8 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/gandi-plugins/ed67c6596967de3fa7e08bc3d96687f330b67c8b/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gandi-plugins", 3 | "version": "0.0.1", 4 | "description": "Plug-ins for Gandi-IDE.", 5 | "main": "dist/index.js", 6 | "types": "lib/main.d.ts", 7 | "author": "Luka (https://github.com/zxq142857)", 8 | "license": "LGPL-3.0-only", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Gandi-IDE/gandi-plugins.git" 12 | }, 13 | "scripts": { 14 | "createPlugin": "plop", 15 | "start": "webpack-dev-server --config webpackDevServer.config.js", 16 | "startHostCocrea": "cross-env SITE=COCREA webpack-dev-server --config webpackDevServer.config.js", 17 | "build": "webpack --config webpack.config.js --progress", 18 | "buildSinglePlugin": "node scripts/build-single-plugin.js", 19 | "lint": "eslint src --ext .js,.ts", 20 | "prepublishOnly": "pnpm install && pnpm build", 21 | "clean": "rimraf lib && mkdirp lib && rimraf ./dist && mkdirp dist" 22 | }, 23 | "files": [ 24 | "dist", 25 | "lib" 26 | ], 27 | "babel": { 28 | "presets": [ 29 | "@babel/preset-env" 30 | ] 31 | }, 32 | "dependencies": { 33 | "@chatscope/chat-ui-kit-react": "^2.0.3", 34 | "@chatscope/chat-ui-kit-styles": "^1.4.0", 35 | "@chatscope/use-chat": "^3.1.2", 36 | "@formatjs/intl": "^2.5.1", 37 | "@gandi-ide/gandi-ui": "^1.0.8", 38 | "@legendapp/state": "^0.23.3", 39 | "@tanstack/react-virtual": "^3.0.0-beta.45", 40 | "classnames": "^2.3.2", 41 | "computed-style-to-inline-style": "^4.0.0", 42 | "deep-object-diff": "^1.1.9", 43 | "gandiblocks": "^1.0.2", 44 | "livekit-client": "^2.3.2", 45 | "lodash-es": "^4.17.21", 46 | "nanoid": "^5.0.7", 47 | "react": "^18.2.0", 48 | "react-dom": "^18.2.0", 49 | "react-draggable": "^4.4.5", 50 | "react-hot-toast": "^2.4.1", 51 | "react-intl": "^6.1.1", 52 | "react-markdown": "^10.1.0" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.19.3", 56 | "@babel/preset-env": "^7.19.3", 57 | "@svgr/webpack": "^6.3.1", 58 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", 59 | "@types/lodash-es": "^4.17.8", 60 | "@types/node": "^20.11.30", 61 | "@types/react": "^18.0.21", 62 | "@types/react-dom": "^18.0.6", 63 | "@typescript-eslint/eslint-plugin": "^5.38.1", 64 | "@typescript-eslint/parser": "^5.38.1", 65 | "autoprefixer": "^10.4.12", 66 | "axios": "^1.7.2", 67 | "babel-loader": "^8.2.5", 68 | "copy-webpack-plugin": "10.2.4", 69 | "cors": "^2.8.5", 70 | "cross-env": "^7.0.3", 71 | "css-loader": "^6.7.1", 72 | "eslint": "^8.57.0", 73 | "eslint-config-prettier": "^9.1.0", 74 | "eslint-plugin-prettier": "^5.1.3", 75 | "eslint-plugin-react": "^7.31.8", 76 | "handlebars": "^4.7.8", 77 | "html-webpack-plugin": "^5.6.0", 78 | "less": "^4.1.3", 79 | "less-loader": "^11.0.0", 80 | "plop": "^3.1.1", 81 | "postcss": "^8.4.17", 82 | "postcss-loader": "^7.0.1", 83 | "postcss-nested": "^6.0.0", 84 | "prettier": "^3.2.5", 85 | "react": "^18.2.0", 86 | "react-dom": "^18.2.0", 87 | "rimraf": "^3.0.2", 88 | "source-map-loader": "^4.0.0", 89 | "style-loader": "^3.3.1", 90 | "ts-loader": "^9.4.1", 91 | "typescript": "^4.8.3", 92 | "webpack": "^5.90.3", 93 | "webpack-cli": "^4.10.0", 94 | "webpack-dev-server": "^4.11.1", 95 | "yargs": "^17.7.2" 96 | }, 97 | "browserslist": [ 98 | "last 2 versions", 99 | "> 1%" 100 | ] 101 | } -------------------------------------------------------------------------------- /plugin-template/plugin-index-js.hbs: -------------------------------------------------------------------------------- 1 | import styles from "./styles.less"; 2 | 3 | const {{ componentName }} = () => { 4 | console.log("Hello world!"); 5 | return { 6 | dispose: () => { 7 | /** Remove some side effects */ 8 | }, 9 | }; 10 | }; 11 | 12 | export default {{ componentName }}; 13 | -------------------------------------------------------------------------------- /plugin-template/plugin-index-react-js.hbs: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styles from "./styles.less"; 3 | 4 | const {{ componentName }} = () => { 5 | return {"This is a new plugin."}; 6 | }; 7 | 8 | {{ componentName }}.displayName = "{{ componentName }}"; 9 | 10 | export default {{ componentName }}; 11 | -------------------------------------------------------------------------------- /plugin-template/plugin-index-react-ts.hbs: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styles from "./styles.less"; 3 | 4 | const {{ componentName }}: React.FC = () => { 5 | return {"This is a new plugin."}; 6 | }; 7 | 8 | {{ componentName }}.displayName = "{{ componentName }}"; 9 | 10 | export default {{ componentName }}; 11 | -------------------------------------------------------------------------------- /plugin-template/plugin-index-ts.hbs: -------------------------------------------------------------------------------- 1 | import styles from "./styles.less"; 2 | 3 | const {{ componentName }} = (context: PluginContext) => { 4 | return { 5 | dispose: () => { 6 | /** Remove some side effects */ 7 | }, 8 | }; 9 | }; 10 | 11 | export default {{ componentName }}; 12 | -------------------------------------------------------------------------------- /plugin-template/plugin-manifest.hbs: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "{{ name }}", 3 | type: "{{ type }}", 4 | description: "{{ description }}", 5 | credits: [ 6 | {{ credits }} 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /plugin-template/styles.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/gandi-plugins/ed67c6596967de3fa7e08bc3d96687f330b67c8b/plugin-template/styles.hbs -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("autoprefixer"), require("postcss-nested")], 3 | }; 4 | -------------------------------------------------------------------------------- /scripts/func-plugin-wrapper.hbs: -------------------------------------------------------------------------------- 1 | import pluginsL10n from "src/plugins-l10n"; 2 | import plugin from "./index"; 3 | import { createIntl, createIntlCache } from "@formatjs/intl"; 4 | 5 | const pluginName = "{{pluginName}}"; 6 | 7 | window.Scratch.plugins.register((context) => { 8 | const locale = context.intl.locale; 9 | const intl = createIntl({locale, messages: pluginsL10n[locale]}, createIntlCache()); 10 | const instance = plugin({ 11 | ...context, 12 | intl, 13 | msg: (id) => intl.formatMessage({ id }), 14 | }); 15 | return { 16 | dispose: instance.dispose || (() => { 17 | /* noop */ 18 | }), 19 | }; 20 | }, pluginName); 21 | -------------------------------------------------------------------------------- /scripts/rc-plugin-wrapper.hbs: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom/client"; 3 | import pluginsL10n from "src/plugins-l10n"; 4 | import PluginComponent from "./index"; 5 | import { createIntl, createIntlCache } from "@formatjs/intl"; 6 | 7 | const pluginName = "{{pluginName}}"; 8 | 9 | window.Scratch.plugins.register((context) => { 10 | const div = document.createElement("div"); 11 | div.setAttribute("data-plugin-name", pluginName); 12 | 13 | const pluginsWrapper = document.body.querySelector("#gandi-plugins-wrapper"); 14 | pluginsWrapper.appendChild(div); 15 | const locale = context.intl.locale; 16 | const intl = createIntl({locale, messages: pluginsL10n[locale]}, createIntlCache()); 17 | const Plugin = React.createElement(PluginComponent, { 18 | ...context, 19 | intl, 20 | msg: (id) => intl.formatMessage({ id }), 21 | }); 22 | const root = ReactDOM.createRoot(div); 23 | root.render(Plugin); 24 | 25 | return { 26 | dispose: () => { 27 | root.unmount(); 28 | pluginsWrapper.removeChild(div); 29 | }, 30 | }; 31 | }, pluginName); 32 | -------------------------------------------------------------------------------- /src/assets/icon--BetterSpriteMenu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icon--batch-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/icon--block-sharing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--clean-pro.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon--clones.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | -------------------------------------------------------------------------------- /src/assets/icon--code-filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/icon--code-find.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | -------------------------------------------------------------------------------- /src/assets/icon--costumepiskel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/icon--custom-css.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--custom-plugin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--data-category-tweaks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icon--dev-tools.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/icon--down.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--draggable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--dropdown-searchable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--extension-manager.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icon--fast-input.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/icon--inspiro-import.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/icon--inspiro.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/icon--kukemcbeautify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon--list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/icon--multiselect-box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--plugins-manage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icon--statistics.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--switchCode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--tack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--tacked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--terminal.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/icon--trashcan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icon--variables.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icon--voice--downarrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--voice--expand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon--voice--microphone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--voice--muted-microphone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--voice--off-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--voice--off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--voice--setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icon--voice--uparrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon--voice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icon--witcat-blockinput.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /src/components/Bubble/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import classNames from "classnames"; 4 | import styles from "./styles.less"; 5 | 6 | interface PopperProps { 7 | className?: string; 8 | left: number; 9 | top: number; 10 | text: string; 11 | visible: boolean; 12 | } 13 | 14 | interface BubbleProps { 15 | className?: string; 16 | title: string; 17 | children: React.ReactElement; 18 | } 19 | 20 | const Popper: React.FC = ({ className, visible, left, top, text = "" }) => { 21 | const [horizontalOffset, setHorizontalOffset] = React.useState(0); 22 | const containerRef = React.useRef(); 23 | 24 | React.useEffect(() => { 25 | if (visible) { 26 | let offset = 0; 27 | const bodyWidth = document.body.offsetWidth; 28 | const width = containerRef.current.offsetWidth / 2; 29 | if (left < width) { 30 | offset = width - left; 31 | } else if (left + width > bodyWidth) { 32 | offset = bodyWidth - (left + width); 33 | } 34 | setHorizontalOffset(offset); 35 | } 36 | }, [left, visible]); 37 | 38 | return visible 39 | ? ReactDOM.createPortal( 40 |
300 ? "80vw" : "190px", 47 | display: visible ? "" : "none", 48 | }} 49 | > 50 | {text} 51 | 57 |
, 58 | document.body, 59 | ) 60 | : null; 61 | }; 62 | 63 | const Bubble: React.FC = (props) => { 64 | const { title = "", children } = props; 65 | const [tipVisible, setTipVisible] = React.useState(false); 66 | const position = React.useRef({ x: 0, y: 0 }); 67 | 68 | const handleMouseEnter = (e: React.MouseEvent) => { 69 | const rect = (e.target as HTMLElement).getBoundingClientRect(); 70 | position.current.x = rect.x + rect.width / 2; 71 | position.current.y = rect.y + rect.height + 9; 72 | setTipVisible(true); 73 | }; 74 | 75 | const handleMouseLeave = () => { 76 | setTipVisible(false); 77 | }; 78 | 79 | return ( 80 | 81 | {React.cloneElement(children, { 82 | onMouseEnter: handleMouseEnter, 83 | onMouseLeave: handleMouseLeave, 84 | })} 85 | 86 | 87 | ); 88 | }; 89 | 90 | const areEqual = (prevProps: BubbleProps, nextProps: BubbleProps) => 91 | prevProps.className === nextProps.className && prevProps.title === nextProps.title; 92 | 93 | export default React.memo(Bubble, areEqual); 94 | -------------------------------------------------------------------------------- /src/components/Bubble/styles.less: -------------------------------------------------------------------------------- 1 | .tip { 2 | width: max-content; 3 | position: absolute; 4 | padding: 8px 12px; 5 | color: white; 6 | font-size: 12px; 7 | line-height: 18px; 8 | border-radius: 8px; 9 | box-sizing: border-box; 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | pointer-events: none; 14 | z-index: 910; 15 | border: var(--theme-border-size-tip) solid var(--theme-border-color-tip); 16 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 17 | transform: translate(-50%, 0); 18 | } 19 | 20 | .triangle { 21 | position: absolute; 22 | top: calc(-4.7px - var(--theme-border-size-tip)); 23 | left: 50%; 24 | width: 10px; 25 | height: 10px; 26 | transform: translate(-50%, 0) rotate(45deg); 27 | border-top: var(--theme-border-size-tip) solid var(--theme-border-color-tip); 28 | border-left: var(--theme-border-size-tip) solid var(--theme-border-color-tip); 29 | } 30 | 31 | .tip, 32 | .triangle { 33 | background: var(--theme-color-600); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/ExpansionBox/styles.less: -------------------------------------------------------------------------------- 1 | .container { 2 | position: fixed; 3 | top: 0; 4 | font-size: 12px; 5 | background: var(--theme-color-300); 6 | border: 1px solid var(--theme-color-200); 7 | border-radius: 8px; 8 | color: var(--theme-text-primary); 9 | display: flex; 10 | flex-direction: column; 11 | box-shadow: 12 | 0px 24px 20px rgba(32, 57, 94, 0.05), 13 | 0px 2px 20px rgba(0, 0, 0, 0.08); 14 | z-index: 101; 15 | } 16 | 17 | .container-header { 18 | position: relative; 19 | cursor: move; 20 | 21 | .title { 22 | font-size: 12px; 23 | line-height: 18px; 24 | font-weight: 400; 25 | text-align: center; 26 | margin: 0; 27 | padding: 3px 0; 28 | border-bottom: 1px solid var(--theme-color-350); 29 | white-space: nowrap; 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | } 33 | 34 | .close-button { 35 | position: absolute; 36 | top: 2px; 37 | left: 2px; 38 | line-height: 0; 39 | cursor: pointer; 40 | padding: 5px; 41 | border-radius: 6px; 42 | 43 | &:hover { 44 | background: var(--theme-color-200); 45 | } 46 | 47 | svg { 48 | width: 10px; 49 | height: 10px; 50 | } 51 | } 52 | } 53 | 54 | .containers { 55 | position: absolute; 56 | border-radius: 0 0 8px 8px; 57 | height: 100%; 58 | width: 100%; 59 | background-color: #0000; 60 | } 61 | 62 | .anchor { 63 | display: block; 64 | width: var(--anchor-point-size); 65 | height: var(--anchor-point-size); 66 | position: absolute; 67 | background: transparent; 68 | } 69 | 70 | .top { 71 | top: 0px; 72 | left: 0; 73 | width: 100%; 74 | height: 2px; 75 | cursor: ns-resize; 76 | } 77 | 78 | .bottom { 79 | bottom: 0px; 80 | left: 0; 81 | width: 100%; 82 | height: 2px; 83 | cursor: ns-resize; 84 | } 85 | 86 | .left { 87 | left: 0px; 88 | top: 0; 89 | width: 2px; 90 | height: 100%; 91 | cursor: ew-resize; 92 | } 93 | 94 | .right { 95 | right: 0px; 96 | top: 0; 97 | width: 2px; 98 | height: 100%; 99 | cursor: ew-resize; 100 | } 101 | 102 | .top-left { 103 | top: -1px; 104 | left: -1px; 105 | cursor: nwse-resize; 106 | } 107 | 108 | .bottom-left { 109 | bottom: -1px; 110 | left: -1px; 111 | cursor: nesw-resize; 112 | } 113 | 114 | .top-right { 115 | top: -1px; 116 | right: -1px; 117 | cursor: nesw-resize; 118 | } 119 | 120 | .bottom-right { 121 | bottom: -1px; 122 | right: -1px; 123 | cursor: nwse-resize; 124 | } 125 | 126 | .interlayer { 127 | position: absolute; 128 | top: 0; 129 | left: 0; 130 | width: 100%; 131 | height: 100%; 132 | background: transparent; 133 | z-index: 100; 134 | } 135 | -------------------------------------------------------------------------------- /src/components/IF/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IFProps { 4 | className?: string; 5 | condition: boolean; 6 | forceRender?: boolean; 7 | children: React.ReactNode; 8 | } 9 | 10 | const IF: React.FC = (props) => { 11 | const { className, condition, forceRender, children } = props; 12 | 13 | if (forceRender) { 14 | return condition ? <>{children} : null; 15 | } 16 | 17 | return ( 18 |
19 | {children} 20 |
21 | ); 22 | }; 23 | 24 | export default React.memo(IF); 25 | -------------------------------------------------------------------------------- /src/components/Tab/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import classNames from "classnames"; 3 | import styles from "./styles.less"; 4 | 5 | interface TabProps { 6 | className?: string; 7 | items: Array; 8 | activeIndex?: number; 9 | onChange?: (activeKey: number) => void; 10 | } 11 | 12 | const Tab = React.forwardRef((props, ref) => { 13 | const { className, items, activeIndex, onChange } = props; 14 | const [index, setIndex] = React.useState(activeIndex); 15 | 16 | React.useEffect(() => { 17 | if (typeof activeIndex !== undefined && index !== activeIndex) { 18 | setIndex(index); 19 | } 20 | }, [index, activeIndex]); 21 | 22 | return ( 23 |
24 | {items.map((label, idx) => ( 25 | 35 | ))} 36 |
37 | ); 38 | }); 39 | 40 | const areEqual = (prevProps: TabProps, nextProps: TabProps) => 41 | prevProps.className === nextProps.className && 42 | prevProps.items === nextProps.items && 43 | prevProps.onChange === nextProps.onChange; 44 | 45 | export default React.memo(Tab, areEqual); 46 | -------------------------------------------------------------------------------- /src/components/Tab/styles.less: -------------------------------------------------------------------------------- 1 | .tab { 2 | font-size: 12px; 3 | line-height: 18px; 4 | border-bottom: 1px solid var(--theme-color-50); 5 | } 6 | 7 | .tab .active { 8 | color: var(--theme-text-primary); 9 | border-bottom: 1px solid var(--theme-brand-color); 10 | } 11 | 12 | .tab button { 13 | background: none; 14 | outline: none; 15 | border: none; 16 | padding-bottom: 5px; 17 | color: var(--theme-color-g400); 18 | margin-bottom: -1px; 19 | } 20 | 21 | .tab button:nth-child(n + 2) { 22 | margin-left: 16px; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Tooltip/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import classNames from "classnames"; 4 | import styles from "./styles.less"; 5 | 6 | interface TipProps { 7 | left: number; 8 | top: number; 9 | tipText: string; 10 | visible: boolean; 11 | shortcutKey?: string[]; 12 | } 13 | 14 | interface TooltipProps { 15 | className?: string; 16 | icon?: React.ReactNode; 17 | tipText: string; 18 | shortcutKey?: string[]; 19 | onClick?: (e: React.MouseEvent) => void; 20 | } 21 | 22 | const Tip: React.FC = ({ visible, left, top, tipText, shortcutKey = [] }) => { 23 | const [horizontalOffset, setHorizontalOffset] = React.useState(0); 24 | const containerRef = React.useRef(); 25 | 26 | React.useEffect(() => { 27 | if (visible) { 28 | let offset = 0; 29 | const bodyWidth = document.body.offsetWidth; 30 | const width = containerRef.current.offsetWidth / 2; 31 | if (left < width) { 32 | offset = width - left; 33 | } else if (left + width > bodyWidth) { 34 | offset = bodyWidth - (left + width); 35 | } 36 | setHorizontalOffset(offset); 37 | } 38 | }, [left, visible]); 39 | 40 | return ReactDOM.createPortal( 41 |
50 | {tipText} 51 | {shortcutKey.map((key, idx) => ( 52 | 53 | {key} 54 | {idx === shortcutKey.length - 1 ? "" : "+"} 55 | 56 | ))} 57 | 63 |
, 64 | document.body, 65 | ); 66 | }; 67 | 68 | const Tooltip = React.forwardRef((props, ref) => { 69 | const { className, icon, tipText, shortcutKey, onClick } = props; 70 | const [tipVisible, setTipVisible] = React.useState(false); 71 | const position = React.useRef({ x: 0, y: 0 }); 72 | 73 | const handleMouseEnter = (e: React.MouseEvent) => { 74 | const rect = (e.target as HTMLElement).getBoundingClientRect(); 75 | position.current.x = rect.x + rect.width / 2; 76 | position.current.y = rect.y + rect.height + 9; 77 | setTipVisible(true); 78 | }; 79 | 80 | const handleMouseLeave = () => { 81 | setTipVisible(false); 82 | }; 83 | 84 | return ( 85 | 86 |
93 | {icon} 94 |
95 | 102 |
103 | ); 104 | }); 105 | 106 | const areEqual = (prevProps: TooltipProps, nextProps: TooltipProps) => 107 | prevProps.className === nextProps.className && 108 | prevProps.tipText === nextProps.tipText && 109 | prevProps.shortcutKey === nextProps.shortcutKey; 110 | 111 | export default React.memo(Tooltip, areEqual); 112 | -------------------------------------------------------------------------------- /src/components/Tooltip/styles.less: -------------------------------------------------------------------------------- 1 | .tip-icon { 2 | width: 24px; 3 | height: 24px; 4 | border-radius: 4px; 5 | cursor: pointer; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | z-index: 99; 10 | } 11 | 12 | .tip-icon:hover { 13 | background: var(--theme-color-200); 14 | } 15 | 16 | .tip-icon:hover .tip { 17 | visibility: unset; 18 | } 19 | 20 | .tip { 21 | position: absolute; 22 | height: 44px; 23 | padding: 0 16px; 24 | color: white; 25 | font-size: 12px; 26 | border-radius: 8px; 27 | box-sizing: border-box; 28 | display: flex; 29 | align-items: center; 30 | justify-content: space-between; 31 | pointer-events: none; 32 | z-index: 910; 33 | border: var(--theme-border-size-tip) solid var(--theme-border-color-tip); 34 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 35 | transform: translate(-50%, 0); 36 | white-space: nowrap; 37 | } 38 | 39 | .tip .code { 40 | line-height: 18px; 41 | color: #d1d5db; 42 | padding: 1px 6px; 43 | background: #3e495b; 44 | border-radius: 4px; 45 | margin: 0 2px; 46 | } 47 | 48 | .tip .text + .code { 49 | margin-left: 24px; 50 | } 51 | 52 | .triangle { 53 | position: absolute; 54 | top: calc(-4.7px - var(--theme-border-size-tip)); 55 | left: 50%; 56 | width: 10px; 57 | height: 10px; 58 | transform: translate(-50%, 0) rotate(45deg); 59 | border-top: var(--theme-border-size-tip) solid var(--theme-border-color-tip); 60 | border-left: var(--theme-border-size-tip) solid var(--theme-border-color-tip); 61 | } 62 | 63 | .tip, 64 | .triangle { 65 | background: var(--theme-color-600); 66 | } 67 | -------------------------------------------------------------------------------- /src/hooks/useStorageInfo.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo } from "react"; 2 | 3 | function useStorageInfo(key: string, defaultValue: T) { 4 | const defaultStr = useMemo(() => JSON.stringify(defaultValue), [defaultValue]); 5 | const storageValue: string = localStorage.getItem(key) || defaultStr; 6 | 7 | const value: T = useMemo(() => JSON.parse(storageValue) || defaultValue, [storageValue]); 8 | 9 | const setValue = useCallback( 10 | (data: T) => { 11 | localStorage.setItem(key, JSON.stringify(data)); 12 | }, 13 | [key], 14 | ); 15 | 16 | return [value, setValue] as const; 17 | } 18 | 19 | export default useStorageInfo; 20 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import * as ReactDOM from "react-dom/client"; 3 | import PluginsController from "./plugins-controller"; 4 | 5 | const App = () => { 6 | const pluginsController = React.useRef(); 7 | const initd = React.useRef(false); 8 | const initTimeout = React.useRef(1); 9 | const iframeRef = React.useRef(null); 10 | 11 | const handleInit = React.useCallback(() => { 12 | iframeRef.current.contentWindow.postMessage({ name: "plugins-inject", path: location.origin + "/main.js" }, "*"); 13 | if (!initd.current) { 14 | initTimeout.current = setTimeout(handleInit, 3000); 15 | } 16 | }, []); 17 | 18 | React.useEffect(() => { 19 | const onMessage = (event) => { 20 | if (event.data && event.data.name === "plugins-inject-success") { 21 | initd.current = true; 22 | if (initTimeout.current) { 23 | clearTimeout(initTimeout.current); 24 | initTimeout.current = null; 25 | } 26 | } 27 | if (event.data && event.data.name === "plugins-unmounted") { 28 | initd.current = false; 29 | handleInit(); 30 | } 31 | }; 32 | window.addEventListener("message", onMessage, false); 33 | return () => { 34 | window.removeEventListener("message", onMessage, false); 35 | }; 36 | }, [handleInit]); 37 | 38 | useEffect(() => { 39 | pluginsController.current = PluginsController; 40 | setTimeout(handleInit, 1000); 41 | }, [handleInit]); 42 | 43 | return ; 44 | }; 45 | 46 | const root = ReactDOM.createRoot(document.getElementById("root")); 47 | root.render(); 48 | -------------------------------------------------------------------------------- /src/lib/client-info.ts: -------------------------------------------------------------------------------- 1 | export const isMac = /macintosh|mac os x/i.test(navigator.userAgent); 2 | -------------------------------------------------------------------------------- /src/lib/code-hash.json: -------------------------------------------------------------------------------- 1 | { 2 | "[motion_movesteps, motion_movegrids,motion_turnright,motion_turnleft,motion_gotoxy,motion_glideto,motion_sety,motion_changeyby,motion_setx,motion_changexby,motion_pointindirection,motion_glidesecstoxy,looks_goforwardbackwardlayers,looks_seteffectto,looks_changeeffectby,looks_setsizeto,looks_changesizeby,sound_setvolumeto,sound_changevolumeby,sound_seteffectto,sound_changeeffectby,data_listcontainsitem,data_itemnumoflist,data_itemoflist,data_replaceitemoflist,data_insertatlist,data_deleteoflist,data_addtolist,operator_mathop,operator_round,operator_mod,operator_equals,operator_lt,operator_gt,operator_random,operator_divide,operator_multiply,operator_subtract,operator_add,askandwait,event_whengreaterthan]": "[operator_add,operator_subtract,operator_multiply,operator_divide,operator_random,data_variable,data_listcontents,data_itemoflist,data_itemnumoflist,data_lengthoflist,operator_mod,operator_round,operator_mathop,operator_length,xposition,yposition,direction,costumenumbername,backdropnumbername,size,volume,sensing_distanceto,sensing_mousex,sensing_mousey,loudness,timer,of,current,sensing_dayssince2000,sensing_username,operator_join,operator_letter_of]", 3 | "[looks_think,looks_thinkforsecs,looks_say,looks_sayforsecs,looks_switchbackdropto,switchcostumeto,askandwait,operator_join,operator_letter_of,operator_length,operator_contains]": "[operator_join,operator_letter_of,operator_length,answer,data_variable,data_listcontents,data_itemoflist,data_itemnumoflist,data_lengthoflist,operator_add,operator_subtract,operator_multiply,operator_divide,operator_random,xposition,yposition,direction,costumenumbername,backdropnumbername,size,volume,sensing_distanceto,sensing_mousex,sensing_mousey,loudness,timer,of,current,sensing_dayssince2000,sensing_username]", 4 | "[control_if,control_if_else,wait_until,repeat_until]": "[operator_gt,operator_lt,operator_equals,operator_and,operator_or,operator_not,operator_contains,data_listcontainsitem,sensing_touchingobject,sensing_touchingcolor,sensing_coloristouchingcolor,sensing_keypressed,sensing_mousedown]", 5 | "[event_whenflagclicked,event_whenbroadcastreceived,event_whenkeypressed,event_whenthisspriteclicked,event_whenbackdropswitchesto,event_whengreaterthan,]": "[data_setvariableto,data_changevariableby,looks_show,looks_hide,motion_gotoxy,motion_goto,control_forever,control_repeat,repeat_until,control_if,control_if_else,control_wait,data_deletealloflist]", 6 | "[control_forever]": "[control_if,control_if_else]", 7 | "[data_setvariableto,data_changevariableby,control_wait,control_repeat]": "[data_setvariableto,data_changevariableby,control_forever,control_repeat,repeat_until,control_if,control_if_else,control_wait,operator_add,operator_subtract,operator_multiply,operator_divide,operator_random,data_variable,data_listcontents,data_itemoflist,data_itemnumoflist,data_lengthoflist,operator_mod,operator_round,operator_mathop,operator_length,xposition,yposition,direction,costumenumbername,backdropnumbername,size,volume,sensing_distanceto,sensing_mousex,sensing_mousey,loudness,timer,of,current,sensing_dayssince2000,sensing_username,operator_join,operator_letter_of]", 8 | "[operator_or,operator_and,operator_not]": "[sensing_touchingobject,sensing_touchingcolor,sensing_coloristouchingcolor,sensing_keypressed,sensing_mousedown,operator_gt,operator_lt,operator_equals,operator_and,operator_or,operator_not]" 9 | } 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import PluginsController from "./plugins-controller"; 2 | import Plugins from "./plugins-entry"; 3 | 4 | export const PluginNames = Object.keys(Plugins); 5 | export { PluginsController as default }; 6 | -------------------------------------------------------------------------------- /src/plugins-entry.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prettier/prettier */ 2 | export default { 3 | folder: () => import(/* webpackChunkName: "plugin-folder" */ "src/plugins/folder"), 4 | "code-find": () => import(/* webpackChunkName: "plugin-code-find" */ "src/plugins/code-find"), 5 | "code-filter": () => import(/* webpackChunkName: "plugin-code-filter" */ "src/plugins/code-filter"), 6 | "dev-tools": () => import(/* webpackChunkName: "plugin-dev-tools" */ "src/plugins/dev-tools"), 7 | "code-switch": () => import(/* webpackChunkName: "plugin-code-switch" */ "src/plugins/code-switch"), 8 | terminal: () => import(/* webpackChunkName: "plugin-terminal" */ "src/plugins/terminal"), 9 | "code-batch-select": () => import(/* webpackChunkName: "plugin-code-batch-select" */ "src/plugins/code-batch-select"), 10 | "dropdown-searchable": () => 11 | import(/* webpackChunkName: "plugin-dropdown-searchable" */ "src/plugins/dropdown-searchable"), 12 | statistics: () => import(/* webpackChunkName: "plugin-statistics" */ "src/plugins/statistics"), 13 | "historical-version": () => 14 | import(/* webpackChunkName: "plugin-historical-version" */ "src/plugins/historical-version"), 15 | "custom-plugin": () => import(/* webpackChunkName: "plugin-custom-plugin" */ "src/plugins/custom-plugin"), 16 | "witcat-blockinput": () => import(/* webpackChunkName: "plugin-witcat-blockinput" */ "src/plugins/witcat-blockinput"), 17 | "kukemc-beautify": () => import(/* webpackChunkName: "plugin-kukemc-beautify" */ "src/plugins/kukemc-beautify"), 18 | "fast-input": () => import(/* webpackChunkName: "plugin-fast-input" */ "src/plugins/fast-input"), 19 | "better-sprite-menu": () => import(/* webpackChunkName: "plugin-better-sprite-menu" */ "plugins/better-sprite-menu"), 20 | inspiro: () => import(/* webpackChunkName: "plugin-inspiro" */ "src/plugins/inspiro"), 21 | "custom-css": () => import(/* webpackChunkName: "plugin-custom-css" */ "src/plugins/custom-css"), 22 | "extension-manager": () => import(/* webpackChunkName: "plugin-extension-manager" */ "src/plugins/extension-manager"), 23 | "voice-cooperation": () => import(/* webpackChunkName: "plugin-voice-cooperation" */ "src/plugins/voice-cooperation"), 24 | "block-sharing": () => import(/* webpackChunkName: "plugin-block-sharing" */ "plugins/block-sharing"), 25 | "costume-piskel": () => import(/* webpackChunkName: "plugin-costume-piskel" */ "src/plugins/costume-piskel"), 26 | "data-category-tweaks": () => import(/* webpackChunkName: "plugin-data-category-tweaks" */ "plugins/data-category-tweaks"), 27 | "mobile-code-batch-select": () => import(/* webpackChunkName: "plugin-mobile-code-batch-select" */ "src/plugins/mobile-code-batch-select"), 28 | "clean-pro": () => import(/* webpackChunkName: "plugin-clean-pro" */ "src/plugins/clean-pro"), 29 | } as const; 30 | -------------------------------------------------------------------------------- /src/plugins-l10n.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | en: require("./l10n/en.json"), 3 | es: require("./l10n/es.json"), 4 | ms: require("./l10n/ms.json"), 5 | ru: require("./l10n/ru.json"), 6 | uk: require("./l10n/uk.json"), 7 | "zh-cn": require("./l10n/zh-cn.json"), 8 | }; 9 | -------------------------------------------------------------------------------- /src/plugins-manifest.ts: -------------------------------------------------------------------------------- 1 | import folder from "src/plugins/folder/manifest"; 2 | import codeFind from "src/plugins/code-find/manifest"; 3 | import codeFilter from "src/plugins/code-filter/manifest"; 4 | import devTools from "src/plugins/dev-tools/manifest"; 5 | import codeSwitch from "src/plugins/code-switch/manifest"; 6 | import terminal from "src/plugins/terminal/manifest"; 7 | import codeBatchSelect from "src/plugins/code-batch-select/manifest"; 8 | import dropdownSearchable from "src/plugins/dropdown-searchable/manifest"; 9 | import statistics from "src/plugins/statistics/manifest"; 10 | import historicalVersion from "src/plugins/historical-version/manifest"; 11 | import customPlugin from "src/plugins/custom-plugin/manifest"; 12 | import witcatBlockinput from "src/plugins/witcat-blockinput/manifest"; 13 | import kukemcBeautify from "src/plugins/kukemc-beautify/manifest"; 14 | import fastInput from "src/plugins/fast-input/manifest"; 15 | import BetterSpriteMenu from "plugins/better-sprite-menu/manifest"; 16 | import inspiro from "plugins/inspiro/manifest"; 17 | import customCss from "src/plugins/custom-css/manifest"; 18 | import extensionManager from "src/plugins/extension-manager/manifest"; 19 | import voiceCooperation from "src/plugins/voice-cooperation/manifest"; 20 | import blockSharing from "plugins/block-sharing/manifest"; 21 | import costumePiskel from "src/plugins/costume-piskel/manifest"; 22 | import dataCategoryTweaks from "plugins/data-category-tweaks/manifest"; 23 | import mobileCodeBatchSelect from "src/plugins/mobile-code-batch-select/manifest"; 24 | import cleanPro from "src/plugins/clean-pro/manifest"; 25 | 26 | export default { 27 | folder, 28 | "code-find": codeFind, 29 | "code-filter": codeFilter, 30 | "dev-tools": devTools, 31 | "code-switch": codeSwitch, 32 | terminal, 33 | "code-batch-select": codeBatchSelect, 34 | "dropdown-searchable": dropdownSearchable, 35 | statistics, 36 | "historical-version": historicalVersion, 37 | "custom-plugin": customPlugin, 38 | "witcat-blockinput": witcatBlockinput, 39 | "kukemc-beautify": kukemcBeautify, 40 | "fast-input": fastInput, 41 | "better-sprite-menu": BetterSpriteMenu, 42 | inspiro, 43 | "custom-css": customCss, 44 | "extension-manager": extensionManager, 45 | "voice-cooperation": voiceCooperation, 46 | "block-sharing": blockSharing, 47 | "costume-piskel": costumePiskel, 48 | "data-category-tweaks": dataCategoryTweaks, 49 | "mobile-code-batch-select": mobileCodeBatchSelect, 50 | "clean-pro": cleanPro, 51 | }; 52 | -------------------------------------------------------------------------------- /src/plugins/better-sprite-menu/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import BetterSpriteMenuIcon from 'assets/icon--BetterSpriteMenu.svg'; 3 | import styles from './styles.less' 4 | 5 | let currentSpriteMenuLayout = 'default' 6 | const menuLayoutList = ["default", "grid", "compact", "superCompact"] 7 | 8 | const removeAllStyles = () => { 9 | document.body.classList.remove(styles.grid); 10 | document.body.classList.remove(styles.compact); 11 | document.body.classList.remove(styles.superCompact); 12 | } 13 | const updateSpriteMenuStyle = () => { 14 | removeAllStyles() 15 | switch(currentSpriteMenuLayout) { 16 | case "grid": 17 | document.body.classList.add(styles.grid); 18 | //stageInit() 19 | break; 20 | case "compact": 21 | document.body.classList.add(styles.compact); 22 | break; 23 | case "superCompact": 24 | document.body.classList.add(styles.superCompact); 25 | break; 26 | } 27 | } 28 | 29 | let collapsibleBox = document.querySelectorAll('.gandi_collapsible-box_collapsible-box_1_329')[1]; 30 | 31 | // Create a new MutationObserver instance 32 | let observer = new MutationObserver(function(mutations) { 33 | mutations.forEach(function(mutation) { 34 | if (mutation.attributeName === "class" && currentSpriteMenuLayout == "grid") { 35 | let targetElement = mutation.target as Element; 36 | let newClassList = targetElement.className.split(' '); 37 | 38 | if (newClassList.includes('gandi_collapsible-box_collapsed_oQuU1')) { 39 | console.log('The element contains the class "gandi_collapsible-box_collapsed_oQuU1"'); 40 | removeAllStyles(); 41 | } else { 42 | updateSpriteMenuStyle() 43 | } 44 | } 45 | }); 46 | }); 47 | 48 | let config = { attributes: true, attributeFilter: ['class'] }; 49 | observer.observe(collapsibleBox, config); 50 | 51 | 52 | const BetterSpriteMenu: React.FC = ({ redux, msg, registerSettings}) => { 53 | React.useEffect(() => { 54 | currentSpriteMenuLayout = 'default' 55 | const register = registerSettings( 56 | msg('plugins.betterSpriteMenu.title'), 57 | 'Better Sprite Menu', 58 | [ 59 | { 60 | key: 'layouts', 61 | label: msg('plugins.betterSpriteMenu.title'), 62 | description: msg("plugins.betterSpriteMenu.description"), 63 | items: [ 64 | { 65 | key: 'layout', 66 | type: 'select', 67 | label: msg('plugins.betterSpriteMenu.layouts.label'), 68 | value: currentSpriteMenuLayout, 69 | options: [ 70 | { label: msg('plugins.betterSpriteMenu.layouts.default'), value: menuLayoutList[0] }, 71 | { label: msg('plugins.betterSpriteMenu.layouts.grid'), value: menuLayoutList[1] }, 72 | { label: msg('plugins.betterSpriteMenu.layouts.compact'), value: menuLayoutList[2] }, 73 | { label: msg('plugins.betterSpriteMenu.layouts.superCompact'), value: menuLayoutList[3] }, 74 | ], 75 | onChange: (value) => { 76 | currentSpriteMenuLayout = value.toString(); 77 | updateSpriteMenuStyle(); 78 | }, 79 | }, 80 | ], 81 | }, 82 | ], 83 | , 84 | ); 85 | return () => { 86 | removeAllStyles() 87 | register.dispose(); 88 | }; 89 | }, [registerSettings, msg]); 90 | 91 | // Use the layout style in your render method 92 | return null; 93 | }; 94 | 95 | BetterSpriteMenu.displayName = 'BetterSpriteMenu'; 96 | 97 | export default BetterSpriteMenu; 98 | -------------------------------------------------------------------------------- /src/plugins/better-sprite-menu/manifest.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "better-sprite-menu", 3 | type: "component", 4 | description: "New varieties of sprite menu layout.", 5 | credits: [ 6 | { 7 | name: "fath11", 8 | link: "https://cocrea.world/@Fath11", 9 | }, 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/Article.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React from "react"; 3 | import classNames from "classnames"; 4 | 5 | interface ArticleProps { 6 | content: { url: string; block: string }; 7 | } 8 | 9 | const Article: React.FC = ({ content }) => { 10 | return ( 11 | 16 | ); 17 | }; 18 | 19 | export default Article; 20 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/ArticleList.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React from "react"; 3 | import Article from "./Article"; 4 | 5 | interface ArticleListProps { 6 | list: Array<{ url: string; block: string }>; 7 | msg: (key: string) => string; 8 | } 9 | 10 | const ArticleList: React.FC = ({ list, msg }) => { 11 | return ( 12 |
13 | {list.length === 0 ? ( 14 |

{msg("plugins.blockSharing.noArticle")}

15 | ) : ( 16 | list.map((item, index) =>
) 17 | )} 18 |
19 | ); 20 | }; 21 | 22 | export default ArticleList; 23 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/BluePrint.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React, { useRef } from "react"; 3 | import { Spinner } from "@gandi-ide/gandi-ui"; 4 | import classNames from "classnames"; 5 | 6 | interface BluePrintProps { 7 | content: { 8 | url: string; 9 | block: string; 10 | detail?: { creationRelease: { coverGifLink: number; coverLink: number }; description: string; type: Array }; 11 | }; 12 | msg: (key: string) => string; 13 | } 14 | 15 | const BluePrint: React.FC = ({ content, msg }) => { 16 | const imgRef = useRef(null); 17 | const [loading, setLoading] = React.useState(true); 18 | 19 | const dragstart = () => { 20 | if (content.detail && content.detail.type) { 21 | window.postMessage(["startDrop", content.detail], "*"); 22 | } else { 23 | window.postMessage(["startDrop", String(content.url)], "*"); 24 | } 25 | }; 26 | 27 | const dragend = (e: React.DragEvent) => { 28 | window.postMessage(["cancelDrop", [e.clientX, e.clientY]], "*"); 29 | }; 30 | 31 | const load = () => { 32 | setLoading(false); 33 | }; 34 | 35 | return ( 36 |
37 | {loading && ( 38 |
39 | 40 |
41 | )} 42 | {msg("plugins.blockSharing.bluePrint")} 53 |
54 | ); 55 | }; 56 | 57 | export default BluePrint; 58 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/BluePrintList.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React from "react"; 3 | import BluePrint from "./BluePrint"; 4 | 5 | interface BluePrintListProps { 6 | list: Array<{ url: string; block: string }>; 7 | msg: (key: string) => string; 8 | } 9 | 10 | const BluePrintList: React.FC = ({ list, msg }) => { 11 | return ( 12 |
13 | {list.length === 0 ? ( 14 |

{msg("plugins.blockSharing.noBluePrint")}

15 | ) : ( 16 | list.map((item, index) => ) 17 | )} 18 |
19 | ); 20 | }; 21 | 22 | export default BluePrintList; 23 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/Demo.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React, { useRef } from "react"; 3 | import { Spinner } from "@gandi-ide/gandi-ui"; 4 | import classNames from "classnames"; 5 | 6 | interface BluePrintProps { 7 | content: { 8 | url: string; 9 | block: string; 10 | detail?: { creationRelease: { coverGifLink: number; coverLink: number }; description: string; type: Array }; 11 | }; 12 | } 13 | 14 | const BluePrint: React.FC = ({ content }) => { 15 | const imgRef = useRef(null); 16 | const [loading, setLoading] = React.useState(true); 17 | const load = () => { 18 | setLoading(false); 19 | }; 20 | 21 | const handleClick = () => { 22 | window.open(`${content.url}?remixing=true`, "_blank"); 23 | }; 24 | 25 | return ( 26 |
27 | {loading && ( 28 |
29 | 30 |
31 | )} 32 | {content.detail.description} 46 |
47 | ); 48 | }; 49 | 50 | export default BluePrint; 51 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/DemoList.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React from "react"; 3 | import Demo from "./Demo"; 4 | 5 | interface BluePrintListProps { 6 | list: Array<{ url: string; block: string }>; 7 | msg: (key: string) => string; 8 | } 9 | 10 | const BluePrintList: React.FC = ({ list, msg }) => { 11 | return ( 12 |
13 | {list.length === 0 ? ( 14 |

{msg("plugins.blockSharing.noDemo")}

15 | ) : ( 16 | list.map((item, index) => ) 17 | )} 18 |
19 | ); 20 | }; 21 | 22 | export default BluePrintList; 23 | -------------------------------------------------------------------------------- /src/plugins/block-sharing/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import styles from "../styles.less"; 2 | import React, { useRef } from "react"; 3 | import { Spinner } from "@gandi-ide/gandi-ui"; 4 | import hack from "../hack"; 5 | 6 | interface ArticleProps { 7 | name: string; 8 | Jump: string; 9 | } 10 | 11 | const Article: React.FC = ({ name, Jump }) => { 12 | const iframeRef = useRef(null); 13 | const [loading, setLoading] = React.useState(true); 14 | 15 | React.useEffect(() => { 16 | hack.setLoad(setLoading); 17 | if (iframeRef.current) { 18 | iframeRef.current.onload = () => { 19 | hack.bluePrint = []; 20 | hack.article = []; 21 | hack.demo = []; 22 | // 在这里执行你的加载完成后的逻辑 23 | setTimeout(() => { 24 | setTimeout(() => { 25 | if (window.location.search.indexOf("Block") !== -1) { 26 | iframeRef.current.contentWindow.postMessage(["loadBlock"], "*"); 27 | } 28 | }, 2000); 29 | setLoading(false); 30 | }, 100); 31 | }; 32 | } 33 | }, []); 34 | 35 | return ( 36 |
37 | {loading && ( 38 |
39 | 40 |
41 | )} 42 |