first-commit
0 parents
Showing
83 changed files
with
2600 additions
and
0 deletions
.editorconfig
0 → 100644
.eslintignore
0 → 100644
.eslintrc.js
0 → 100644
.gitattributes
0 → 100644
| 1 | # Set the default behavior, in case people don't have core.autocrlf set. | ||
| 2 | * text=auto | ||
| 3 | |||
| 4 | # Explicitly specify line endings for as many files as possible. | ||
| 5 | # People who (for example) rsync between Windows and Linux need this. | ||
| 6 | |||
| 7 | # File types which we know are binary | ||
| 8 | |||
| 9 | # Prefer LF for most file types | ||
| 10 | *.css text eol=lf | ||
| 11 | *.htm text eol=lf | ||
| 12 | *.html text eol=lf | ||
| 13 | *.js text eol=lf | ||
| 14 | *.js.map text eol=lf | ||
| 15 | *.json text eol=lf | ||
| 16 | *.json5 text eol=lf | ||
| 17 | *.jsx text eol=lf | ||
| 18 | *.md text eol=lf | ||
| 19 | *.plist text eol=lf | ||
| 20 | *.xml text eol=lf | ||
| 21 | *.yml text eol=lf | ||
| 22 | *.yaml text eol=lf | ||
| 23 | |||
| 24 | # Prefer LF for these files | ||
| 25 | .editorconfig text eol=lf | ||
| 26 | .eslintignore text eol=lf | ||
| 27 | .gitattributes text eol=lf | ||
| 28 | .gitignore text eol=lf | ||
| 29 | .npmignore text eol=lf | ||
| 30 | LICENSE text eol=lf | ||
| 31 | Makefile text eol=lf | ||
| 32 | TRADEMARK text eol=lf | ||
| 33 | |||
| 34 | # Use CRLF for Windows-specific file types |
.github/CONTRIBUTING.md
0 → 100644
| 1 | ## Contributing | ||
| 2 | The development of Scratch is an ongoing process, and we love to have people in the Scratch and open source communities help us along the way. | ||
| 3 | |||
| 4 | ### Ways to Help | ||
| 5 | |||
| 6 | * **Documenting bugs** | ||
| 7 | * If you've identified a bug in Scratch you should first check to see if it's been filed as an issue, if not you can file one. Make sure you follow the issue template. | ||
| 8 | * It's important that we can consistently reproduce issues. When writing an issue, be sure to follow our [reproduction step guidelines](https://github.com/LLK/scratch-gui/wiki/Writing-good-repro-steps). | ||
| 9 | * Some issues are marked "Needs Repro". Adding a comment with good reproduction steps to those issues is a great way to help. | ||
| 10 | * If you don't have an issue in mind already, you can look through the [Bugs & Glitches forum.](https://scratch.mit.edu/discuss/3/) Look for users reporting problems, reproduce the problem yourself, and file new issues following our guidelines. | ||
| 11 | |||
| 12 | * **Fixing bugs** | ||
| 13 | * You can request to fix a bug in a comment on the issue if you at mention the repo coordinator, who for this repo is @cwillisf. | ||
| 14 | * If the issue is marked "Help Wanted" you can go ahead and start working on it! | ||
| 15 | * **We will only accept Pull Requests for bugs that have an issue filed that has a priority label** | ||
| 16 | * If you're interested in fixing a bug with no issue, file the issue first and wait for it to have a priority added to it. | ||
| 17 | |||
| 18 | * We are not looking for Pull Requests ("PR") for every issue and may deny a PR if it doesn't fit our criteria. | ||
| 19 | * We are far more likely to accept a PR if it is for an issue marked with Help Wanted. | ||
| 20 | * We will not accept PRs for issues marked with "Needs Discussion" or "Needs Design." | ||
| 21 | * Wait until the Repo Coordinator assigns the issue to you before you begin work or submit a PR. | ||
| 22 | |||
| 23 | ### Learning Git and Github | ||
| 24 | |||
| 25 | If you want to work on fixing issues, you should be familiar with Git and Github. | ||
| 26 | |||
| 27 | * [Learn Git branching](https://learngitbranching.js.org/) includes an introduction to basic git commands and useful branching features. | ||
| 28 | * Here's a general introduction to [contributing to an open source project](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). | ||
| 29 | |||
| 30 | **Important:** we follow the [Github Flow process](https://guides.github.com/introduction/flow/) as our development process. | ||
| 31 | |||
| 32 | ### How to Fix Bugs | ||
| 33 | 1. Identify which Github issue you are working on. Leave a comment on the issue to let us (and other contributors) know you're working on it. | ||
| 34 | 2. Make sure you have a fork of this repo (see [Github's forking a repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) for details) | ||
| 35 | 3. Switch to the `develop` branch, and pull down the latest changes from upstream | ||
| 36 | 4. Run the code, and reproduce the problem | ||
| 37 | 5. Create your branch from the `develop` branch | ||
| 38 | 6. Make code changes to fix the problem | ||
| 39 | 7. Run `npm test` to make sure that your changes pass our tests | ||
| 40 | 8. Commit your changes | ||
| 41 | 9. Push your branch to your fork | ||
| 42 | 10. Create your pull request | ||
| 43 | 1. Make sure to follow the template in the PR description | ||
| 44 | 1. Remember to check the “[Allow edits from maintainers](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)” box | ||
| 45 | |||
| 46 | When submitting pull requests keep in mind: | ||
| 47 | * please be patient -- it can take a while to find time to review them | ||
| 48 | * try to change the least amount of code necessary to fix the bug | ||
| 49 | * the code can't be radically changed without significant coordination with the Scratch Team, so these types of changes should be avoided | ||
| 50 | * if you find yourself changing a substantial amount of code or considering radical changes, please ask for clarification -- we may have envisioned a different approach, or underestimated the amount of effort | ||
| 51 | |||
| 52 | ### Suggestions | ||
| 53 |  | ||
| 54 | |||
| 55 | Please note: **_we are unlikely to accept PRs with new features that haven't been thought through and discussed as a group_**. | ||
| 56 | |||
| 57 | Why? Because we have a strong belief in the value of keeping things simple for new users. It's been said that the Scratch Team spends about one hour of design discussion for every pixel in Scratch. To learn more about our design philosophy, see [the Scratch Developers page](https://scratch.mit.edu/developers), or [this paper](http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf). | ||
| 58 | |||
| 59 | We welcome suggestions! If you want to suggest a feature, please post in our [suggestions forum](https://scratch.mit.edu/discuss/1/). Your suggestion will be helped if you include a mockup design; this can be simple, even hand-drawn. | ||
| 60 | |||
| 61 | ### Other resources | ||
| 62 | Beyond this repo, there are also some other resources that you might want to take a look at: | ||
| 63 | * [Community Guidelines](https://github.com/LLK/scratch-www/wiki/Community-Guidelines) (we find it important to maintain a constructive and welcoming community, just like on Scratch) | ||
| 64 | * [Open Source forum](https://scratch.mit.edu/discuss/49/) on Scratch | ||
| 65 | * [Suggestions forum](https://scratch.mit.edu/discuss/1/) on Scratch | ||
| 66 | * [Bugs & Glitches forum](https://scratch.mit.edu/discuss/3/) on Scratch |
.github/FUNDING.yml
0 → 100644
| 1 | ko_fi: arthurzheng |
.github/ISSUE_TEMPLATE.md
0 → 100644
| 1 | ### Expected Behavior | ||
| 2 | |||
| 3 | _Please describe what should happen_ | ||
| 4 | |||
| 5 | ### Actual Behavior | ||
| 6 | |||
| 7 | _Describe what actually happens_ | ||
| 8 | |||
| 9 | ### Steps to Reproduce | ||
| 10 | |||
| 11 | _Explain what someone needs to do in order to see what's described in *Actual behavior* above_ | ||
| 12 | |||
| 13 | ### Operating System and Browser | ||
| 14 | |||
| 15 | _e.g. Mac OS 10.11.6 Safari 10.0_ |
.github/PULL_REQUEST_TEMPLATE.md
0 → 100644
| 1 | ### Resolves | ||
| 2 | |||
| 3 | _What Github issue does this resolve (please include link)?_ | ||
| 4 | |||
| 5 | - Resolves # | ||
| 6 | |||
| 7 | ### Proposed Changes | ||
| 8 | |||
| 9 | _Describe what this Pull Request does_ | ||
| 10 | |||
| 11 | ### Reason for Changes | ||
| 12 | |||
| 13 | _Explain why these changes should be made_ | ||
| 14 | |||
| 15 | ### Test Coverage | ||
| 16 | |||
| 17 | _Please show how you have added tests to cover your changes_ |
.github/workflows/build-and-release.yml
0 → 100644
| 1 | name: Build and release | ||
| 2 | |||
| 3 | on: | ||
| 4 | push: | ||
| 5 | tags: | ||
| 6 | - 'v*' | ||
| 7 | paths-ignore: | ||
| 8 | - 'README.md' | ||
| 9 | - 'CHANGE.md' | ||
| 10 | - 'doc/*' | ||
| 11 | - '.github/*' | ||
| 12 | |||
| 13 | jobs: | ||
| 14 | create-release: | ||
| 15 | runs-on: ubuntu-20.04 | ||
| 16 | steps: | ||
| 17 | - uses: actions/checkout@v2 | ||
| 18 | |||
| 19 | - name: Get tag | ||
| 20 | id: tag | ||
| 21 | uses: dawidd6/action-get-tag@v1 | ||
| 22 | |||
| 23 | - name: Create Release | ||
| 24 | id: create_release | ||
| 25 | uses: actions/create-release@v1 | ||
| 26 | env: | ||
| 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| 28 | with: | ||
| 29 | tag_name: ${{ steps.tag.outputs.tag }} | ||
| 30 | release_name: OpenBlock Desktop ${{ steps.tag.outputs.tag }} | ||
| 31 | body: | | ||
| 32 | ## Change Log (en) | ||
| 33 | |||
| 34 | - **New feature** | ||
| 35 | |||
| 36 | 1. Feature 1. | ||
| 37 | 2. Feature 2. | ||
| 38 | |||
| 39 | - **Fix bug** | ||
| 40 | |||
| 41 | 1. Bug 1. | ||
| 42 | 2. Bug 2. | ||
| 43 | |||
| 44 | ## 更改日志 (zh-cn) | ||
| 45 | |||
| 46 | - **新功能** | ||
| 47 | |||
| 48 | 1. 功能1。 | ||
| 49 | 2. 功能2。 | ||
| 50 | |||
| 51 | - **修复错误** | ||
| 52 | |||
| 53 | 1. 错误1。 | ||
| 54 | 2. 错误2。 | ||
| 55 | draft: true | ||
| 56 | prerelease: false | ||
| 57 | |||
| 58 | build-windows: | ||
| 59 | needs: create-release | ||
| 60 | runs-on: windows-2019 | ||
| 61 | steps: | ||
| 62 | - uses: actions/checkout@v2 | ||
| 63 | |||
| 64 | - name: Use Node.js | ||
| 65 | uses: actions/setup-node@v2 | ||
| 66 | with: | ||
| 67 | node-version: '14.x' | ||
| 68 | |||
| 69 | - name: Npm Install | ||
| 70 | run: | | ||
| 71 | git config --global url."https://github.com".insteadOf ssh://git@github.com | ||
| 72 | npm ci | ||
| 73 | |||
| 74 | - name: Build and Publish | ||
| 75 | env: | ||
| 76 | GA_ID: ${{ secrets.GA_ID }} | ||
| 77 | GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| 78 | NODE_ENV: production | ||
| 79 | NODE_OPTIONS: --max_old_space_size=4096 | ||
| 80 | run: npm run publish | ||
| 81 | |||
| 82 | build-mac: | ||
| 83 | needs: create-release | ||
| 84 | runs-on: macos-13 | ||
| 85 | steps: | ||
| 86 | - uses: actions/checkout@v2 | ||
| 87 | |||
| 88 | - name: Use Node.js | ||
| 89 | uses: actions/setup-node@v2 | ||
| 90 | with: | ||
| 91 | node-version: '16.x' | ||
| 92 | |||
| 93 | - name: Install python2.7 | ||
| 94 | run: | | ||
| 95 | brew install pyenv | ||
| 96 | pyenv install 2.7.18 | ||
| 97 | pyenv global 2.7.18 | ||
| 98 | |||
| 99 | - name: Npm Install | ||
| 100 | run: | | ||
| 101 | pip install setuptools | ||
| 102 | npm ci | ||
| 103 | |||
| 104 | - name: Build and Publish | ||
| 105 | env: | ||
| 106 | GA_ID: ${{ secrets.GA_ID }} | ||
| 107 | GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| 108 | NODE_ENV: production | ||
| 109 | NODE_OPTIONS: --max_old_space_size=8192 | ||
| 110 | CSC_IDENTITY_AUTO_DISCOVERY: false | ||
| 111 | PYTHON_PATH: python | ||
| 112 | run: | | ||
| 113 | export PYTHON_PATH=$(pyenv root)/shims/python | ||
| 114 | npm run publish | ||
| 115 | |||
| 116 | build-linux: | ||
| 117 | needs: create-release | ||
| 118 | runs-on: ubuntu-20.04 | ||
| 119 | steps: | ||
| 120 | - uses: actions/checkout@v2 | ||
| 121 | |||
| 122 | - name: Use Node.js | ||
| 123 | uses: actions/setup-node@v2 | ||
| 124 | with: | ||
| 125 | node-version: '16.x' | ||
| 126 | |||
| 127 | - name: Npm Install | ||
| 128 | run: npm ci | ||
| 129 | |||
| 130 | - name: Build and Publish | ||
| 131 | env: | ||
| 132 | GA_ID: ${{ secrets.GA_ID }} | ||
| 133 | GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| 134 | NODE_ENV: production | ||
| 135 | NODE_OPTIONS: --max_old_space_size=8192 | ||
| 136 | CSC_IDENTITY_AUTO_DISCOVERY: false | ||
| 137 | run: npm run publish |
.github/workflows/build.yml
0 → 100644
| 1 | name: Build Test App | ||
| 2 | |||
| 3 | on: | ||
| 4 | push: | ||
| 5 | branches: [ main ] | ||
| 6 | paths-ignore: | ||
| 7 | - 'README.md' | ||
| 8 | - 'CHANGE.md' | ||
| 9 | - 'doc/*' | ||
| 10 | - '.github/*' | ||
| 11 | |||
| 12 | jobs: | ||
| 13 | build-windows: | ||
| 14 | runs-on: windows-2019 | ||
| 15 | steps: | ||
| 16 | - uses: actions/checkout@v2 | ||
| 17 | |||
| 18 | - name: Use Node.js | ||
| 19 | uses: actions/setup-node@v2 | ||
| 20 | with: | ||
| 21 | node-version: '14.x' | ||
| 22 | |||
| 23 | - name: Npm Install | ||
| 24 | run: | | ||
| 25 | git config --global url."https://github.com".insteadOf ssh://git@github.com | ||
| 26 | npm ci | ||
| 27 | |||
| 28 | - name: Build Dist | ||
| 29 | env: | ||
| 30 | GA_ID: ${{ secrets.GA_ID }} | ||
| 31 | NODE_ENV: production | ||
| 32 | NODE_OPTIONS: --max_old_space_size=4096 | ||
| 33 | run: npm run dist | ||
| 34 | |||
| 35 | - name: Archive production artifacts | ||
| 36 | uses: actions/upload-artifact@v2 | ||
| 37 | with: | ||
| 38 | name: OpenBlock-Desktop-win32 | ||
| 39 | path: dist/OpenBlock-Desktop*.exe | ||
| 40 | retention-days: 1 | ||
| 41 | |||
| 42 | build-mac: | ||
| 43 | runs-on: macos-13 | ||
| 44 | steps: | ||
| 45 | - uses: actions/checkout@v2 | ||
| 46 | |||
| 47 | - name: Use Node.js | ||
| 48 | uses: actions/setup-node@v2 | ||
| 49 | with: | ||
| 50 | node-version: '16.x' | ||
| 51 | |||
| 52 | - name: Install python2.7 | ||
| 53 | run: | | ||
| 54 | brew install pyenv | ||
| 55 | pyenv install 2.7.18 | ||
| 56 | pyenv global 2.7.18 | ||
| 57 | |||
| 58 | - name: Npm Install | ||
| 59 | run: | | ||
| 60 | pip install setuptools | ||
| 61 | npm ci | ||
| 62 | |||
| 63 | - name: Build Dist | ||
| 64 | env: | ||
| 65 | GA_ID: ${{ secrets.GA_ID }} | ||
| 66 | NODE_ENV: production | ||
| 67 | NODE_OPTIONS: --max_old_space_size=8192 | ||
| 68 | CSC_IDENTITY_AUTO_DISCOVERY: false | ||
| 69 | run: | | ||
| 70 | export PYTHON_PATH=$(pyenv root)/shims/python | ||
| 71 | npm run dist | ||
| 72 | |||
| 73 | - name: Archive production artifacts | ||
| 74 | uses: actions/upload-artifact@v2 | ||
| 75 | with: | ||
| 76 | name: OpenBlock-Desktop-darwin | ||
| 77 | path: dist/OpenBlock-Desktop*.dmg | ||
| 78 | retention-days: 1 | ||
| 79 | |||
| 80 | build-linux: | ||
| 81 | runs-on: ubuntu-20.04 | ||
| 82 | steps: | ||
| 83 | - uses: actions/checkout@v2 | ||
| 84 | |||
| 85 | - name: Use Node.js | ||
| 86 | uses: actions/setup-node@v2 | ||
| 87 | with: | ||
| 88 | node-version: '16.x' | ||
| 89 | |||
| 90 | - name: Npm Install | ||
| 91 | run: npm ci | ||
| 92 | |||
| 93 | - name: Build Dist | ||
| 94 | env: | ||
| 95 | GA_ID: ${{ secrets.GA_ID }} | ||
| 96 | NODE_ENV: production | ||
| 97 | NODE_OPTIONS: --max_old_space_size=8192 | ||
| 98 | CSC_IDENTITY_AUTO_DISCOVERY: false | ||
| 99 | run: npm run dist | ||
| 100 | |||
| 101 | - name: Archive production artifacts | ||
| 102 | uses: actions/upload-artifact@v2 | ||
| 103 | with: | ||
| 104 | name: OpenBlock-Desktop-linux | ||
| 105 | path: dist/OpenBlock-Desktop*.deb | ||
| 106 | retention-days: 1 |
.gitignore
0 → 100644
| 1 | # Mac OS | ||
| 2 | .DS_Store | ||
| 3 | |||
| 4 | # Windows | ||
| 5 | thumbs.db | ||
| 6 | |||
| 7 | # NPM | ||
| 8 | /node_modules | ||
| 9 | npm-* | ||
| 10 | |||
| 11 | # Testing | ||
| 12 | /.nyc_output | ||
| 13 | /coverage | ||
| 14 | .eslintcache | ||
| 15 | |||
| 16 | # Build | ||
| 17 | /build | ||
| 18 | /dist | ||
| 19 | /.opt-in | ||
| 20 | /*.provisionprofile | ||
| 21 | |||
| 22 | # don't store the assets downloaded with the `fetch` script | ||
| 23 | /static | ||
| 24 | |||
| 25 | # Temporary resource | ||
| 26 | /external-resources | ||
| 27 | /tools | ||
| 28 | /firmwares | ||
| 29 | /drivers | ||
| 30 | |||
| 31 | |||
| 32 | # generated translation files | ||
| 33 | /translations | ||
| 34 | /locale |
.npmignore
0 → 100644
.vscode/launch.json
0 → 100644
| 1 | { | ||
| 2 | // 使用 IntelliSense 了解相关属性。 | ||
| 3 | // 悬停以查看现有属性的描述。 | ||
| 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 | ||
| 5 | "version": "0.2.0", | ||
| 6 | "configurations": [ | ||
| 7 | { | ||
| 8 | "name": "Desktop", | ||
| 9 | "type": "node", | ||
| 10 | "request": "launch", | ||
| 11 | "cwd": "${workspaceFolder:scratch-desktop}", | ||
| 12 | "runtimeExecutable": "npm", | ||
| 13 | "autoAttachChildProcesses": true, | ||
| 14 | "runtimeArgs": ["start", "--"], | ||
| 15 | "protocol": "inspector", | ||
| 16 | "skipFiles": [ | ||
| 17 | // it seems like skipFiles only reliably works with 1 entry :( | ||
| 18 | //"<node_internals>/**", | ||
| 19 | "${workspaceFolder:scratch-desktop}/node_modules/electron/dist/resources/*.asar/**" | ||
| 20 | ], | ||
| 21 | "sourceMaps": true, | ||
| 22 | "timeout": 30000, | ||
| 23 | "outputCapture": "std" | ||
| 24 | } | ||
| 25 | ] | ||
| 26 | } |
CHANGE.md
0 → 100644
This diff is collapsed.
Click to expand it.
LICENSE
0 → 100644
| 1 | MIT License | ||
| 2 | |||
| 3 | Copyright (c) 2021 OpenBlock.cc | ||
| 4 | |||
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 6 | of this software and associated documentation files (the "Software"), to deal | ||
| 7 | in the Software without restriction, including without limitation the rights | ||
| 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 9 | copies of the Software, and to permit persons to whom the Software is | ||
| 10 | furnished to do so, subject to the following conditions: | ||
| 11 | |||
| 12 | The above copyright notice and this permission notice shall be included in all | ||
| 13 | copies or substantial portions of the Software. | ||
| 14 | |||
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 21 | SOFTWARE. | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
LICENSE.ScratchFoundation
0 → 100644
| 1 | Copyright (c) 2019, Scratch Foundation | ||
| 2 | All rights reserved. | ||
| 3 | |||
| 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | ||
| 5 | |||
| 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | ||
| 7 | |||
| 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | ||
| 9 | |||
| 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | ||
| 11 | |||
| 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
README.md
0 → 100644
| 1 | # openblock-desktop | ||
| 2 | |||
| 3 | ## 安装依赖 | ||
| 4 | |||
| 5 | 1. 需要稳定的代理工具 | ||
| 6 | |||
| 7 | 2. ```javascript | ||
| 8 | //代理到工具指定端口 | ||
| 9 | npm config set proxy=http://localhost:xxxx | ||
| 10 | //使用npm源 使用淘宝源 遇到了 包文件hash不一致的情况 | ||
| 11 | npx nrm use npm | ||
| 12 | ``` | ||
| 13 | |||
| 14 | 3. 至少需要python3.12的环境,原因`openblock-link`中的`@abandonware/bluetooth-hci-socket`的依赖库中指定了node-gyp的版本 `10.1.0` | ||
| 15 | |||
| 16 | |||
| 17 | |||
| 18 | |||
| 19 | |||
| 20 |  | ||
| 21 |  | ||
| 22 | |||
| 23 | ## Getting Start | ||
| 24 | |||
| 25 | Visit the wiki: [https://openblockcc.github.io](https://openblockcc.github.io) | ||
| 26 |
TRADEMARK
0 → 100644
| 1 | The Openblock trademarks, including the Openblock name, logo and the Openblock Devil Bird graphics(the "Marks"), are property of the Openblock.cc. Marks may not be used to endorse or promote non-software products products derived from this software without specific prior written permission. | ||
| 2 | |||
| 3 | Without retaining the openblock name and logo, Openblock Devil Bird cannot be used for redistribution and use in source code and binary form without prior express written permission. |
buildResources/.gitignore
0 → 100644
buildResources/OpenBlockDesktop.icns
0 → 100644
No preview for this file type
buildResources/OpenBlockDesktop.ico
0 → 100644
No preview for this file type
buildResources/OpenBlockFile.icns
0 → 100644
No preview for this file type
buildResources/OpenBlockFile.ico
0 → 100644
No preview for this file type
buildResources/appx/Square150x150Logo.png
0 → 100644
19.2 KB
buildResources/appx/Square44x44Logo.png
0 → 100644
15.8 KB
buildResources/appx/StoreLogo.png
0 → 100644
16.1 KB
buildResources/appx/Wide310x150Logo.png
0 → 100644
19.5 KB
buildResources/entitlements.mac.plist
0 → 100644
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| 3 | <plist version="1.0"> | ||
| 4 | <dict> | ||
| 5 | <key>com.apple.security.cs.allow-dyld-environment-variables</key> | ||
| 6 | <true/> | ||
| 7 | <key>com.apple.security.cs.allow-jit</key> | ||
| 8 | <true/> | ||
| 9 | <key>com.apple.security.cs.allow-unsigned-executable-memory</key> | ||
| 10 | <true/> | ||
| 11 | <key>com.apple.security.device.audio-input</key> | ||
| 12 | <true/> | ||
| 13 | <key>com.apple.security.device.camera</key> | ||
| 14 | <true/> | ||
| 15 | <key>com.apple.security.device.microphone</key> | ||
| 16 | <true/> | ||
| 17 | <key>com.apple.security.files.user-selected.read-only</key> | ||
| 18 | <true/> | ||
| 19 | <key>com.apple.security.files.user-selected.read-write</key> | ||
| 20 | <true/> | ||
| 21 | <key>com.apple.security.network.client</key> | ||
| 22 | <true/> | ||
| 23 | </dict> | ||
| 24 | </plist> |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| 3 | <plist version="1.0"> | ||
| 4 | <dict> | ||
| 5 | <key>com.apple.security.app-sandbox</key> | ||
| 6 | <true/> | ||
| 7 | <key>com.apple.security.inherit</key> | ||
| 8 | <true/> | ||
| 9 | </dict> | ||
| 10 | </plist> |
buildResources/entitlements.mas.plist
0 → 100644
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| 3 | <plist version="1.0"> | ||
| 4 | <dict> | ||
| 5 | <key>com.apple.security.app-sandbox</key> | ||
| 6 | <true/> | ||
| 7 | <key>com.apple.security.cs.allow-dyld-environment-variables</key> | ||
| 8 | <true/> | ||
| 9 | <key>com.apple.security.cs.allow-jit</key> | ||
| 10 | <true/> | ||
| 11 | <key>com.apple.security.cs.allow-unsigned-executable-memory</key> | ||
| 12 | <true/> | ||
| 13 | <key>com.apple.security.device.audio-input</key> | ||
| 14 | <true/> | ||
| 15 | <key>com.apple.security.device.camera</key> | ||
| 16 | <true/> | ||
| 17 | <key>com.apple.security.device.microphone</key> | ||
| 18 | <true/> | ||
| 19 | <key>com.apple.security.files.user-selected.read-only</key> | ||
| 20 | <true/> | ||
| 21 | <key>com.apple.security.files.user-selected.read-write</key> | ||
| 22 | <true/> | ||
| 23 | <key>com.apple.security.network.client</key> | ||
| 24 | <true/> | ||
| 25 | </dict> | ||
| 26 | </plist> |
buildResources/installer.nsh
0 → 100644
| 1 | !include x64.nsh | ||
| 2 | !include LogicLib.nsh | ||
| 3 | !include StrFunc.nsh | ||
| 4 | ${StrRep} | ||
| 5 | |||
| 6 | !macro preInit | ||
| 7 | |||
| 8 | ${If} ${RunningX64} | ||
| 9 | SetRegView 64 | ||
| 10 | ${EndIf} | ||
| 11 | |||
| 12 | ${StrRep} $0 "${UNINSTALL_REGISTRY_KEY}" "Software" "SOFTWARE" | ||
| 13 | ${StrRep} $1 "${INSTALL_REGISTRY_KEY}" "Software" "SOFTWARE" | ||
| 14 | |||
| 15 | ReadRegStr $R0 HKCU "$0" "UninstallString" | ||
| 16 | ReadRegStr $R1 HKCU "$1" "InstallLocation" | ||
| 17 | |||
| 18 | StrCmp $R0 "" 0 +4 | ||
| 19 | |||
| 20 | ReadRegStr $R0 HKLM "$0" "UninstallString" | ||
| 21 | ReadRegStr $R1 HKLM "$1" "InstallLocation" | ||
| 22 | |||
| 23 | StrCmp $R0 "" 0 done | ||
| 24 | StrCmp $R1 "" 0 done | ||
| 25 | |||
| 26 | WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\OpenBlockDesktop" | ||
| 27 | WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\OpenBlockDesktop" | ||
| 28 | |||
| 29 | done: | ||
| 30 | ${If} ${RunningX64} | ||
| 31 | SetRegView LastUsed | ||
| 32 | ${EndIf} | ||
| 33 | |||
| 34 | !macroend | ||
| 35 | |||
| 36 | !macro customUnInstall | ||
| 37 | |||
| 38 | ${If} ${RunningX64} | ||
| 39 | SetRegView 64 | ||
| 40 | ${EndIf} | ||
| 41 | |||
| 42 | DeleteRegKey HKLM "${INSTALL_REGISTRY_KEY}" | ||
| 43 | DeleteRegKey HKCU "${INSTALL_REGISTRY_KEY}" | ||
| 44 | |||
| 45 | ${If} ${RunningX64} | ||
| 46 | SetRegView LastUsed | ||
| 47 | ${EndIf} | ||
| 48 | |||
| 49 | !macroend |
buildResources/linux/512x512.png
0 → 100644
32.5 KB
buildResources/make-icons.sh
0 → 100755
| 1 | #!/bin/bash | ||
| 2 | SRC=../src/icon/OpenBlockDesktop.svg | ||
| 3 | OUT_ICONSET=OpenBlockDesktop.iconset | ||
| 4 | OUT_ICNS=OpenBlockDesktop.icns | ||
| 5 | OUT_ICO=OpenBlockDesktop.ico | ||
| 6 | TMP_ICO=tmp | ||
| 7 | |||
| 8 | ICO_BASIC_SIZES="16 24 32 48 256" | ||
| 9 | ICO_EXTRA_SIZES="20 30 36 40 60 64 72 80 96 512" | ||
| 10 | |||
| 11 | if command -v pngcrush >/dev/null 2>&1; then | ||
| 12 | function optimize () { | ||
| 13 | pngcrush -new -brute -ow "$@" | ||
| 14 | } | ||
| 15 | else | ||
| 16 | echo "pngcrush is not available - skipping PNG optimization" | ||
| 17 | function optimize () { | ||
| 18 | echo "Not optimizing:" "$@" | ||
| 19 | } | ||
| 20 | fi | ||
| 21 | |||
| 22 | # usage: resize newWidth newHeight input output [otherOptions...] | ||
| 23 | function resize () { | ||
| 24 | WIDTH=$1 | ||
| 25 | HEIGHT=$2 | ||
| 26 | SRC=$3 | ||
| 27 | DST=$4 | ||
| 28 | shift 4 | ||
| 29 | convert -background none -resize "${WIDTH}x${HEIGHT}" -extent "${WIDTH}x${HEIGHT}" -gravity center "$@" "${SRC}" "${DST}" | ||
| 30 | optimize "${DST}" | ||
| 31 | } | ||
| 32 | |||
| 33 | if command -v convert >/dev/null 2>&1; then | ||
| 34 | # Mac | ||
| 35 | if command -v iconutil >/dev/null 2>&1; then | ||
| 36 | mkdir -p "${OUT_ICONSET}" | ||
| 37 | for SIZE in 16 32 128 256 512; do | ||
| 38 | SIZE2=`expr "${SIZE}" '*' 2` | ||
| 39 | resize "${SIZE}" "${SIZE}" "${SRC}" "${OUT_ICONSET}/icon_${SIZE}x${SIZE}.png" -density 72 -units PixelsPerInch | ||
| 40 | resize "${SIZE2}" "${SIZE2}" "${SRC}" "${OUT_ICONSET}/icon_${SIZE}x${SIZE}@2x.png" -density 144 -units PixelsPerInch | ||
| 41 | done | ||
| 42 | iconutil -c icns --output "${OUT_ICNS}" "${OUT_ICONSET}" | ||
| 43 | else | ||
| 44 | echo "iconutil is not available - skipping ICNS and ICONSET" | ||
| 45 | fi | ||
| 46 | |||
| 47 | # Windows ICO | ||
| 48 | mkdir -p "${TMP_ICO}" | ||
| 49 | for SIZE in ${ICO_BASIC_SIZES} ${ICO_EXTRA_SIZES}; do | ||
| 50 | resize "${SIZE}" "${SIZE}" "${SRC}" "${TMP_ICO}/icon_${SIZE}x${SIZE}.png" | ||
| 51 | done | ||
| 52 | # Asking for "Zip" compression actually results in PNG compression | ||
| 53 | convert "${TMP_ICO}"/icon_*.png -colorspace sRGB -compress Zip "${OUT_ICO}" | ||
| 54 | |||
| 55 | # Windows AppX | ||
| 56 | mkdir -p "appx" | ||
| 57 | resize 44 44 "${SRC}" 'appx/Square44x44Logo.png' | ||
| 58 | resize 50 50 "${SRC}" 'appx/StoreLogo.png' | ||
| 59 | resize 150 150 "${SRC}" 'appx/Square150x150Logo.png' | ||
| 60 | resize 310 150 "${SRC}" 'appx/Wide310x150Logo.png' | ||
| 61 | else | ||
| 62 | echo "ImageMagick is not available - cannot convert icons" | ||
| 63 | fi |
buildResources/screenshot.png
0 → 100644
75.7 KB
doc/alipayQRCode.png
0 → 100644
47.3 KB
doc/screenshot.png
0 → 100644
231 KB
doc/screenshot2.png
0 → 100644
427 KB
electron-builder.yaml
0 → 100644
| 1 | directories: | ||
| 2 | buildResources: buildResources | ||
| 3 | output: dist | ||
| 4 | extraFiles: ['LICENSE', 'LICENSE.ScratchFoundation', 'TRADEMARK', "tools", "external-resources", 'firmwares', "drivers"] | ||
| 5 | |||
| 6 | appId: openblock.cc.openblock-desktop | ||
| 7 | productName: "OpenBlockDesktop" | ||
| 8 | publish: | ||
| 9 | - provider: github | ||
| 10 | artifactName: "OpenBlock-Desktop_v${version}_${os}_${arch}.${ext}" | ||
| 11 | |||
| 12 | fileAssociations: | ||
| 13 | ext: ob | ||
| 14 | name: OpenBlock project file | ||
| 15 | role: Editor | ||
| 16 | icon: buildResources/OpenBlockFile.ico | ||
| 17 | |||
| 18 | mac: | ||
| 19 | category: public.app-category.education | ||
| 20 | entitlements: buildResources/entitlements.mac.plist | ||
| 21 | extendInfo: | ||
| 22 | NSCameraUsageDescription: >- | ||
| 23 | This app requires camera access when using the video sensing blocks. | ||
| 24 | NSMicrophoneUsageDescription: >- | ||
| 25 | This app requires microphone access when recording sounds or detecting loudness. | ||
| 26 | gatekeeperAssess: true | ||
| 27 | hardenedRuntime: true | ||
| 28 | icon: buildResources/OpenBlockDesktop.icns | ||
| 29 | provisioningProfile: embedded.provisionprofile | ||
| 30 | target: | ||
| 31 | - dmg | ||
| 32 | # - mas | ||
| 33 | dmg: | ||
| 34 | title: "OpenBlock-Desktop_${version}" | ||
| 35 | # mas: | ||
| 36 | # category: public.app-category.education | ||
| 37 | # entitlements: buildResources/entitlements.mas.plist | ||
| 38 | # entitlementsInherit: buildResources/entitlements.mas.inherit.plist | ||
| 39 | # hardenedRuntime: false | ||
| 40 | # icon: buildResources/OpenBlockDesktop.icns | ||
| 41 | # masDev: | ||
| 42 | # type: development | ||
| 43 | # provisioningProfile: mas-dev.provisionprofile | ||
| 44 | |||
| 45 | win: | ||
| 46 | icon: buildResources/OpenBlockDesktop.ico | ||
| 47 | target: | ||
| 48 | # - appx | ||
| 49 | - nsis | ||
| 50 | # appx: | ||
| 51 | # identityName: "OpenblockTeam.OpenblockDesktop | ||
| 52 | # publisherDisplayName: "OpenBlock Team" | ||
| 53 | # publisher: "CN=2EC43DF1-469A-4119-9AB9-568A0A1FF65F" | ||
| 54 | nsis: | ||
| 55 | oneClick: false # allow user to choose per-user or per-machine | ||
| 56 | allowToChangeInstallationDirectory: true | ||
| 57 | include: buildResources/installer.nsh | ||
| 58 | # license: LICENSE | ||
| 59 | |||
| 60 | linux: | ||
| 61 | category: Education | ||
| 62 | icon: buildResources/linux | ||
| 63 | desktop: | ||
| 64 | - Encoding: UTF-8 | ||
| 65 | - Name: OpenBlockDesktop | ||
| 66 | - Icon: openblock-desktop | ||
| 67 | - Type: Application | ||
| 68 | - Terminal: false | ||
| 69 | target: | ||
| 70 | - deb | ||
| 71 | |||
| 72 | deb: | ||
| 73 | depends: ["libnotify4", "libxtst6", "libnss3"] | ||
| 74 |
electron-webpack.json5
0 → 100644
package-lock.json
0 → 100644
This diff could not be displayed because it is too large.
package.json
0 → 100644
| 1 | { | ||
| 2 | "name": "openblock-desktop", | ||
| 3 | "productName": "OpenBlockDesktop", | ||
| 4 | "description": "OpenBlock as a self-contained desktop application", | ||
| 5 | "author": "Openblock.cc Team <contact@openblock.cc> (http://www.openblock.cc/)", | ||
| 6 | "version": "2.5.2", | ||
| 7 | "license": "MIT", | ||
| 8 | "scripts": { | ||
| 9 | "postinstall": "npx electron-builder install-app-deps", | ||
| 10 | "clean": "rimraf ./dist ./static ./external-resources ./tools ./translations ./firmwares ./drivers", | ||
| 11 | "i18n:src": "mkdirp translations/core && format-message extract --out-file translations/core/en.json src/main/**.js", | ||
| 12 | "i18n:push": "tx-push-src openblock-editor desktop translations/core/en.json", | ||
| 13 | "start": "mkdirp ./dist && electron-webpack dev --bail --display-error-details --env.minify=false --no-progress", | ||
| 14 | "compile": "mkdirp ./dist && electron-webpack --bail --display-error-details --env.minify=false --no-progress", | ||
| 15 | "fetch:drivers": "rimraf ./drivers && node scripts/download-driver.js", | ||
| 16 | "fetch:exts": "rimraf ./external-resources && node ./node_modules/openblock-resource/script/download.js --repo=openblockcc/external-resources-v3", | ||
| 17 | "fetch:firmwares": "rimraf ./firmwares && node ./node_modules/openblock-link/script/download-firmwares.js", | ||
| 18 | "fetch:tools": "rimraf ./tools && node ./node_modules/openblock-link/script/download-tools.js", | ||
| 19 | "fetch:static": "rimraf ./static && mkdirp ./static && git clone https://github.com/openblockcc/openblock-assets.git static && rimraf ./static/.git", | ||
| 20 | "fetch:all": "npm run fetch:drivers && npm run fetch:exts && npm run fetch:firmwares && npm run fetch:tools && npm run fetch:static", | ||
| 21 | "build": "npm run build:dev", | ||
| 22 | "build:dev": "npm run compile && npm run doBuild -- --mode=dev", | ||
| 23 | "build:dir": "npm run compile && npm run doBuild -- --mode=dir", | ||
| 24 | "build:dist": "npm run compile && npm run doBuild -- --mode=dist", | ||
| 25 | "build:publish": "npm run compile && npm run doBuild -- --mode=publish", | ||
| 26 | "doBuild": "node ./scripts/electron-builder-wrapper.js", | ||
| 27 | "dist": "npm run clean && npm run compile && npm run fetch:all && npm run doBuild -- --mode=dist", | ||
| 28 | "publish": "npm run clean && npm run compile && npm run fetch:all && npm run doBuild -- --mode=publish", | ||
| 29 | "test": "npm run test:lint", | ||
| 30 | "test:lint": "eslint --cache --color --ext .jsx,.js src scripts" | ||
| 31 | }, | ||
| 32 | "repository": { | ||
| 33 | "type": "git", | ||
| 34 | "url": "git+ssh://git@github.com/openblockcc/openblock-desktop.git" | ||
| 35 | }, | ||
| 36 | "dependencies": { | ||
| 37 | "bytes": "^3.1.2", | ||
| 38 | "openblock-link": "^0.2.0-prerelease.20240710015813", | ||
| 39 | "openblock-resource": "^0.2.0-prerelease.20240708150142", | ||
| 40 | "postinstall": "^0.7.4", | ||
| 41 | "source-map-support": "^0.5.19" | ||
| 42 | }, | ||
| 43 | "devDependencies": { | ||
| 44 | "@aws-sdk/client-s3": "^3.53.1", | ||
| 45 | "@babel/core": "^7.9.6", | ||
| 46 | "@babel/plugin-proposal-object-rest-spread": "^7.9.6", | ||
| 47 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", | ||
| 48 | "@babel/plugin-transform-async-to-generator": "^7.8.3", | ||
| 49 | "@babel/preset-env": "^7.9.6", | ||
| 50 | "@babel/preset-react": "^7.9.4", | ||
| 51 | "@electron/remote": "^2.0.5", | ||
| 52 | "async": "^3.2.0", | ||
| 53 | "babel-eslint": "^10.1.0", | ||
| 54 | "babel-loader": "^8.1.0", | ||
| 55 | "babel-plugin-react-intl": "^7.5.7", | ||
| 56 | "classnames": "^2.3.1", | ||
| 57 | "copy-webpack-plugin": "^5.1.1", | ||
| 58 | "download-github-release": "^0.3.2", | ||
| 59 | "electron": "^15.3.1", | ||
| 60 | "electron-builder": "^22.14.13", | ||
| 61 | "electron-devtools-installer": "^3.2.0", | ||
| 62 | "electron-fetch": "^1.7.4", | ||
| 63 | "electron-log": "^4.4.6", | ||
| 64 | "electron-notarize": "^1.1.1", | ||
| 65 | "electron-store": "^8.0.1", | ||
| 66 | "electron-updater": "^4.6.5", | ||
| 67 | "electron-webpack": "^2.8.2", | ||
| 68 | "eslint": "^7.0.0", | ||
| 69 | "eslint-config-scratch": "^6.0.0", | ||
| 70 | "eslint-plugin-import": "^2.29.1", | ||
| 71 | "eslint-plugin-react": "^7.20.0", | ||
| 72 | "format-message": "^6.2.3", | ||
| 73 | "format-message-cli": "^6.2.3", | ||
| 74 | "fs-extra": "^11.1.0", | ||
| 75 | "intl": "1.2.5", | ||
| 76 | "lodash.bindall": "^4.4.0", | ||
| 77 | "lodash.defaultsdeep": "^4.6.1", | ||
| 78 | "minilog": "^3.1.0", | ||
| 79 | "minimist": "^1.2.5", | ||
| 80 | "mkdirp": "^1.0.4", | ||
| 81 | "monaco-editor-webpack-plugin": "^1.7.0", | ||
| 82 | "nets": "^3.2.0", | ||
| 83 | "node-abort-controller": "^3.0.1", | ||
| 84 | "openblock-gui": "github:openblockcc/openblock-gui#openblock-desktop-v2.5.2", | ||
| 85 | "openblock-l10n": "^3.15.20240704111820", | ||
| 86 | "openblock-parse-release-message": "0.0.4", | ||
| 87 | "os-locale": "^5.0.0", | ||
| 88 | "react": "^16.14.0", | ||
| 89 | "react-dom": "^16.14.0", | ||
| 90 | "react-intl": "2.9.0", | ||
| 91 | "react-redux": "5.0.7", | ||
| 92 | "redux": "3.7.2", | ||
| 93 | "rimraf": "^3.0.2", | ||
| 94 | "sudo-prompt": "^9.2.1", | ||
| 95 | "uuid": "^8.3.2", | ||
| 96 | "webpack": "^4.43.0" | ||
| 97 | } | ||
| 98 | } |
scripts/afterSign.js
0 → 100644
| 1 | const {notarize} = require('electron-notarize'); | ||
| 2 | |||
| 3 | const notarizeMacBuild = async function (context) { | ||
| 4 | // keep this in sync with appId in the electron-builder config | ||
| 5 | const appId = 'edu.mit.scratch.scratch-desktop'; | ||
| 6 | |||
| 7 | if (!process.env.AC_USERNAME) { | ||
| 8 | console.error([ | ||
| 9 | 'This build is not notarized and will not run on newer versions of macOS!', | ||
| 10 | 'Notarizing the macOS build requires an Apple ID. To notarize future builds:', | ||
| 11 | '* Set the environment variable AC_USERNAME to your@apple.id and', | ||
| 12 | '* Either set AC_PASSWORD or ensure your keychain has an item for "Application Loader: your@apple.id"' | ||
| 13 | ].join('\n')); | ||
| 14 | return; | ||
| 15 | } | ||
| 16 | |||
| 17 | const appleId = process.env.AC_USERNAME; | ||
| 18 | const appleIdKeychainItem = `Application Loader: ${appleId}`; | ||
| 19 | |||
| 20 | if (process.env.AC_PASSWORD) { | ||
| 21 | console.log(`Notarizing with Apple ID "${appleId}" and a password`); | ||
| 22 | } else { | ||
| 23 | console.log(`Notarizing with Apple ID "${appleId}" and keychain item "${appleIdKeychainItem}"`); | ||
| 24 | } | ||
| 25 | |||
| 26 | const {appOutDir} = context; | ||
| 27 | const productFilename = context.packager.appInfo.productFilename; | ||
| 28 | await notarize({ | ||
| 29 | appBundleId: appId, | ||
| 30 | appPath: `${appOutDir}/${productFilename}.app`, | ||
| 31 | appleId, | ||
| 32 | appleIdPassword: process.env.AC_PASSWORD || `@keychain:${appleIdKeychainItem}` | ||
| 33 | }); | ||
| 34 | }; | ||
| 35 | |||
| 36 | const afterSign = async function (context) { | ||
| 37 | const {electronPlatformName} = context; | ||
| 38 | |||
| 39 | switch (electronPlatformName) { | ||
| 40 | case 'mas': // macOS build for Mac App Store | ||
| 41 | break; | ||
| 42 | case 'darwin': // macOS build NOT for Mac App Store | ||
| 43 | await notarizeMacBuild(context); | ||
| 44 | break; | ||
| 45 | } | ||
| 46 | }; | ||
| 47 | |||
| 48 | module.exports = afterSign; |
scripts/download-driver.js
0 → 100644
| 1 | /* eslint-disable */ | ||
| 2 | const downloadRelease = require('download-github-release'); | ||
| 3 | const path = require('path'); | ||
| 4 | const os = require('os'); | ||
| 5 | const fs = require('fs'); | ||
| 6 | |||
| 7 | const user = 'openblockcc'; | ||
| 8 | const repo = 'openblock-driver'; | ||
| 9 | const outputdir = path.join(__dirname, '../drivers'); | ||
| 10 | const leaveZipped = false; | ||
| 11 | |||
| 12 | function filterRelease (release) { | ||
| 13 | return release.prerelease === false; | ||
| 14 | } | ||
| 15 | |||
| 16 | function filterAsset(asset) { | ||
| 17 | return (asset.name.indexOf(os.platform()) >= 0); | ||
| 18 | } | ||
| 19 | |||
| 20 | if (!fs.existsSync(outputdir)) { | ||
| 21 | fs.mkdirSync(outputdir, {recursive: true}); | ||
| 22 | } | ||
| 23 | |||
| 24 | downloadRelease(user, repo, outputdir, filterRelease, filterAsset, leaveZipped) | ||
| 25 | .then(() => { | ||
| 26 | console.log('Tools download complete'); | ||
| 27 | }) | ||
| 28 | .catch(err => { | ||
| 29 | console.error(err.message); | ||
| 30 | }); |
scripts/electron-builder-wrapper.js
0 → 100644
| 1 | /** | ||
| 2 | * @overview This script runs `electron-builder` with special management of code signing configuration on Windows. | ||
| 3 | * Running this script with no command line parameters should build all targets for the current platform. | ||
| 4 | * On Windows, make sure to set CSC_* or WIN_CSC_* environment variables or the NSIS build will fail. | ||
| 5 | * On Mac, the CSC_* variables are optional but will be respected if present. | ||
| 6 | * See also: https://www.electron.build/code-signing | ||
| 7 | */ | ||
| 8 | |||
| 9 | const {spawnSync} = require('child_process'); | ||
| 10 | const fs = require('fs'); | ||
| 11 | |||
| 12 | /** | ||
| 13 | * Strip any code signing configuration (CSC) from a set of environment variables. | ||
| 14 | * @param {object} environment - a collection of environment variables which might include code signing configuration. | ||
| 15 | * @returns {object} - a collection of environment variables which does not include code signing configuration. | ||
| 16 | */ | ||
| 17 | const stripCSC = function (environment) { | ||
| 18 | const { | ||
| 19 | CSC_LINK: _CSC_LINK, | ||
| 20 | CSC_KEY_PASSWORD: _CSC_KEY_PASSWORD, | ||
| 21 | WIN_CSC_LINK: _WIN_CSC_LINK, | ||
| 22 | WIN_CSC_KEY_PASSWORD: _WIN_CSC_KEY_PASSWORD, | ||
| 23 | ...strippedEnvironment | ||
| 24 | } = environment; | ||
| 25 | return strippedEnvironment; | ||
| 26 | }; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * @returns {string} - an `electron-builder` flag to build for the current platform, based on `process.platform`. | ||
| 30 | */ | ||
| 31 | const getPlatformFlag = function () { | ||
| 32 | switch (process.platform) { | ||
| 33 | case 'win32': return '--windows'; | ||
| 34 | case 'darwin': return '--macos'; | ||
| 35 | case 'linux': return '--linux'; | ||
| 36 | } | ||
| 37 | throw new Error(`Could not determine platform flag for platform: ${process.platform}`); | ||
| 38 | }; | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Run `electron-builder` once to build one or more target(s). | ||
| 42 | * @param {object} wrapperConfig - overall configuration object for the wrapper script. | ||
| 43 | * @param {object} target - the target to build in this call. | ||
| 44 | * If the `target.name` is `'nsis'` then the environment must contain code-signing config (CSC_* or WIN_CSC_*). | ||
| 45 | * If the `target.name` is `'appx'` then code-signing config will be stripped from the environment if present. | ||
| 46 | */ | ||
| 47 | const runBuilder = function (wrapperConfig, target) { | ||
| 48 | // the AppX build fails if CSC_* or WIN_CSC_* variables are set | ||
| 49 | const shouldStripCSC = (target.name.indexOf('appx') === 0) || (!wrapperConfig.doSign); | ||
| 50 | const childEnvironment = shouldStripCSC ? stripCSC(process.env) : process.env; | ||
| 51 | if (wrapperConfig.doSign && | ||
| 52 | (target.name.indexOf('nsis') === 0) && | ||
| 53 | !(childEnvironment.CSC_LINK || childEnvironment.WIN_CSC_LINK)) { | ||
| 54 | // throw new Error(`Signing NSIS build requires CSC_LINK or WIN_CSC_LINK`); | ||
| 55 | } | ||
| 56 | const platformFlag = getPlatformFlag(); | ||
| 57 | let allArgs = [platformFlag, target.name]; | ||
| 58 | if (target.platform === 'darwin') { | ||
| 59 | allArgs.push(`--c.mac.type=${wrapperConfig.mode === 'dist' ? 'distribution' : 'development'}`); | ||
| 60 | if (target.name === 'mas-dev') { | ||
| 61 | allArgs.push('--c.mac.provisioningProfile=mas-dev.provisionprofile'); | ||
| 62 | } | ||
| 63 | if (wrapperConfig.doSign) { | ||
| 64 | // really this is "notarize only if we also sign" | ||
| 65 | allArgs.push('--c.afterSign=scripts/afterSign.js'); | ||
| 66 | } else { | ||
| 67 | allArgs.push('--c.mac.identity=null'); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | if (target.platform === 'win32' && wrapperConfig.mode !== 'dev') { | ||
| 71 | allArgs.push('--ia32', '--x64'); | ||
| 72 | } | ||
| 73 | if (!wrapperConfig.doPackage) { | ||
| 74 | allArgs.push('--dir', '--c.compression=store'); | ||
| 75 | } | ||
| 76 | if (wrapperConfig.doPublish) { | ||
| 77 | allArgs.push('--publish', 'always'); | ||
| 78 | } else { | ||
| 79 | // Prevent electron build from automatically publishing in github action | ||
| 80 | allArgs.push('--publish', 'never'); | ||
| 81 | } | ||
| 82 | allArgs = allArgs.concat(wrapperConfig.builderArgs); | ||
| 83 | console.log(`running electron-builder with arguments: ${allArgs}`); | ||
| 84 | const result = spawnSync('electron-builder', allArgs, { | ||
| 85 | env: childEnvironment, | ||
| 86 | shell: true, | ||
| 87 | stdio: 'inherit' | ||
| 88 | }); | ||
| 89 | if (result.error) { | ||
| 90 | throw result.error; | ||
| 91 | } | ||
| 92 | if (result.signal) { | ||
| 93 | throw new Error(`Child process terminated due to signal ${result.signal}`); | ||
| 94 | } | ||
| 95 | if (result.status) { | ||
| 96 | throw new Error(`Child process returned status code ${result.status}`); | ||
| 97 | } | ||
| 98 | }; | ||
| 99 | |||
| 100 | /** | ||
| 101 | * @param {object} wrapperConfig - overall configuration object for the wrapper script. | ||
| 102 | * @returns {Array.<object>} - the default list of targets on this platform. Each item in the array represents one | ||
| 103 | * call to `runBuilder` for exactly one build target. In theory electron-builder can build two or more targets at the | ||
| 104 | * same time but doing so limits has unwanted side effects on both macOS and Windows (see function body). | ||
| 105 | */ | ||
| 106 | const calculateTargets = function (wrapperConfig) { | ||
| 107 | const masDevProfile = 'mas-dev.provisionprofile'; | ||
| 108 | const availableTargets = { | ||
| 109 | macAppStore: { | ||
| 110 | name: 'mas', | ||
| 111 | platform: 'darwin' | ||
| 112 | }, | ||
| 113 | macAppStoreDev: { | ||
| 114 | name: 'mas-dev', | ||
| 115 | platform: 'darwin' | ||
| 116 | }, | ||
| 117 | macDirectDownload: { | ||
| 118 | name: 'dmg', | ||
| 119 | platform: 'darwin' | ||
| 120 | }, | ||
| 121 | // microsoftStore: { | ||
| 122 | // name: 'appx:ia32 appx:x64', | ||
| 123 | // platform: 'win32' | ||
| 124 | // }, | ||
| 125 | windowsDirectDownload: { | ||
| 126 | name: 'nsis', | ||
| 127 | platform: 'win32' | ||
| 128 | }, | ||
| 129 | linuxDirectDownload: { | ||
| 130 | name: 'deb', | ||
| 131 | platform: 'linux' | ||
| 132 | } | ||
| 133 | }; | ||
| 134 | const targets = []; | ||
| 135 | console.log(process.platform); | ||
| 136 | switch (process.platform) { | ||
| 137 | case 'win32': | ||
| 138 | // Run in two passes so we can skip signing the AppX for distribution through the MS Store. | ||
| 139 | // targets.push(availableTargets.microsoftStore); | ||
| 140 | targets.push(availableTargets.windowsDirectDownload); | ||
| 141 | break; | ||
| 142 | case 'darwin': | ||
| 143 | // Running 'dmg' and 'mas' in the same pass causes electron-builder to skip signing the non-MAS app copy. | ||
| 144 | // Running them as separate passes means they can both get signed. | ||
| 145 | // Seems like a bug in electron-builder... | ||
| 146 | // Running the 'mas' build first means that its output is available while we wait for 'dmg' notarization. | ||
| 147 | // Add macAppStoreDev here to test a MAS-like build locally. You'll need a Mac Developer provisioning profile. | ||
| 148 | if (fs.existsSync(masDevProfile)) { | ||
| 149 | targets.push(availableTargets.macAppStoreDev); | ||
| 150 | } else { | ||
| 151 | console.log(`skipping target "${availableTargets.macAppStoreDev.name}": ${masDevProfile} missing`); | ||
| 152 | } | ||
| 153 | if (wrapperConfig.doSign) { | ||
| 154 | targets.push(availableTargets.macAppStore); | ||
| 155 | } else { | ||
| 156 | // electron-builder doesn't seem to support this configuration even if mac.type is "development" | ||
| 157 | console.log(`skipping target "${availableTargets.macAppStore.name}" because code-signing is disabled`); | ||
| 158 | } | ||
| 159 | targets.push(availableTargets.macDirectDownload); | ||
| 160 | break; | ||
| 161 | case 'linux': | ||
| 162 | targets.push(availableTargets.linuxDirectDownload); | ||
| 163 | break; | ||
| 164 | default: | ||
| 165 | throw new Error(`Could not determine targets for platform: ${process.platform}`); | ||
| 166 | } | ||
| 167 | return targets; | ||
| 168 | }; | ||
| 169 | |||
| 170 | const parseArgs = function () { | ||
| 171 | const scriptArgs = process.argv.slice(2); // remove `node` and `this-script.js` | ||
| 172 | console.log(scriptArgs); | ||
| 173 | const builderArgs = []; | ||
| 174 | let mode = 'dev'; // default | ||
| 175 | let arch = null; | ||
| 176 | |||
| 177 | for (const arg of scriptArgs) { | ||
| 178 | const modeSplit = arg.split(/--mode(\s+|=)/); | ||
| 179 | const archSplit = arg.split(/--arch(\s+|=)/); | ||
| 180 | if (modeSplit.length === 3) { | ||
| 181 | mode = modeSplit[2]; | ||
| 182 | } else if (archSplit.length === 3) { | ||
| 183 | arch = archSplit[2]; | ||
| 184 | } else { | ||
| 185 | builderArgs.push(arg); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | let doPackage; | ||
| 190 | let doSign; | ||
| 191 | let doPublish; | ||
| 192 | |||
| 193 | switch (mode) { | ||
| 194 | case 'dev': | ||
| 195 | doPackage = true; | ||
| 196 | doSign = false; | ||
| 197 | doPublish = false; | ||
| 198 | break; | ||
| 199 | case 'dir': | ||
| 200 | doPackage = false; | ||
| 201 | doSign = false; | ||
| 202 | doPublish = false; | ||
| 203 | break; | ||
| 204 | case 'dist': | ||
| 205 | doPackage = true; | ||
| 206 | // doSign = true; // skip code signing before getting a certificate | ||
| 207 | doSign = false; | ||
| 208 | doPublish = false; | ||
| 209 | break; | ||
| 210 | case 'publish': | ||
| 211 | doPackage = true; | ||
| 212 | // doSign = true; // skip code signing before getting a certificate | ||
| 213 | doSign = false; | ||
| 214 | doPublish = true; | ||
| 215 | break; | ||
| 216 | } | ||
| 217 | |||
| 218 | return { | ||
| 219 | builderArgs, | ||
| 220 | doPackage, // false = build to directory | ||
| 221 | doSign, | ||
| 222 | doPublish, | ||
| 223 | mode, | ||
| 224 | arch | ||
| 225 | }; | ||
| 226 | }; | ||
| 227 | |||
| 228 | const main = function () { | ||
| 229 | const wrapperConfig = parseArgs(); | ||
| 230 | console.log(wrapperConfig); | ||
| 231 | // TODO: allow user to specify targets? We could theoretically build NSIS on Mac, for example. | ||
| 232 | wrapperConfig.targets = calculateTargets(wrapperConfig); | ||
| 233 | |||
| 234 | for (const target of wrapperConfig.targets) { | ||
| 235 | runBuilder(wrapperConfig, target); | ||
| 236 | } | ||
| 237 | }; | ||
| 238 | |||
| 239 | main(); |
scripts/fetchMediaLibraryAssets.js
0 → 100644
| 1 | const fs = require('fs'); | ||
| 2 | const https = require('https'); | ||
| 3 | const path = require('path'); | ||
| 4 | const util = require('util'); | ||
| 5 | |||
| 6 | const async = require('async'); | ||
| 7 | |||
| 8 | const libraries = require('./lib/libraries'); | ||
| 9 | |||
| 10 | const ASSET_HOST = 'cdn.assets.scratch.mit.edu'; | ||
| 11 | const NUM_SIMULTANEOUS_DOWNLOADS = 5; | ||
| 12 | const OUT_PATH = path.resolve('static', 'assets'); | ||
| 13 | |||
| 14 | |||
| 15 | const describe = function (object) { | ||
| 16 | return util.inspect(object, false, Infinity, true); | ||
| 17 | }; | ||
| 18 | |||
| 19 | const collectSimple = function (library, dest, debugLabel = 'Item') { | ||
| 20 | library.forEach(item => { | ||
| 21 | let md5Count = 0; | ||
| 22 | if (item.md5) { | ||
| 23 | ++md5Count; | ||
| 24 | dest.add(item.md5); | ||
| 25 | } | ||
| 26 | if (item.baseLayerMD5) { // 2.0 library syntax for costumes | ||
| 27 | ++md5Count; | ||
| 28 | dest.add(item.baseLayerMD5); | ||
| 29 | } | ||
| 30 | if (item.md5ext) { // 3.0 library syntax for costumes | ||
| 31 | ++md5Count; | ||
| 32 | dest.add(item.md5ext); | ||
| 33 | } | ||
| 34 | if (md5Count < 1) { | ||
| 35 | console.warn(`${debugLabel} has no MD5 property:\n${describe(item)}`); | ||
| 36 | } else if (md5Count > 1) { | ||
| 37 | // is this actually bad? | ||
| 38 | console.warn(`${debugLabel} has multiple MD5 properties:\n${describe(item)}`); | ||
| 39 | } | ||
| 40 | }); | ||
| 41 | return dest; | ||
| 42 | }; | ||
| 43 | |||
| 44 | const collectAssets = function (dest) { | ||
| 45 | collectSimple(libraries.backdrops, dest, 'Backdrop'); | ||
| 46 | collectSimple(libraries.costumes, dest, 'Costume'); | ||
| 47 | collectSimple(libraries.sounds, dest, 'Sound'); | ||
| 48 | libraries.sprites.forEach(sprite => { | ||
| 49 | if (sprite.costumes) { | ||
| 50 | collectSimple(sprite.costumes, dest, `Costume for sprite ${sprite.name}`); | ||
| 51 | } | ||
| 52 | if (sprite.sounds) { | ||
| 53 | collectSimple(sprite.sounds, dest, `Sound for sprite ${sprite.name}`); | ||
| 54 | } | ||
| 55 | }); | ||
| 56 | return dest; | ||
| 57 | }; | ||
| 58 | |||
| 59 | const connectionPool = []; | ||
| 60 | |||
| 61 | const fetchAsset = function (md5, callback) { | ||
| 62 | const myAgent = connectionPool.pop() || new https.Agent({keepAlive: true}); | ||
| 63 | const getOptions = { | ||
| 64 | host: ASSET_HOST, | ||
| 65 | path: `/internalapi/asset/${md5}/get/`, | ||
| 66 | agent: myAgent | ||
| 67 | }; | ||
| 68 | const urlHuman = `//${getOptions.host}${getOptions.path}`; | ||
| 69 | https.get(getOptions, response => { | ||
| 70 | if (response.statusCode !== 200) { | ||
| 71 | callback(new Error(`Request failed: status code ${response.statusCode} for ${urlHuman}`)); | ||
| 72 | return; | ||
| 73 | } | ||
| 74 | |||
| 75 | const stream = fs.createWriteStream(path.resolve(OUT_PATH, md5), {encoding: 'binary'}); | ||
| 76 | stream.on('error', callback); | ||
| 77 | response.on('data', chunk => { | ||
| 78 | stream.write(chunk); | ||
| 79 | }); | ||
| 80 | response.on('end', () => { | ||
| 81 | connectionPool.push(myAgent); | ||
| 82 | stream.end(); | ||
| 83 | console.log(`Fetched ${urlHuman}`); | ||
| 84 | callback(); | ||
| 85 | }); | ||
| 86 | }); | ||
| 87 | }; | ||
| 88 | |||
| 89 | const fetchAllAssets = function () { | ||
| 90 | const allAssets = collectAssets(new Set()); | ||
| 91 | console.log(`Total library assets: ${allAssets.size}`); | ||
| 92 | |||
| 93 | async.forEachLimit(allAssets, NUM_SIMULTANEOUS_DOWNLOADS, fetchAsset, err => { | ||
| 94 | if (err) { | ||
| 95 | console.error(`Fetch failed:\n${describe(err)}`); | ||
| 96 | } else { | ||
| 97 | console.log('Fetch succeeded.'); | ||
| 98 | } | ||
| 99 | |||
| 100 | console.log(`Shutting down ${connectionPool.length} agents.`); | ||
| 101 | while (connectionPool.length > 0) { | ||
| 102 | connectionPool.pop().destroy(); | ||
| 103 | } | ||
| 104 | }); | ||
| 105 | }; | ||
| 106 | |||
| 107 | fetchAllAssets(); |
scripts/lib/libraries.js
0 → 100644
| 1 | const backdrops = require('openblock-gui/src/lib/libraries/backdrops.json'); | ||
| 2 | const costumes = require('openblock-gui/src/lib/libraries/costumes.json'); | ||
| 3 | const sounds = require('openblock-gui/src/lib/libraries/sounds.json'); | ||
| 4 | const sprites = require('openblock-gui/src/lib/libraries/sprites.json'); | ||
| 5 | |||
| 6 | const libraries = { | ||
| 7 | backdrops, | ||
| 8 | costumes, | ||
| 9 | sounds, | ||
| 10 | sprites | ||
| 11 | }; | ||
| 12 | |||
| 13 | module.exports = libraries; |
src/.eslintrc.js
0 → 100644
src/common/ElectronStorageHelper.js
0 → 100644
| 1 | const fs = require('fs'); | ||
| 2 | const path = require('path'); | ||
| 3 | |||
| 4 | const staticAssets = path.resolve(__static, 'assets'); | ||
| 5 | |||
| 6 | /** | ||
| 7 | * Allow the storage module to load files bundled in the Electron application. | ||
| 8 | */ | ||
| 9 | class ElectronStorageHelper { | ||
| 10 | constructor (storageInstance) { | ||
| 11 | this.parent = storageInstance; | ||
| 12 | } | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Fetch an asset but don't process dependencies. | ||
| 16 | * @param {AssetType} assetType - The type of asset to fetch. | ||
| 17 | * @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc. | ||
| 18 | * @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc. | ||
| 19 | * @return {Promise.<Asset>} A promise for the contents of the asset. | ||
| 20 | */ | ||
| 21 | load (assetType, assetId, dataFormat) { | ||
| 22 | assetId = path.basename(assetId); | ||
| 23 | dataFormat = path.basename(dataFormat); | ||
| 24 | |||
| 25 | return new Promise((resolve, reject) => { | ||
| 26 | fs.readFile( | ||
| 27 | path.resolve(staticAssets, `${assetId}.${dataFormat}`), | ||
| 28 | (err, data) => { | ||
| 29 | if (err) { | ||
| 30 | reject(err); | ||
| 31 | } else { | ||
| 32 | resolve(new this.parent.Asset(assetType, assetId, dataFormat, data)); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | ); | ||
| 36 | }); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | module.exports = ElectronStorageHelper; |
src/common/log.js
0 → 100644
| 1 | import minilog from 'minilog'; | ||
| 2 | minilog.enable(); | ||
| 3 | |||
| 4 | const namespace = (() => { | ||
| 5 | switch (process.type) { | ||
| 6 | case 'browser': return 'main'; | ||
| 7 | case 'renderer': return 'web'; | ||
| 8 | default: return process.type; // probably 'worker' for a web worker | ||
| 9 | } | ||
| 10 | })(); | ||
| 11 | |||
| 12 | export default minilog(`app-${namespace}`); |
src/icon/OpenBlockDesktop.png
0 → 100644
51.7 KB
src/icon/OpenBlockDesktop.svg
0 → 100644
| 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.78 306.36"><defs><style>.a{fill:#ffbc47;}.b{fill:#cf8b17;}.c{fill:#ffab19;}.d{fill:#fff;}</style></defs><title>OpenBlock-Desktop</title><path class="a" d="M0,236.43c0-2,0-4,0-6Q0,128,0,25.51a37.76,37.76,0,0,1,.66-7.63A22,22,0,0,1,22.63.06H176.68c28.08,0,56.15.06,84.23-.06a22.33,22.33,0,0,1,22.83,22.42q-.08,98-.05,196.05c0,6.4-.06,12.79-.09,19.19-2.36,11-11,17.94-22.26,18-12.39,0-24.79.13-37.18-.06a11,11,0,0,0-9.75,4.9c-5.36,7.17-11,14.11-16.54,21.17a27.39,27.39,0,0,1-3.63,3.81,10.93,10.93,0,0,1-7.43,2.84q-44.73,0-89.46,0c-4.64,0-8-2.29-10.77-5.8-5.61-7.18-11.29-14.31-16.83-21.55a12.39,12.39,0,0,0-11-5.39c-12.15.28-24.3.22-36.45.05C13,255.46,6.19,251,1.75,242.93A24.38,24.38,0,0,1,0,237.38"/><path class="b" d="M0,249.14"/><path class="b" d="M0,237.92a17.51,17.51,0,0,0,3,6c4.41,7,10.68,10.89,19,11,12.63.13,25.27.21,37.9,0,4.49-.09,7.42,1.85,9.94,5.07,6,7.66,11.87,15.39,17.94,23a12.14,12.14,0,0,0,10,4.75c29.1-.08,58.21-.15,87.31.05,5.37,0,8.94-2.26,12-6.12,5.14-6.5,10.23-13,15.36-19.54a43.75,43.75,0,0,1,3.57-4.49,8.71,8.71,0,0,1,6.43-2.65c13,.06,25.91,0,38.86,0a22.25,22.25,0,0,0,21.38-15.1,5.42,5.42,0,0,1,.93-2.17c0,4.55.32,9.12.08,13.66a21.72,21.72,0,0,1-20.46,20.46c-11.26.56-22.54.27-33.81.14-4.13,0-7.4,1.07-10.12,4.3-7.42,8.8-15,17.47-22.5,26.2a10.49,10.49,0,0,1-8.55,3.92q-46.3-.13-92.6,0a10.73,10.73,0,0,1-8.74-4.06c-7.23-8.44-14.6-16.76-21.68-25.33-3.1-3.75-6.68-5.16-11.48-5-10.15.26-20.31.14-30.46.08-6.66,0-12.58-2.08-17.23-7.07-3.14-3.36-6-8.23-6-12.94"/><path class="c" d="M259.35,12.27a22.72,22.72,0,0,1,5.9.73c4.89,1.31,7.29,4.92,7.81,10a11.82,11.82,0,0,1,0,1.2q0,103.78,0,207.56a11.93,11.93,0,0,1-5.07,9.81,8.77,8.77,0,0,1-5.63,1.58c-14.32,0-28.63,0-43,0a12.51,12.51,0,0,0-10.79,5.44c-3.76,5-7.79,9.86-11.68,14.8-1.87,2.37-3.63,4.83-5.53,7.19-1.75,2.17-3.49,4.34-6.49,5a14.73,14.73,0,0,1-3.08.28H107c-1.43,0-2.86.19-4.27.27a11.15,11.15,0,0,1-6.57-1.72c-2.34-1.46-3.64-3.8-5.18-5.87-4.23-5.68-8.84-11.06-13.09-16.72a30.58,30.58,0,0,0-5.76-6.31,10.55,10.55,0,0,0-6.62-2.31c-14.32,0-28.64-.09-43,.06A11.47,11.47,0,0,1,11,231.65q.13-52.19,0-104.38h0c0-34.63.12-69.27-.11-103.9A11.29,11.29,0,0,1,21.66,12.31"/><path class="d" d="M71.1,101.89c-3,0-5.92,0-8.88,0-3.25,0-5.47-1.88-7.28-4.31a4.78,4.78,0,0,1-.7-3c0-5,0-10.08,0-15.12a5.94,5.94,0,0,1,3.12-5.24A14,14,0,0,1,64.8,72.2c5.28,0,10.56,0,15.84,0,1.16,0,1.72-.18,2.15-1.52A28,28,0,0,1,86,63.93a21,21,0,0,1,6.18-5.8,15.34,15.34,0,0,1,8.92-2.59q43.44.05,86.88,0a17.88,17.88,0,0,1,9.71,3.11c3.84,2.39,6,6,7.83,9.87a1.27,1.27,0,0,1,.19.43c0,1.12.07,2.39,1.06,3s2.19.22,3.29.23c3.52,0,7,.13,10.56,0a16.84,16.84,0,0,1,6.53.92c3.24,1.18,5.64,3.1,5.56,7.33-.08,4.64-.14,9.28,0,13.92.1,3-1.45,4.76-3.68,6.21a8.81,8.81,0,0,1-5.18,1.38c-5.2-.12-10.4-.05-15.6,0-1.84,0-1.87,0-1.88,1.87,0,3.6,0,7.2,0,10.8,0,2.14,0,2.16,2.31,2.16q7,0,13.92,0a13.53,13.53,0,0,1,7.39,2.17,6,6,0,0,1,2.66,5.09c.07,5.28,0,10.56,0,15.84a4.51,4.51,0,0,1-1.35,3.2,9.64,9.64,0,0,1-7.76,3.42c-5-.18-10.08-.06-15.12,0-2,0-2.06,0-2.06,1.91q0,5.88,0,11.76c0,1.7,0,1.75,1.75,1.75,5.44,0,10.88.06,16.32,0,3.39,0,5.66,1.81,7.59,4.23a4.64,4.64,0,0,1,.66,3c0,4.8-.09,9.6,0,14.4.09,4.15-2.5,6.09-5.65,7.29a16.35,16.35,0,0,1-6.31.83c-4.16-.11-8.32,0-12.48,0-1.67,0-2.11.35-2.46,1.9a19.63,19.63,0,0,1-18.76,15c-29-.1-57.92-.06-86.88,0A14.11,14.11,0,0,1,93,206a22.82,22.82,0,0,1-4.12-2.77A23.43,23.43,0,0,1,82.73,193c-.39-1.21-.85-1.45-1.92-1.45-5.52,0-11,0-16.56,0a12.12,12.12,0,0,1-8-3,5.62,5.62,0,0,1-2-4.19c0-5.28,0-10.56,0-15.84a4.37,4.37,0,0,1,1.23-3,9.67,9.67,0,0,1,7.89-3.67c5.75.19,11.52.06,17.28,0,1.87,0,1.9,0,1.91-1.82,0-4,0-8,0-12,0-1.27-.45-1.64-1.67-1.63-6.16,0-12.32,0-18.48,0-3.48,0-5.72-2-7.62-4.57a4.35,4.35,0,0,1-.58-2.75c0-4.8,0-9.6,0-14.4.06-4.42,2.82-6.32,6.2-7.35,2.72-.83,5.51-.59,8.28-.6,3.92,0,7.84,0,11.76,0,2,0,2,0,2.06-1.92,0-3.76,0-7.52,0-11.28,0-1.25-.42-1.68-1.66-1.65C77.66,101.93,74.38,101.89,71.1,101.89Z"/><path class="c" d="M109.23,93.44c-6.7.4-12.82-5.25-13.19-12.18a12.93,12.93,0,0,1,25.79-1.74A12.89,12.89,0,0,1,109.23,93.44Z"/></svg> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/icon/OpenBlockLoading.svg
0 → 100644
This diff could not be displayed because it is too large.
src/icon/logo-OpenBlockcc.svg
0 → 100644
This diff could not be displayed because it is too large.
src/icon/logo-ScratchFoundation.svg
0 → 100644
This diff is collapsed.
Click to expand it.
src/main/.eslintrc.js
0 → 100644
src/main/FileFilters.js
0 → 100644
| 1 | const saveFilters = { | ||
| 2 | JPEG: { | ||
| 3 | name: 'JPEG Image', | ||
| 4 | extensions: ['jpg', 'jpeg'] | ||
| 5 | }, | ||
| 6 | MP3: { | ||
| 7 | name: 'MP3 Sound', | ||
| 8 | extensions: ['mp3'] | ||
| 9 | }, | ||
| 10 | PNG: { | ||
| 11 | name: 'PNG Image', | ||
| 12 | extensions: ['png'] | ||
| 13 | }, | ||
| 14 | SB: { | ||
| 15 | name: 'Scratch 1 Project', | ||
| 16 | extensions: ['sb'] | ||
| 17 | }, | ||
| 18 | SB2: { | ||
| 19 | name: 'Scratch 2 Project', | ||
| 20 | extensions: ['sb2'] | ||
| 21 | }, | ||
| 22 | SB3: { | ||
| 23 | name: 'Scratch 3 Project', | ||
| 24 | extensions: ['sb3'] | ||
| 25 | }, | ||
| 26 | OB: { | ||
| 27 | name: 'OpenBlock Project', | ||
| 28 | extensions: ['ob'] | ||
| 29 | }, | ||
| 30 | Sprite2: { | ||
| 31 | name: 'Scratch 2 Sprite', | ||
| 32 | extensions: ['sprite2'] | ||
| 33 | }, | ||
| 34 | Sprite3: { | ||
| 35 | name: 'Scratch 3 Sprite', | ||
| 36 | extensions: ['sprite3'] | ||
| 37 | }, | ||
| 38 | SVG: { | ||
| 39 | name: 'SVG Image', | ||
| 40 | extensions: ['svg'] | ||
| 41 | }, | ||
| 42 | WAV: { | ||
| 43 | name: 'WAV Sound', | ||
| 44 | extensions: ['wav'] | ||
| 45 | } | ||
| 46 | }; | ||
| 47 | |||
| 48 | const loadFilters = { | ||
| 49 | ...saveFilters, | ||
| 50 | AllBitmaps: { | ||
| 51 | name: 'All Bitmaps', | ||
| 52 | extensions: [ | ||
| 53 | ...saveFilters.JPEG.extensions, | ||
| 54 | ...saveFilters.PNG.extensions | ||
| 55 | ] | ||
| 56 | }, | ||
| 57 | AllImages: { | ||
| 58 | name: 'All Images', | ||
| 59 | extensions: [ | ||
| 60 | ...saveFilters.JPEG.extensions, | ||
| 61 | ...saveFilters.PNG.extensions, | ||
| 62 | ...saveFilters.SVG.extensions | ||
| 63 | ] | ||
| 64 | }, | ||
| 65 | AllProjects: { | ||
| 66 | name: 'All OpenBlock Projects', | ||
| 67 | extensions: [ | ||
| 68 | ...saveFilters.SB3.extensions, | ||
| 69 | ...saveFilters.SB2.extensions, | ||
| 70 | ...saveFilters.SB.extensions, | ||
| 71 | ...saveFilters.OB.extensions | ||
| 72 | ] | ||
| 73 | }, | ||
| 74 | AllSounds: { | ||
| 75 | name: 'All Sounds', | ||
| 76 | extensions: [ | ||
| 77 | ...saveFilters.MP3.extensions, | ||
| 78 | ...saveFilters.WAV.extensions | ||
| 79 | ] | ||
| 80 | }, | ||
| 81 | AllSprites: { | ||
| 82 | name: 'All Sprites', | ||
| 83 | extensions: [ | ||
| 84 | ...saveFilters.Sprite3.extensions, | ||
| 85 | ...saveFilters.Sprite2.extensions | ||
| 86 | ] | ||
| 87 | } | ||
| 88 | }; | ||
| 89 | |||
| 90 | const filtersByExtension = Object.values(saveFilters).reduce((result, filter) => { | ||
| 91 | for (const extension of filter.extensions) { | ||
| 92 | result[extension] = filter; | ||
| 93 | } | ||
| 94 | return result; | ||
| 95 | }, {}); | ||
| 96 | |||
| 97 | const getFilterForExtension = extNameNoDot => | ||
| 98 | filtersByExtension[extNameNoDot] || { | ||
| 99 | name: `${extNameNoDot.toUpperCase()} Files`, | ||
| 100 | extensions: [extNameNoDot] | ||
| 101 | }; | ||
| 102 | |||
| 103 | export { | ||
| 104 | saveFilters, | ||
| 105 | loadFilters, | ||
| 106 | getFilterForExtension | ||
| 107 | }; |
src/main/MacOSMenu.js
0 → 100644
| 1 | // Include the standard keyboard shortcuts in the edit menu | ||
| 2 | // so they can be used within the app. Only needed on Mac. | ||
| 3 | export default app => ([ | ||
| 4 | { | ||
| 5 | label: 'App', // Always overridden by app name | ||
| 6 | submenu: [{ | ||
| 7 | label: 'Quit', | ||
| 8 | accelerator: 'CmdOrCtrl+Q', | ||
| 9 | click: () => app.quit() | ||
| 10 | }] | ||
| 11 | }, | ||
| 12 | { | ||
| 13 | label: 'Edit', | ||
| 14 | submenu: [ | ||
| 15 | { | ||
| 16 | label: 'Undo', | ||
| 17 | accelerator: 'CmdOrCtrl+Z', | ||
| 18 | role: 'undo' | ||
| 19 | }, | ||
| 20 | { | ||
| 21 | label: 'Redo', | ||
| 22 | accelerator: 'Shift+CmdOrCtrl+Z', | ||
| 23 | role: 'redo' | ||
| 24 | }, | ||
| 25 | { | ||
| 26 | type: 'separator' | ||
| 27 | }, | ||
| 28 | { | ||
| 29 | label: 'Cut', | ||
| 30 | accelerator: 'CmdOrCtrl+X', | ||
| 31 | role: 'cut' | ||
| 32 | }, | ||
| 33 | { | ||
| 34 | label: 'Copy', | ||
| 35 | accelerator: 'CmdOrCtrl+C', | ||
| 36 | role: 'copy' | ||
| 37 | }, | ||
| 38 | { | ||
| 39 | label: 'Paste', | ||
| 40 | accelerator: 'CmdOrCtrl+V', | ||
| 41 | role: 'paste' | ||
| 42 | }, | ||
| 43 | { | ||
| 44 | label: 'Select All', | ||
| 45 | accelerator: 'CmdOrCtrl+A', | ||
| 46 | role: 'selectall' | ||
| 47 | } | ||
| 48 | ] | ||
| 49 | } | ||
| 50 | ]); |
src/main/OpenblockDesktopLink.js
0 → 100644
| 1 | import {app} from 'electron'; | ||
| 2 | import path from 'path'; | ||
| 3 | import os from 'os'; | ||
| 4 | import {execFile, spawn} from 'child_process'; | ||
| 5 | import fs from 'fs-extra'; | ||
| 6 | |||
| 7 | import sudo from 'sudo-prompt'; | ||
| 8 | import {productName} from '../../package.json'; | ||
| 9 | |||
| 10 | import OpenBlockLink from 'openblock-link'; | ||
| 11 | import OpenblockResourceServer from 'openblock-resource'; | ||
| 12 | |||
| 13 | class OpenblockDesktopLink { | ||
| 14 | constructor () { | ||
| 15 | this._resourceServer = null; | ||
| 16 | |||
| 17 | this.appPath = app.getAppPath(); | ||
| 18 | if (this.appPath.search(/app/g) !== -1) { | ||
| 19 | // Normal app | ||
| 20 | this.appPath = path.join(this.appPath, '../../'); | ||
| 21 | } else if (this.appPath.search(/main/g) !== -1) { // eslint-disable-line no-negated-condition | ||
| 22 | // Start by start script in debug mode. | ||
| 23 | this.appPath = path.join(this.appPath, '../../'); | ||
| 24 | } else { | ||
| 25 | // App in dir mode | ||
| 26 | this.appPath = path.join(this.appPath, '../'); | ||
| 27 | } | ||
| 28 | |||
| 29 | const userDataPath = app.getPath( | ||
| 30 | 'userData' | ||
| 31 | ); | ||
| 32 | this.dataPath = path.join(userDataPath, 'Data'); | ||
| 33 | |||
| 34 | this._link = new OpenBlockLink(this.dataPath, path.join(this.appPath, 'tools')); | ||
| 35 | this._resourceServer = new OpenblockResourceServer(this.dataPath, | ||
| 36 | path.join(this.appPath, 'external-resources'), | ||
| 37 | app.getLocaleCountryCode()); | ||
| 38 | } | ||
| 39 | |||
| 40 | get resourceServer () { | ||
| 41 | return this._resourceServer; | ||
| 42 | } | ||
| 43 | |||
| 44 | installDriver (callback = null) { | ||
| 45 | const driverPath = path.join(this.appPath, 'drivers'); | ||
| 46 | if ((os.platform() === 'win32') && (os.arch() === 'x64')) { | ||
| 47 | execFile('install_x64.bat', [], {cwd: driverPath}); | ||
| 48 | } else if ((os.platform() === 'win32') && (os.arch() === 'ia32')) { | ||
| 49 | execFile('install_x86.bat', [], {cwd: driverPath}); | ||
| 50 | } else if ((os.platform() === 'darwin')) { | ||
| 51 | spawn('sh', ['install.sh'], {shell: true, cwd: driverPath}); | ||
| 52 | } else if ((os.platform() === 'linux')) { | ||
| 53 | sudo.exec(`sh ${path.join(driverPath, 'linux_setup.sh')} yang`, {name: productName}, | ||
| 54 | error => { | ||
| 55 | if (error) throw error; | ||
| 56 | if (callback) { | ||
| 57 | callback(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | ); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | clearCache (reboot = true) { | ||
| 65 | if (fs.existsSync(this.dataPath)) { | ||
| 66 | fs.rmSync(this.dataPath, {recursive: true, force: true}); | ||
| 67 | } | ||
| 68 | if (reboot){ | ||
| 69 | app.relaunch(); | ||
| 70 | app.exit(); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | start () { | ||
| 75 | this._link.listen(); | ||
| 76 | |||
| 77 | // start resource server | ||
| 78 | this._resourceServer.listen(); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | export default OpenblockDesktopLink; |
src/main/OpenblockDesktopTelemetry.js
0 → 100644
| 1 | import {app, ipcMain} from 'electron'; | ||
| 2 | import defaultsDeep from 'lodash.defaultsdeep'; | ||
| 3 | import {version} from '../../package.json'; | ||
| 4 | |||
| 5 | import TelemetryClient from './telemetry/TelemetryClient'; | ||
| 6 | |||
| 7 | const EVENT_TEMPLATE = { | ||
| 8 | version, | ||
| 9 | projectName: '', | ||
| 10 | language: '', | ||
| 11 | metadata: { | ||
| 12 | scriptCount: -1, | ||
| 13 | spriteCount: -1, | ||
| 14 | variablesCount: -1, | ||
| 15 | blocksCount: -1, | ||
| 16 | costumesCount: -1, | ||
| 17 | listsCount: -1, | ||
| 18 | soundsCount: -1 | ||
| 19 | } | ||
| 20 | }; | ||
| 21 | |||
| 22 | const APP_ID = 'openblock-desktop'; | ||
| 23 | const APP_VERSION = app.getVersion(); | ||
| 24 | const APP_INFO = Object.freeze({ | ||
| 25 | projectName: `${APP_ID} ${APP_VERSION}` | ||
| 26 | }); | ||
| 27 | |||
| 28 | class ScratchDesktopTelemetry { | ||
| 29 | constructor () { | ||
| 30 | this._telemetryClient = new TelemetryClient(); | ||
| 31 | } | ||
| 32 | |||
| 33 | get didOptIn () { | ||
| 34 | return this._telemetryClient.didOptIn; | ||
| 35 | } | ||
| 36 | set didOptIn (value) { | ||
| 37 | this._telemetryClient.didOptIn = value; | ||
| 38 | } | ||
| 39 | |||
| 40 | appWasOpened () { | ||
| 41 | this._telemetryClient.addEvent('app::open', {...EVENT_TEMPLATE, ...APP_INFO}); | ||
| 42 | } | ||
| 43 | |||
| 44 | appWillClose () { | ||
| 45 | this._telemetryClient.addEvent('app::close', {...EVENT_TEMPLATE, ...APP_INFO}); | ||
| 46 | } | ||
| 47 | |||
| 48 | projectDidLoad (metadata = {}) { | ||
| 49 | this._telemetryClient.addEvent('project::load', this._buildMetadata(metadata)); | ||
| 50 | } | ||
| 51 | |||
| 52 | projectDidSave (metadata = {}) { | ||
| 53 | // Since the save dialog appears on the main process the GUI does not wait for the actual save to complete. | ||
| 54 | // That means the GUI sends this event before we know the file name used for the save, which is where the new | ||
| 55 | // project title comes from. Instead, just hold on to this metadata pending a `projectSaveCompleted` event | ||
| 56 | // from the save code on the main process. If the user cancels the save this data will be cleared. | ||
| 57 | this._pendingProjectSave = metadata; | ||
| 58 | } | ||
| 59 | |||
| 60 | projectSaveCompleted (newProjectTitle) { | ||
| 61 | const metadata = this._pendingProjectSave; | ||
| 62 | this._pendingProjectSave = null; | ||
| 63 | |||
| 64 | metadata.projectName = newProjectTitle; | ||
| 65 | this._telemetryClient.addEvent('project::save', this._buildMetadata(metadata)); | ||
| 66 | } | ||
| 67 | |||
| 68 | projectSaveCanceled () { | ||
| 69 | this._pendingProjectSave = null; | ||
| 70 | } | ||
| 71 | |||
| 72 | projectWasCreated (metadata = {}) { | ||
| 73 | this._telemetryClient.addEvent('project::create', this._buildMetadata(metadata)); | ||
| 74 | } | ||
| 75 | |||
| 76 | projectWasUploaded (metadata = {}) { | ||
| 77 | this._telemetryClient.addEvent('project::upload', this._buildMetadata(metadata)); | ||
| 78 | } | ||
| 79 | |||
| 80 | _buildMetadata (metadata) { | ||
| 81 | const {projectName, language, ...codeMetadata} = metadata; | ||
| 82 | return defaultsDeep({ | ||
| 83 | projectName, | ||
| 84 | language, | ||
| 85 | metadata: codeMetadata | ||
| 86 | }, EVENT_TEMPLATE); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // make a singleton so it's easy to share across both Electron processes | ||
| 91 | const scratchDesktopTelemetrySingleton = new ScratchDesktopTelemetry(); | ||
| 92 | |||
| 93 | // `handle` works with `invoke` | ||
| 94 | ipcMain.handle('getTelemetryDidOptIn', () => | ||
| 95 | scratchDesktopTelemetrySingleton.didOptIn | ||
| 96 | ); | ||
| 97 | // `on` works with `sendSync` (and `send`) | ||
| 98 | ipcMain.on('getTelemetryDidOptIn', event => { | ||
| 99 | event.returnValue = scratchDesktopTelemetrySingleton.didOptIn; | ||
| 100 | }); | ||
| 101 | ipcMain.on('setTelemetryDidOptIn', (event, arg) => { | ||
| 102 | scratchDesktopTelemetrySingleton.didOptIn = arg; | ||
| 103 | }); | ||
| 104 | ipcMain.on('projectDidLoad', (event, arg) => { | ||
| 105 | scratchDesktopTelemetrySingleton.projectDidLoad(arg); | ||
| 106 | }); | ||
| 107 | ipcMain.on('projectDidSave', (event, arg) => { | ||
| 108 | scratchDesktopTelemetrySingleton.projectDidSave(arg); | ||
| 109 | }); | ||
| 110 | ipcMain.on('projectWasCreated', (event, arg) => { | ||
| 111 | scratchDesktopTelemetrySingleton.projectWasCreated(arg); | ||
| 112 | }); | ||
| 113 | ipcMain.on('projectWasUploaded', (event, arg) => { | ||
| 114 | scratchDesktopTelemetrySingleton.projectWasUploaded(arg); | ||
| 115 | }); | ||
| 116 | |||
| 117 | export default scratchDesktopTelemetrySingleton; |
src/main/OpenblockDesktopUpdater.js
0 → 100644
This diff is collapsed.
Click to expand it.
src/main/argv.js
0 → 100644
| 1 | import minimist from 'minimist'; | ||
| 2 | |||
| 3 | // inspired by yargs' process-argv | ||
| 4 | export const isElectronApp = () => !!process.versions.electron; | ||
| 5 | export const isElectronBundledApp = () => isElectronApp() && !process.defaultApp; | ||
| 6 | |||
| 7 | export const parseAndTrimArgs = argv => { | ||
| 8 | // bundled Electron app: ignore 1 from "my-app arg1 arg2" | ||
| 9 | // unbundled Electron app: ignore 2 from "electron main/index.js arg1 arg2" | ||
| 10 | // node.js app: ignore 2 from "node src/index.js arg1 arg2" | ||
| 11 | const ignoreCount = isElectronBundledApp() ? 1 : 2; | ||
| 12 | |||
| 13 | const parsed = minimist(argv); | ||
| 14 | |||
| 15 | // ignore arguments AFTER parsing to handle cases like "electron --inspect=42 my.js arg1 arg2" | ||
| 16 | parsed._ = parsed._.slice(ignoreCount); | ||
| 17 | |||
| 18 | return parsed; | ||
| 19 | }; | ||
| 20 | |||
| 21 | const argv = parseAndTrimArgs(process.argv); | ||
| 22 | |||
| 23 | export default argv; |
src/main/index.js
0 → 100644
This diff is collapsed.
Click to expand it.
src/main/telemetry/TelemetryClient.js
0 → 100644
This diff is collapsed.
Click to expand it.
src/renderer/.eslintrc.js
0 → 100644
src/renderer/ScratchDesktopAppStateHOC.jsx
0 → 100644
| 1 | import {ipcRenderer} from 'electron'; | ||
| 2 | import bindAll from 'lodash.bindall'; | ||
| 3 | import React from 'react'; | ||
| 4 | |||
| 5 | /** | ||
| 6 | * Higher-order component to add desktop logic to AppStateHOC. | ||
| 7 | * @param {Component} WrappedComponent - an AppStateHOC-like component to wrap. | ||
| 8 | * @returns {Component} - a component similar to AppStateHOC with desktop-specific logic added. | ||
| 9 | */ | ||
| 10 | const ScratchDesktopAppStateHOC = function (WrappedComponent) { | ||
| 11 | class ScratchDesktopAppStateComponent extends React.Component { | ||
| 12 | constructor (props) { | ||
| 13 | super(props); | ||
| 14 | bindAll(this, [ | ||
| 15 | 'handleTelemetryModalOptIn', | ||
| 16 | 'handleTelemetryModalOptOut' | ||
| 17 | ]); | ||
| 18 | this.state = { | ||
| 19 | // use `sendSync` because this should be set before first render | ||
| 20 | telemetryDidOptIn: ipcRenderer.sendSync('getTelemetryDidOptIn') | ||
| 21 | }; | ||
| 22 | } | ||
| 23 | handleTelemetryModalOptIn () { | ||
| 24 | ipcRenderer.send('setTelemetryDidOptIn', true); | ||
| 25 | ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => { | ||
| 26 | this.setState({telemetryDidOptIn}); | ||
| 27 | }); | ||
| 28 | } | ||
| 29 | handleTelemetryModalOptOut () { | ||
| 30 | ipcRenderer.send('setTelemetryDidOptIn', false); | ||
| 31 | ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => { | ||
| 32 | this.setState({telemetryDidOptIn}); | ||
| 33 | }); | ||
| 34 | } | ||
| 35 | render () { | ||
| 36 | const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean'); | ||
| 37 | |||
| 38 | return (<WrappedComponent | ||
| 39 | isTelemetryEnabled={this.state.telemetryDidOptIn} | ||
| 40 | onTelemetryModalOptIn={this.handleTelemetryModalOptIn} | ||
| 41 | onTelemetryModalOptOut={this.handleTelemetryModalOptOut} | ||
| 42 | showTelemetryModal={shouldShowTelemetryModal} | ||
| 43 | |||
| 44 | // allow passed-in props to override any of the above | ||
| 45 | {...this.props} | ||
| 46 | />); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | return ScratchDesktopAppStateComponent; | ||
| 51 | }; | ||
| 52 | |||
| 53 | export default ScratchDesktopAppStateHOC; |
src/renderer/ScratchDesktopGUIHOC.jsx
0 → 100644
This diff is collapsed.
Click to expand it.
src/renderer/about.css
0 → 100644
| 1 | html, body { | ||
| 2 | background-color: #4D97FF; | ||
| 3 | color: white; | ||
| 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | ||
| 5 | font-weight: bolder; | ||
| 6 | } | ||
| 7 | |||
| 8 | a:active, a:hover, a:link, a:visited { | ||
| 9 | color: currentColor; | ||
| 10 | } | ||
| 11 | |||
| 12 | a:active, a:hover { | ||
| 13 | filter: brightness(0.9); | ||
| 14 | } | ||
| 15 | |||
| 16 | .aboutBox { | ||
| 17 | margin: 0; | ||
| 18 | position: absolute; | ||
| 19 | top: 50%; | ||
| 20 | left: 50%; | ||
| 21 | transform: translate(-50%, -50%); | ||
| 22 | text-align:center; | ||
| 23 | } | ||
| 24 | |||
| 25 | .aboutLogo { | ||
| 26 | max-width: 10rem; | ||
| 27 | max-height: 10rem; | ||
| 28 | margin: 1.5rem 1.5rem 0 1.5rem; | ||
| 29 | } | ||
| 30 | |||
| 31 | .aboutText { | ||
| 32 | margin: 1.5rem; | ||
| 33 | } | ||
| 34 | |||
| 35 | .aboutDetails { | ||
| 36 | font-size: x-small; | ||
| 37 | text-align:left; | ||
| 38 | margin:auto; | ||
| 39 | |||
| 40 | } | ||
| 41 | |||
| 42 | .aboutFooter { | ||
| 43 | font-size: small; | ||
| 44 | } |
src/renderer/about.jsx
0 → 100644
| 1 | import React from 'react'; | ||
| 2 | import {productName, version} from '../../package.json'; | ||
| 3 | |||
| 4 | import logo from '../icon/OpenBlockDesktop.svg'; | ||
| 5 | import styles from './about.css'; | ||
| 6 | |||
| 7 | const AboutElement = () => ( | ||
| 8 | <div className={styles.aboutBox}> | ||
| 9 | <div><img | ||
| 10 | alt={`${productName} icon`} | ||
| 11 | src={logo} | ||
| 12 | className={styles.aboutLogo} | ||
| 13 | /></div> | ||
| 14 | <div className={styles.aboutText}> | ||
| 15 | <h2>{productName}</h2> | ||
| 16 | Version {version} | ||
| 17 | <table className={styles.aboutDetails}><tbody> | ||
| 18 | { | ||
| 19 | ['Electron', 'Chrome', 'Node'].map(component => { | ||
| 20 | const componentVersion = process.versions[component.toLowerCase()]; | ||
| 21 | return <tr key={component}><td>{component}</td><td>{componentVersion}</td></tr>; | ||
| 22 | }) | ||
| 23 | } | ||
| 24 | </tbody></table> | ||
| 25 | </div> | ||
| 26 | </div> | ||
| 27 | ); | ||
| 28 | |||
| 29 | export default <AboutElement />; |
src/renderer/app.css
0 → 100644
src/renderer/app.jsx
0 → 100644
| 1 | import React from 'react'; | ||
| 2 | import {compose} from 'redux'; | ||
| 3 | import GUI from 'openblock-gui/src/index'; | ||
| 4 | |||
| 5 | import AppStateHOC from 'openblock-gui/src/lib/app-state-hoc.jsx'; | ||
| 6 | |||
| 7 | import ScratchDesktopAppStateHOC from './ScratchDesktopAppStateHOC.jsx'; | ||
| 8 | import ScratchDesktopGUIHOC from './ScratchDesktopGUIHOC.jsx'; | ||
| 9 | import styles from './app.css'; | ||
| 10 | |||
| 11 | const appTarget = document.getElementById('app'); | ||
| 12 | appTarget.className = styles.app || 'app'; | ||
| 13 | |||
| 14 | GUI.setAppElement(appTarget); | ||
| 15 | |||
| 16 | |||
| 17 | // note that redux's 'compose' function is just being used as a general utility to make | ||
| 18 | // the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's | ||
| 19 | // ability to compose reducers. | ||
| 20 | const WrappedGui = compose( | ||
| 21 | ScratchDesktopAppStateHOC, | ||
| 22 | AppStateHOC, | ||
| 23 | ScratchDesktopGUIHOC | ||
| 24 | )(GUI); | ||
| 25 | |||
| 26 | export default <WrappedGui />; |
src/renderer/index.html
0 → 100644
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta charset="utf-8"> | ||
| 5 | <style> | ||
| 6 | body { | ||
| 7 | background-color: #4D97FF; | ||
| 8 | } | ||
| 9 | .splash { | ||
| 10 | color: white; | ||
| 11 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | ||
| 12 | font-size: xx-large; | ||
| 13 | font-weight: bolder; | ||
| 14 | margin: 0; | ||
| 15 | position: absolute; | ||
| 16 | top: 50%; | ||
| 17 | left: 50%; | ||
| 18 | transform: translate(-50%, -50%); | ||
| 19 | } | ||
| 20 | </style> | ||
| 21 | </head> | ||
| 22 | <body> | ||
| 23 | <div id="app"><p class="splash">OpenBlock is loading...</p></div> | ||
| 24 | </body> | ||
| 25 | </html> |
src/renderer/index.js
0 → 100644
| 1 | // This file does async imports of the heavy JSX, especially app.jsx, to avoid blocking the first render. | ||
| 2 | // The main index.html just contains a loading/splash screen which will display while this import loads. | ||
| 3 | |||
| 4 | import {ipcRenderer} from 'electron'; | ||
| 5 | |||
| 6 | import ReactDOM from 'react-dom'; | ||
| 7 | |||
| 8 | ipcRenderer.on('ready-to-show', () => { | ||
| 9 | // Start without any element in focus, otherwise the first link starts with focus and shows an orange box. | ||
| 10 | // We shouldn't disable that box or the focus behavior in case someone wants or needs to navigate that way. | ||
| 11 | // This seems like a hack... maybe there's some better way to do avoid any element starting with focus? | ||
| 12 | document.activeElement.blur(); | ||
| 13 | }); | ||
| 14 | |||
| 15 | const route = new URLSearchParams(window.location.search).get('route') || 'app'; | ||
| 16 | let routeModulePromise; | ||
| 17 | switch (route) { | ||
| 18 | case 'loading': | ||
| 19 | routeModulePromise = import('./loading.jsx'); | ||
| 20 | break; | ||
| 21 | case 'app': | ||
| 22 | routeModulePromise = import('./app.jsx'); | ||
| 23 | break; | ||
| 24 | case 'about': | ||
| 25 | routeModulePromise = import('./about.jsx'); | ||
| 26 | break; | ||
| 27 | case 'license': | ||
| 28 | routeModulePromise = import('./license.jsx'); | ||
| 29 | break; | ||
| 30 | case 'privacy': | ||
| 31 | routeModulePromise = import('./privacy.jsx'); | ||
| 32 | break; | ||
| 33 | } | ||
| 34 | |||
| 35 | routeModulePromise.then(routeModule => { | ||
| 36 | const appTarget = document.getElementById('app'); | ||
| 37 | const routeElement = routeModule.default; | ||
| 38 | ReactDOM.render(routeElement, appTarget); | ||
| 39 | }); |
src/renderer/license.css
0 → 100644
| 1 | html, body { | ||
| 2 | background-color: #4D97FF; | ||
| 3 | color: white; | ||
| 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | ||
| 5 | font-weight: normal; | ||
| 6 | line-height: 150%; | ||
| 7 | } | ||
| 8 | |||
| 9 | a:active, a:hover, a:link, a:visited { | ||
| 10 | color: currentColor; | ||
| 11 | } | ||
| 12 | |||
| 13 | a:active, a:hover { | ||
| 14 | filter: brightness(0.9); | ||
| 15 | } | ||
| 16 | |||
| 17 | .licenseBox { | ||
| 18 | margin: 3rem; | ||
| 19 | position: absolute; | ||
| 20 | } | ||
| 21 | |||
| 22 | .aboutFooter { | ||
| 23 | font-size: small; | ||
| 24 | } | ||
| 25 | |||
| 26 | .tabList { | ||
| 27 | height: 32px; | ||
| 28 | width: 250px; /* Match width of the toolbox */ | ||
| 29 | display: flex; | ||
| 30 | align-items: flex-end; | ||
| 31 | flex-shrink: 0; | ||
| 32 | |||
| 33 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | ||
| 34 | font-weight: 500; | ||
| 35 | font-size: 0.80rem; | ||
| 36 | |||
| 37 | /* Overrides for react-tabs styling */ | ||
| 38 | margin: 0 !important; | ||
| 39 | border-bottom: 0 !important; | ||
| 40 | } | ||
| 41 | |||
| 42 | .tab { | ||
| 43 | flex-grow: 1; | ||
| 44 | height: 80%; | ||
| 45 | margin-bottom: 0; | ||
| 46 | |||
| 47 | border-radius: 0.5rem 0.5rem 0 0; | ||
| 48 | border: 1px solid hsla(0, 0%, 0%, 0.15); | ||
| 49 | |||
| 50 | padding: 0.125rem 1.25rem 0; | ||
| 51 | font-size: 0.75rem; | ||
| 52 | |||
| 53 | background-color: rgb(219, 219, 219); | ||
| 54 | color: #575e75; | ||
| 55 | |||
| 56 | display: flex; | ||
| 57 | justify-content: center; | ||
| 58 | align-items: center; | ||
| 59 | |||
| 60 | user-select: none; | ||
| 61 | white-space: nowrap; | ||
| 62 | } | ||
| 63 | |||
| 64 | .tab { | ||
| 65 | margin-left: -0.5rem; | ||
| 66 | } | ||
| 67 | |||
| 68 | .tab:nth-of-type(1) { | ||
| 69 | margin-left: 0; | ||
| 70 | } | ||
| 71 | |||
| 72 | /* Use z-indices to force left-on-top for tabs */ | ||
| 73 | .tab:nth-of-type(1) { | ||
| 74 | z-index: 3; | ||
| 75 | } | ||
| 76 | .tab:nth-of-type(2) { | ||
| 77 | z-index: 2; | ||
| 78 | } | ||
| 79 | .tab:nth-of-type(3) { | ||
| 80 | z-index: 1; | ||
| 81 | } | ||
| 82 | |||
| 83 | .tab:hover { | ||
| 84 | background-color: rgb(235, 235, 235); | ||
| 85 | } | ||
| 86 | |||
| 87 | .tab.isSelected { | ||
| 88 | height: 90%; | ||
| 89 | background-color: white; | ||
| 90 | color: #4D97FF; | ||
| 91 | border-bottom: none; | ||
| 92 | z-index: 4; | ||
| 93 | } | ||
| 94 | |||
| 95 | .tab.active { | ||
| 96 | background-color: hsla(215, 100%, 65%, 0.15); | ||
| 97 | } | ||
| 98 | |||
| 99 | .tabs { | ||
| 100 | position: relative; | ||
| 101 | flex-grow: 1; | ||
| 102 | display: flex; | ||
| 103 | flex-direction: column; | ||
| 104 | border: 1px solid #D9D9D9; | ||
| 105 | border-top: none; | ||
| 106 | } | ||
| 107 | |||
| 108 | .tabPanel { | ||
| 109 | position: relative; | ||
| 110 | flex-grow: 1; | ||
| 111 | display: none; | ||
| 112 | background-color: white; | ||
| 113 | color: #575e75; | ||
| 114 | padding: 2rem 3rem; | ||
| 115 | } | ||
| 116 | |||
| 117 | .tabPanel h4{ | ||
| 118 | margin: 0.2rem 0; | ||
| 119 | } | ||
| 120 | |||
| 121 | .tabPanel.isSelected { | ||
| 122 | display: flex; | ||
| 123 | flex-direction: column; | ||
| 124 | } | ||
| 125 | |||
| 126 | .logo { | ||
| 127 | height: 80px; | ||
| 128 | margin: 3rem auto 3rem; | ||
| 129 | } | ||
| 130 | |||
| 131 | .logo:hover { | ||
| 132 | cursor: pointer; | ||
| 133 | } |
src/renderer/license.jsx
0 → 100644
| 1 | /* eslint-disable max-len */ | ||
| 2 | import React from 'react'; | ||
| 3 | import styles from './license.css'; | ||
| 4 | import bindAll from 'lodash.bindall'; | ||
| 5 | import classNames from 'classnames'; | ||
| 6 | |||
| 7 | import OpenBlockLogo from '../icon/logo-OpenBlockcc.svg'; | ||
| 8 | import ScratchFoundationLogo from '../icon/logo-ScratchFoundation.svg'; | ||
| 9 | |||
| 10 | // Insert new copyright information at the head of the array to add a new copyright notice | ||
| 11 | const copyrightInformations = [ | ||
| 12 | { | ||
| 13 | id: 'OpenBlock.cc', | ||
| 14 | logo: OpenBlockLogo, | ||
| 15 | link: 'https://www.openblock.cc/', | ||
| 16 | license: 'MIT' | ||
| 17 | }, | ||
| 18 | { | ||
| 19 | id: 'Scratch Foundation', | ||
| 20 | link: 'https://www.scratchfoundation.org/', | ||
| 21 | logo: ScratchFoundationLogo, | ||
| 22 | license: 'BSD-3-Clause' | ||
| 23 | } | ||
| 24 | ]; | ||
| 25 | |||
| 26 | const licenseContent = { | ||
| 27 | 'MIT': ( | ||
| 28 | <div className={styles.licenseContent}> | ||
| 29 | <p> | ||
| 30 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 31 | of this software and associated documentation files (the "Software"), to deal | ||
| 32 | in the Software without restriction, including without limitation the rights | ||
| 33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 34 | copies of the Software, and to permit persons to whom the Software is | ||
| 35 | furnished to do so, subject to the following conditions: | ||
| 36 | </p> | ||
| 37 | <p> | ||
| 38 | The above copyright notice and this permission notice shall be included in all | ||
| 39 | copies or substantial portions of the Software. | ||
| 40 | </p> | ||
| 41 | <p> | ||
| 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 48 | SOFTWARE. | ||
| 49 | </p> | ||
| 50 | </div> | ||
| 51 | ), | ||
| 52 | 'BSD-3-Clause': ( | ||
| 53 | <div className={styles.licenseContent}> | ||
| 54 | <p> | ||
| 55 | Redistribution and use in source and binary forms, with or without modification, | ||
| 56 | are permitted provided that the following conditions are met: | ||
| 57 | </p> | ||
| 58 | <p> | ||
| 59 | 1. Redistributions of source code must retain the above copyright notice, this | ||
| 60 | list of conditions and the following disclaimer. | ||
| 61 | </p> | ||
| 62 | <p> | ||
| 63 | 2. Redistributions in binary form must reproduce the above copyright notice, this | ||
| 64 | list of conditions and the following disclaimer in the documentation and/or other | ||
| 65 | materials provided with the distribution. | ||
| 66 | </p> | ||
| 67 | <p> | ||
| 68 | 3. Neither the name of the copyright holder nor the names of its contributors may be | ||
| 69 | used to endorse or promote products derived from this software without specific | ||
| 70 | prior written permission. | ||
| 71 | </p> | ||
| 72 | <p> | ||
| 73 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY | ||
| 74 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
| 75 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT | ||
| 76 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
| 77 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||
| 78 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | ||
| 79 | OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER | ||
| 80 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | ||
| 81 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||
| 82 | SUCH DAMAGE. | ||
| 83 | </p> | ||
| 84 | </div> | ||
| 85 | ) | ||
| 86 | }; | ||
| 87 | |||
| 88 | class LicenseElement extends React.Component { | ||
| 89 | constructor (props) { | ||
| 90 | super(props); | ||
| 91 | bindAll(this, [ | ||
| 92 | 'handleClickTab' | ||
| 93 | ]); | ||
| 94 | this.state = { | ||
| 95 | selectedTab: copyrightInformations[0].id | ||
| 96 | }; | ||
| 97 | } | ||
| 98 | |||
| 99 | handleClickLogo (e) { | ||
| 100 | copyrightInformations.forEach(item => { | ||
| 101 | if (item.id === e.currentTarget.alt) { | ||
| 102 | window.open(item.link); | ||
| 103 | } | ||
| 104 | }); | ||
| 105 | } | ||
| 106 | |||
| 107 | handleClickTab (e) { | ||
| 108 | this.setState({selectedTab: e.currentTarget.id}); | ||
| 109 | } | ||
| 110 | |||
| 111 | buildLicenseTabList () { | ||
| 112 | return copyrightInformations.map(item => ( | ||
| 113 | <button | ||
| 114 | key={item.id} | ||
| 115 | id={item.id} | ||
| 116 | className={classNames(styles.tab, { | ||
| 117 | [styles.isSelected]: this.state.selectedTab === item.id | ||
| 118 | })} | ||
| 119 | onClick={this.handleClickTab} | ||
| 120 | > | ||
| 121 | {item.id} | ||
| 122 | </button> | ||
| 123 | )); | ||
| 124 | } | ||
| 125 | |||
| 126 | buildLicenseContent () { | ||
| 127 | return copyrightInformations.map(item => ( | ||
| 128 | <div | ||
| 129 | key={item.id} | ||
| 130 | className={classNames(styles.tabPanel, { | ||
| 131 | [styles.isSelected]: this.state.selectedTab === item.id | ||
| 132 | })} | ||
| 133 | > | ||
| 134 | <img | ||
| 135 | alt={item.id} | ||
| 136 | className={styles.logo} | ||
| 137 | draggable={false} | ||
| 138 | src={item.logo} | ||
| 139 | onClick={this.handleClickLogo} | ||
| 140 | /> | ||
| 141 | <h4>{item.license} License</h4> | ||
| 142 | <h4>Copyright © {item.id}</h4> | ||
| 143 | {licenseContent[item.license]} | ||
| 144 | </div> | ||
| 145 | )); | ||
| 146 | } | ||
| 147 | |||
| 148 | render () { | ||
| 149 | const tabList = this.buildLicenseTabList(); | ||
| 150 | const content = this.buildLicenseContent(); | ||
| 151 | |||
| 152 | return ( | ||
| 153 | <div className={styles.licenseBox}> | ||
| 154 | <div className={styles.tabList}> | ||
| 155 | {tabList} | ||
| 156 | </div> | ||
| 157 | <div className={styles.tabs}> | ||
| 158 | {content} | ||
| 159 | </div> | ||
| 160 | </div> | ||
| 161 | ); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | export default <LicenseElement />; |
src/renderer/loading.css
0 → 100644
src/renderer/loading.jsx
0 → 100644
| 1 | import React from 'react'; | ||
| 2 | import {productName} from '../../package.json'; | ||
| 3 | |||
| 4 | import logo from '../icon/OpenBlockLoading.svg'; | ||
| 5 | import styles from './loading.css'; | ||
| 6 | |||
| 7 | const LoadingElement = () => ( | ||
| 8 | <div className={styles.loadingBox}> | ||
| 9 | <div> | ||
| 10 | <img | ||
| 11 | alt={`${productName} loading icon`} | ||
| 12 | src={logo} | ||
| 13 | className={styles.loadingLogo} | ||
| 14 | /> | ||
| 15 | </div> | ||
| 16 | </div> | ||
| 17 | ); | ||
| 18 | |||
| 19 | export default <LoadingElement />; |
src/renderer/privacy.css
0 → 100644
src/renderer/privacy.jsx
0 → 100644
This diff is collapsed.
Click to expand it.
src/renderer/showPrivacyPolicy.js
0 → 100644
| 1 | import {ipcRenderer} from 'electron'; | ||
| 2 | |||
| 3 | const showPrivacyPolicy = event => { | ||
| 4 | if (event) { | ||
| 5 | // Probably a click on a link; don't actually follow the link in the `href` attribute. | ||
| 6 | event.preventDefault(); | ||
| 7 | } | ||
| 8 | // tell the main process to open the privacy policy window | ||
| 9 | ipcRenderer.send('open-privacy-policy-window'); | ||
| 10 | return false; | ||
| 11 | }; | ||
| 12 | |||
| 13 | export default showPrivacyPolicy; |
webpack.main.js
0 → 100644
| 1 | const path = require('path'); | ||
| 2 | |||
| 3 | const makeConfig = require('./webpack.makeConfig.js'); | ||
| 4 | |||
| 5 | module.exports = defaultConfig => | ||
| 6 | makeConfig( | ||
| 7 | defaultConfig, | ||
| 8 | { | ||
| 9 | name: 'main', | ||
| 10 | useReact: false, | ||
| 11 | disableDefaultRulesForExtensions: ['js'], | ||
| 12 | babelPaths: [ | ||
| 13 | path.resolve(__dirname, 'src', 'main') | ||
| 14 | ] | ||
| 15 | } | ||
| 16 | ); |
webpack.makeConfig.js
0 → 100644
| 1 | const childProcess = require('child_process'); | ||
| 2 | const fs = require('fs'); | ||
| 3 | const path = require('path'); | ||
| 4 | const util = require('util'); | ||
| 5 | |||
| 6 | const electronPath = require('electron'); | ||
| 7 | const webpack = require('webpack'); | ||
| 8 | const merge = require('webpack-merge'); | ||
| 9 | |||
| 10 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); | ||
| 11 | const MONACO_DIR = path.resolve(__dirname, './node_modules/monaco-editor'); | ||
| 12 | |||
| 13 | // PostCss | ||
| 14 | const autoprefixer = require('autoprefixer'); | ||
| 15 | const postcssVars = require('postcss-simple-vars'); | ||
| 16 | const postcssImport = require('postcss-import'); | ||
| 17 | |||
| 18 | const isProduction = (process.env.NODE_ENV === 'production'); | ||
| 19 | |||
| 20 | const electronVersion = childProcess.execSync(`${electronPath} --version`, {encoding: 'utf8'}).trim(); | ||
| 21 | console.log(`Targeting Electron ${electronVersion}`); // eslint-disable-line no-console | ||
| 22 | |||
| 23 | const makeConfig = function (defaultConfig, options) { | ||
| 24 | const babelOptions = { | ||
| 25 | // Explicitly disable babelrc so we don't catch various config in much lower dependencies. | ||
| 26 | babelrc: false, | ||
| 27 | plugins: [ | ||
| 28 | '@babel/plugin-syntax-dynamic-import', | ||
| 29 | '@babel/plugin-transform-async-to-generator', | ||
| 30 | '@babel/plugin-proposal-object-rest-spread' | ||
| 31 | ], | ||
| 32 | presets: [ | ||
| 33 | ['@babel/preset-env', {targets: {electron: electronVersion}}] | ||
| 34 | ] | ||
| 35 | }; | ||
| 36 | |||
| 37 | const sourceFileTest = options.useReact ? /\.jsx?$/ : /\.js$/; | ||
| 38 | if (options.useReact) { | ||
| 39 | babelOptions.presets = babelOptions.presets.concat('@babel/preset-react'); | ||
| 40 | babelOptions.plugins.push(['react-intl', { | ||
| 41 | messagesDir: './translations/messages/' | ||
| 42 | }]); | ||
| 43 | } | ||
| 44 | |||
| 45 | // TODO: consider adjusting these rules instead of discarding them in at least some cases | ||
| 46 | if (options.disableDefaultRulesForExtensions) { | ||
| 47 | defaultConfig.module.rules = defaultConfig.module.rules.filter(rule => { | ||
| 48 | if (!(rule.test instanceof RegExp)) { | ||
| 49 | // currently we don't support overriding other kinds of rules | ||
| 50 | return true; | ||
| 51 | } | ||
| 52 | // disable default rules for any file extension listed here | ||
| 53 | // we will handle these files in some other way (see below) | ||
| 54 | // OR we want to avoid any processing at all (such as with fonts) | ||
| 55 | const shouldDisable = options.disableDefaultRulesForExtensions.some( | ||
| 56 | ext => rule.test.test(`test.${ext}`) | ||
| 57 | ); | ||
| 58 | const statusWord = shouldDisable ? 'Discarding' : 'Keeping'; | ||
| 59 | console.log(`${options.name}: ${statusWord} electron-webpack default rule for ${rule.test}`); | ||
| 60 | return !shouldDisable; | ||
| 61 | }); | ||
| 62 | } | ||
| 63 | |||
| 64 | const config = merge.smart(defaultConfig, { | ||
| 65 | devtool: 'cheap-module-eval-source-map', | ||
| 66 | mode: isProduction ? 'production' : 'development', | ||
| 67 | module: { | ||
| 68 | rules: [ | ||
| 69 | { | ||
| 70 | test: sourceFileTest, | ||
| 71 | include: options.babelPaths, | ||
| 72 | loader: 'babel-loader', | ||
| 73 | options: babelOptions | ||
| 74 | }, | ||
| 75 | { // coped from scratch-gui | ||
| 76 | test: /\.css$/, | ||
| 77 | exclude: MONACO_DIR, | ||
| 78 | use: [{ | ||
| 79 | loader: 'style-loader' | ||
| 80 | }, { | ||
| 81 | loader: 'css-loader', | ||
| 82 | options: { | ||
| 83 | modules: true, | ||
| 84 | importLoaders: 1, | ||
| 85 | localIdentName: '[name]_[local]_[hash:base64:5]', | ||
| 86 | camelCase: true | ||
| 87 | } | ||
| 88 | }, { | ||
| 89 | loader: 'postcss-loader', | ||
| 90 | options: { | ||
| 91 | ident: 'postcss', | ||
| 92 | plugins: function () { | ||
| 93 | return [ | ||
| 94 | postcssImport, | ||
| 95 | postcssVars, | ||
| 96 | autoprefixer | ||
| 97 | ]; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | }] | ||
| 101 | }, | ||
| 102 | { | ||
| 103 | test: /\.(svg|png|wav|gif|jpg|ttf)$/, | ||
| 104 | loader: 'file-loader', | ||
| 105 | options: { | ||
| 106 | outputPath: 'static/assets/' | ||
| 107 | } | ||
| 108 | }, | ||
| 109 | { | ||
| 110 | test: /\.css$/, | ||
| 111 | include: MONACO_DIR, | ||
| 112 | use: ['style-loader', 'css-loader'] | ||
| 113 | }, | ||
| 114 | { | ||
| 115 | test: /node_modules[/\\](iconv-lite)[/\\].+/, | ||
| 116 | resolve: { | ||
| 117 | aliasFields: ['main'] | ||
| 118 | } | ||
| 119 | } | ||
| 120 | ] | ||
| 121 | }, | ||
| 122 | plugins: [ | ||
| 123 | new webpack.DefinePlugin({ | ||
| 124 | 'process.env.GA_ID': `"${process.env.GA_ID || 'UA-000000-01'}"` | ||
| 125 | }), | ||
| 126 | new webpack.SourceMapDevToolPlugin({ | ||
| 127 | filename: '[file].map' | ||
| 128 | }), | ||
| 129 | new MonacoWebpackPlugin({ | ||
| 130 | languages: ['c', 'cpp', 'python', 'lua', 'javascript'], | ||
| 131 | features: ['!gotoSymbol'] | ||
| 132 | }) | ||
| 133 | ].concat(options.plugins || []), | ||
| 134 | resolve: { | ||
| 135 | cacheWithContext: false, | ||
| 136 | symlinks: false, | ||
| 137 | alias: { | ||
| 138 | // act like scratch-gui has this line in its package.json: | ||
| 139 | // "browser": "./src/index.js" | ||
| 140 | 'openblock-gui$': path.resolve(__dirname, 'node_modules', 'openblock-gui', 'src', 'index.js') | ||
| 141 | } | ||
| 142 | } | ||
| 143 | }); | ||
| 144 | |||
| 145 | // If we're not on CI, enable Webpack progress output | ||
| 146 | // Note that electron-webpack enables this by default, so use '--no-progress' to avoid double-adding this plugin | ||
| 147 | if (!process.env.CI) { | ||
| 148 | config.plugins.push(new webpack.ProgressPlugin()); | ||
| 149 | } | ||
| 150 | |||
| 151 | fs.writeFileSync( | ||
| 152 | `dist/webpack.${options.name}.js`, | ||
| 153 | `module.exports = ${util.inspect(config, {depth: null})};\n` | ||
| 154 | ); | ||
| 155 | |||
| 156 | return config; | ||
| 157 | }; | ||
| 158 | |||
| 159 | module.exports = makeConfig; |
webpack.renderer.js
0 → 100644
| 1 | const path = require('path'); | ||
| 2 | |||
| 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); | ||
| 4 | |||
| 5 | const makeConfig = require('./webpack.makeConfig.js'); | ||
| 6 | |||
| 7 | const getModulePath = moduleName => path.dirname(require.resolve(`${moduleName}/package.json`)); | ||
| 8 | |||
| 9 | module.exports = defaultConfig => | ||
| 10 | makeConfig( | ||
| 11 | defaultConfig, | ||
| 12 | { | ||
| 13 | name: 'renderer', | ||
| 14 | useReact: true, | ||
| 15 | disableDefaultRulesForExtensions: ['js', 'jsx', 'css', 'svg', 'png', 'wav', 'gif', 'jpg', 'ttf'], | ||
| 16 | babelPaths: [ | ||
| 17 | path.resolve(__dirname, 'src', 'renderer'), | ||
| 18 | /node_modules[\\/]+scratch-[^\\/]+[\\/]+src/, | ||
| 19 | /node_modules[\\/]+openblock-[^\\/]+[\\/]+src/, | ||
| 20 | /node_modules[\\/]+pify/, | ||
| 21 | /node_modules[\\/]+@vernier[\\/]+godirect/ | ||
| 22 | ], | ||
| 23 | plugins: [ | ||
| 24 | new CopyWebpackPlugin([{ | ||
| 25 | from: path.join(getModulePath('openblock-blocks'), 'media'), | ||
| 26 | to: 'static/blocks-media' | ||
| 27 | }]), | ||
| 28 | new CopyWebpackPlugin([{ | ||
| 29 | from: 'extension-worker.{js,js.map}', | ||
| 30 | context: path.join(getModulePath('openblock-vm'), 'dist', 'web') | ||
| 31 | }]), | ||
| 32 | new CopyWebpackPlugin([{ | ||
| 33 | from: path.join(getModulePath('openblock-gui'), 'src', 'lib', 'libraries', '*.json'), | ||
| 34 | to: 'static/libraries', | ||
| 35 | flatten: true | ||
| 36 | }]) | ||
| 37 | ] | ||
| 38 | } | ||
| 39 | ); |
-
Please register or sign in to post a comment