a6e4ddd9 by cx19940809

first-commit

0 parents
Showing 83 changed files with 2600 additions and 0 deletions
1 root = true
2
3 [*]
4 end_of_line = lf
5 insert_final_newline = true
6 charset = utf-8
7 indent_size = 4
8 trim_trailing_whitespace = true
9
10 [*.{js,html}]
11 indent_style = space
1 node_modules/*
2 dist/*
1 module.exports = {
2 root: true,
3 env: {
4 node: true
5 },
6 extends: ['scratch', 'scratch/es6', 'scratch/node']
7 };
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
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 ![Block sketch](https://user-images.githubusercontent.com/3431616/77192550-1dcebe00-6ab3-11ea-9606-8ecd8500c958.png)
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
1 ko_fi: arthurzheng
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_
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_
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
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
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
1 # Mac OS
2 .DS_Store
3
4 # NPM
5 /node_modules
6 npm-*
7
8 # Testing
9 /.nyc_output
10 /coverage
11
12 # Build
13 /.opt-in
14
15 # Temporary resource
16 /external-resources
17 /tools
18 /firmwares
19 /drivers
20
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 }
This diff is collapsed. Click to expand it.
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
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.
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 ![screenshot](./doc/screenshot.png)
21 ![screenshot2](./doc/screenshot2.png)
22
23 ## Getting Start
24
25 Visit the wiki: [https://openblockcc.github.io](https://openblockcc.github.io)
26
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.
1 /ScratchDesktop.iconset
2 /tmp
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
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>
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>
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
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
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
1 {
2 "main": {
3 "webpackConfig": "webpack.main.js"
4 },
5 "renderer": {
6 "template": "src/renderer/index.html",
7 "webpackConfig": "webpack.renderer.js"
8 }
9 }
This diff could not be displayed because it is too large.
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 }
1 module.exports = {
2 rules: {
3 'no-console': 'off'
4 }
5 };
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;
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 });
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();
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();
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;
1 module.exports = {
2 root: true,
3 env: {
4 node: true
5 },
6 extends: ['scratch', 'scratch/es6'],
7 globals: {
8 __static: false // electron-webpack provides this constant to access bundled static assets
9 }
10 };
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;
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}`);
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
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
1 module.exports = {
2 root: true,
3 env: {
4 node: true
5 },
6 extends: ['scratch', 'scratch/es6', 'scratch/node']
7 };
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 };
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 ]);
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;
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;
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;
1 module.exports = {
2 root: true,
3 env: {
4 browser: true,
5 node: true
6 },
7 extends: ['scratch', 'scratch/es6', 'scratch/react'],
8 settings: {
9 react: {
10 version: '16.2' // Prevent 16.3 lifecycle method errors
11 }
12 }
13 };
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;
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 }
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 />;
1 /* Adapted from scratch-gui/src/playground/index.css */
2 html,
3 body,
4 .app {
5 width: 100%;
6 height: 100%;
7 margin: 0;
8
9 /* Setting min height/width makes the UI scroll below those sizes */
10 min-width: 1024px;
11 min-height: 640px; /* Min height to fit sprite/backdrop button */
12 }
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 />;
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>
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 });
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 }
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 &quot;Software&quot;), 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 &quot;AS IS&quot;, 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 &quot;AS IS&quot; 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 &copy; {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 />;
1 html, body {
2 background-color: transparent;
3 }
4
5 .loadingBox {
6 margin: 0;
7 position: absolute;
8 top: 50%;
9 left: 50%;
10 transform: translate(-50%, -50%);
11 width: 100%;
12 height: 100%;
13 }
14
15 .loadingLogo {
16 max-width: 50rem;
17 }
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 />;
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 .privacyBox {
10 background-color: white;
11 color: #575e75;
12 margin: 3rem;
13 padding: 2rem 3rem;
14 }
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;
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 );
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;
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 );
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!