a6e4ddd9 by cx19940809

first-commit

0 parents
Showing 83 changed files with 2600 additions and 0 deletions
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_size = 4
trim_trailing_whitespace = true
[*.{js,html}]
indent_style = space
node_modules/*
dist/*
module.exports = {
root: true,
env: {
node: true
},
extends: ['scratch', 'scratch/es6', 'scratch/node']
};
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly specify line endings for as many files as possible.
# People who (for example) rsync between Windows and Linux need this.
# File types which we know are binary
# Prefer LF for most file types
*.css text eol=lf
*.htm text eol=lf
*.html text eol=lf
*.js text eol=lf
*.js.map text eol=lf
*.json text eol=lf
*.json5 text eol=lf
*.jsx text eol=lf
*.md text eol=lf
*.plist text eol=lf
*.xml text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
# Prefer LF for these files
.editorconfig text eol=lf
.eslintignore text eol=lf
.gitattributes text eol=lf
.gitignore text eol=lf
.npmignore text eol=lf
LICENSE text eol=lf
Makefile text eol=lf
TRADEMARK text eol=lf
# Use CRLF for Windows-specific file types
## Contributing
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.
### Ways to Help
* **Documenting bugs**
* 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.
* 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).
* Some issues are marked "Needs Repro". Adding a comment with good reproduction steps to those issues is a great way to help.
* 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.
* **Fixing bugs**
* 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.
* If the issue is marked "Help Wanted" you can go ahead and start working on it!
* **We will only accept Pull Requests for bugs that have an issue filed that has a priority label**
* 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.
* We are not looking for Pull Requests ("PR") for every issue and may deny a PR if it doesn't fit our criteria.
* We are far more likely to accept a PR if it is for an issue marked with Help Wanted.
* We will not accept PRs for issues marked with "Needs Discussion" or "Needs Design."
* Wait until the Repo Coordinator assigns the issue to you before you begin work or submit a PR.
### Learning Git and Github
If you want to work on fixing issues, you should be familiar with Git and Github.
* [Learn Git branching](https://learngitbranching.js.org/) includes an introduction to basic git commands and useful branching features.
* 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).
**Important:** we follow the [Github Flow process](https://guides.github.com/introduction/flow/) as our development process.
### How to Fix Bugs
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.
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)
3. Switch to the `develop` branch, and pull down the latest changes from upstream
4. Run the code, and reproduce the problem
5. Create your branch from the `develop` branch
6. Make code changes to fix the problem
7. Run `npm test` to make sure that your changes pass our tests
8. Commit your changes
9. Push your branch to your fork
10. Create your pull request
1. Make sure to follow the template in the PR description
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
When submitting pull requests keep in mind:
* please be patient -- it can take a while to find time to review them
* try to change the least amount of code necessary to fix the bug
* the code can't be radically changed without significant coordination with the Scratch Team, so these types of changes should be avoided
* 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
### Suggestions
![Block sketch](https://user-images.githubusercontent.com/3431616/77192550-1dcebe00-6ab3-11ea-9606-8ecd8500c958.png)
Please note: **_we are unlikely to accept PRs with new features that haven't been thought through and discussed as a group_**.
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).
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.
### Other resources
Beyond this repo, there are also some other resources that you might want to take a look at:
* [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)
* [Open Source forum](https://scratch.mit.edu/discuss/49/) on Scratch
* [Suggestions forum](https://scratch.mit.edu/discuss/1/) on Scratch
* [Bugs & Glitches forum](https://scratch.mit.edu/discuss/3/) on Scratch
ko_fi: arthurzheng
### Expected Behavior
_Please describe what should happen_
### Actual Behavior
_Describe what actually happens_
### Steps to Reproduce
_Explain what someone needs to do in order to see what's described in *Actual behavior* above_
### Operating System and Browser
_e.g. Mac OS 10.11.6 Safari 10.0_
### Resolves
_What Github issue does this resolve (please include link)?_
- Resolves #
### Proposed Changes
_Describe what this Pull Request does_
### Reason for Changes
_Explain why these changes should be made_
### Test Coverage
_Please show how you have added tests to cover your changes_
name: Build and release
on:
push:
tags:
- 'v*'
paths-ignore:
- 'README.md'
- 'CHANGE.md'
- 'doc/*'
- '.github/*'
jobs:
create-release:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.tag.outputs.tag }}
release_name: OpenBlock Desktop ${{ steps.tag.outputs.tag }}
body: |
## Change Log (en)
- **New feature**
1. Feature 1.
2. Feature 2.
- **Fix bug**
1. Bug 1.
2. Bug 2.
## 更改日志 (zh-cn)
- **新功能**
1. 功能1。
2. 功能2。
- **修复错误**
1. 错误1。
2. 错误2。
draft: true
prerelease: false
build-windows:
needs: create-release
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Npm Install
run: |
git config --global url."https://github.com".insteadOf ssh://git@github.com
npm ci
- name: Build and Publish
env:
GA_ID: ${{ secrets.GA_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NODE_ENV: production
NODE_OPTIONS: --max_old_space_size=4096
run: npm run publish
build-mac:
needs: create-release
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Install python2.7
run: |
brew install pyenv
pyenv install 2.7.18
pyenv global 2.7.18
- name: Npm Install
run: |
pip install setuptools
npm ci
- name: Build and Publish
env:
GA_ID: ${{ secrets.GA_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NODE_ENV: production
NODE_OPTIONS: --max_old_space_size=8192
CSC_IDENTITY_AUTO_DISCOVERY: false
PYTHON_PATH: python
run: |
export PYTHON_PATH=$(pyenv root)/shims/python
npm run publish
build-linux:
needs: create-release
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Npm Install
run: npm ci
- name: Build and Publish
env:
GA_ID: ${{ secrets.GA_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NODE_ENV: production
NODE_OPTIONS: --max_old_space_size=8192
CSC_IDENTITY_AUTO_DISCOVERY: false
run: npm run publish
name: Build Test App
on:
push:
branches: [ main ]
paths-ignore:
- 'README.md'
- 'CHANGE.md'
- 'doc/*'
- '.github/*'
jobs:
build-windows:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Npm Install
run: |
git config --global url."https://github.com".insteadOf ssh://git@github.com
npm ci
- name: Build Dist
env:
GA_ID: ${{ secrets.GA_ID }}
NODE_ENV: production
NODE_OPTIONS: --max_old_space_size=4096
run: npm run dist
- name: Archive production artifacts
uses: actions/upload-artifact@v2
with:
name: OpenBlock-Desktop-win32
path: dist/OpenBlock-Desktop*.exe
retention-days: 1
build-mac:
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Install python2.7
run: |
brew install pyenv
pyenv install 2.7.18
pyenv global 2.7.18
- name: Npm Install
run: |
pip install setuptools
npm ci
- name: Build Dist
env:
GA_ID: ${{ secrets.GA_ID }}
NODE_ENV: production
NODE_OPTIONS: --max_old_space_size=8192
CSC_IDENTITY_AUTO_DISCOVERY: false
run: |
export PYTHON_PATH=$(pyenv root)/shims/python
npm run dist
- name: Archive production artifacts
uses: actions/upload-artifact@v2
with:
name: OpenBlock-Desktop-darwin
path: dist/OpenBlock-Desktop*.dmg
retention-days: 1
build-linux:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Npm Install
run: npm ci
- name: Build Dist
env:
GA_ID: ${{ secrets.GA_ID }}
NODE_ENV: production
NODE_OPTIONS: --max_old_space_size=8192
CSC_IDENTITY_AUTO_DISCOVERY: false
run: npm run dist
- name: Archive production artifacts
uses: actions/upload-artifact@v2
with:
name: OpenBlock-Desktop-linux
path: dist/OpenBlock-Desktop*.deb
retention-days: 1
# Mac OS
.DS_Store
# Windows
thumbs.db
# NPM
/node_modules
npm-*
# Testing
/.nyc_output
/coverage
.eslintcache
# Build
/build
/dist
/.opt-in
/*.provisionprofile
# don't store the assets downloaded with the `fetch` script
/static
# Temporary resource
/external-resources
/tools
/firmwares
/drivers
# generated translation files
/translations
/locale
# Mac OS
.DS_Store
# NPM
/node_modules
npm-*
# Testing
/.nyc_output
/coverage
# Build
/.opt-in
# Temporary resource
/external-resources
/tools
/firmwares
/drivers
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Desktop",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder:scratch-desktop}",
"runtimeExecutable": "npm",
"autoAttachChildProcesses": true,
"runtimeArgs": ["start", "--"],
"protocol": "inspector",
"skipFiles": [
// it seems like skipFiles only reliably works with 1 entry :(
//"<node_internals>/**",
"${workspaceFolder:scratch-desktop}/node_modules/electron/dist/resources/*.asar/**"
],
"sourceMaps": true,
"timeout": 30000,
"outputCapture": "std"
}
]
}
This diff is collapsed. Click to expand it.
MIT License
Copyright (c) 2021 OpenBlock.cc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
Copyright (c) 2019, Scratch Foundation
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
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.
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.
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.
# openblock-desktop
## 安装依赖
1. 需要稳定的代理工具
2. ```javascript
//代理到工具指定端口
npm config set proxy=http://localhost:xxxx
//使用npm源 使用淘宝源 遇到了 包文件hash不一致的情况
npx nrm use npm
```
3. 至少需要python3.12的环境,原因`openblock-link`中的`@abandonware/bluetooth-hci-socket`的依赖库中指定了node-gyp的版本 `10.1.0`
![screenshot](./doc/screenshot.png)
![screenshot2](./doc/screenshot2.png)
## Getting Start
Visit the wiki: [https://openblockcc.github.io](https://openblockcc.github.io)
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.
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.
/ScratchDesktop.iconset
/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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
!include x64.nsh
!include LogicLib.nsh
!include StrFunc.nsh
${StrRep}
!macro preInit
${If} ${RunningX64}
SetRegView 64
${EndIf}
${StrRep} $0 "${UNINSTALL_REGISTRY_KEY}" "Software" "SOFTWARE"
${StrRep} $1 "${INSTALL_REGISTRY_KEY}" "Software" "SOFTWARE"
ReadRegStr $R0 HKCU "$0" "UninstallString"
ReadRegStr $R1 HKCU "$1" "InstallLocation"
StrCmp $R0 "" 0 +4
ReadRegStr $R0 HKLM "$0" "UninstallString"
ReadRegStr $R1 HKLM "$1" "InstallLocation"
StrCmp $R0 "" 0 done
StrCmp $R1 "" 0 done
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\OpenBlockDesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\OpenBlockDesktop"
done:
${If} ${RunningX64}
SetRegView LastUsed
${EndIf}
!macroend
!macro customUnInstall
${If} ${RunningX64}
SetRegView 64
${EndIf}
DeleteRegKey HKLM "${INSTALL_REGISTRY_KEY}"
DeleteRegKey HKCU "${INSTALL_REGISTRY_KEY}"
${If} ${RunningX64}
SetRegView LastUsed
${EndIf}
!macroend
#!/bin/bash
SRC=../src/icon/OpenBlockDesktop.svg
OUT_ICONSET=OpenBlockDesktop.iconset
OUT_ICNS=OpenBlockDesktop.icns
OUT_ICO=OpenBlockDesktop.ico
TMP_ICO=tmp
ICO_BASIC_SIZES="16 24 32 48 256"
ICO_EXTRA_SIZES="20 30 36 40 60 64 72 80 96 512"
if command -v pngcrush >/dev/null 2>&1; then
function optimize () {
pngcrush -new -brute -ow "$@"
}
else
echo "pngcrush is not available - skipping PNG optimization"
function optimize () {
echo "Not optimizing:" "$@"
}
fi
# usage: resize newWidth newHeight input output [otherOptions...]
function resize () {
WIDTH=$1
HEIGHT=$2
SRC=$3
DST=$4
shift 4
convert -background none -resize "${WIDTH}x${HEIGHT}" -extent "${WIDTH}x${HEIGHT}" -gravity center "$@" "${SRC}" "${DST}"
optimize "${DST}"
}
if command -v convert >/dev/null 2>&1; then
# Mac
if command -v iconutil >/dev/null 2>&1; then
mkdir -p "${OUT_ICONSET}"
for SIZE in 16 32 128 256 512; do
SIZE2=`expr "${SIZE}" '*' 2`
resize "${SIZE}" "${SIZE}" "${SRC}" "${OUT_ICONSET}/icon_${SIZE}x${SIZE}.png" -density 72 -units PixelsPerInch
resize "${SIZE2}" "${SIZE2}" "${SRC}" "${OUT_ICONSET}/icon_${SIZE}x${SIZE}@2x.png" -density 144 -units PixelsPerInch
done
iconutil -c icns --output "${OUT_ICNS}" "${OUT_ICONSET}"
else
echo "iconutil is not available - skipping ICNS and ICONSET"
fi
# Windows ICO
mkdir -p "${TMP_ICO}"
for SIZE in ${ICO_BASIC_SIZES} ${ICO_EXTRA_SIZES}; do
resize "${SIZE}" "${SIZE}" "${SRC}" "${TMP_ICO}/icon_${SIZE}x${SIZE}.png"
done
# Asking for "Zip" compression actually results in PNG compression
convert "${TMP_ICO}"/icon_*.png -colorspace sRGB -compress Zip "${OUT_ICO}"
# Windows AppX
mkdir -p "appx"
resize 44 44 "${SRC}" 'appx/Square44x44Logo.png'
resize 50 50 "${SRC}" 'appx/StoreLogo.png'
resize 150 150 "${SRC}" 'appx/Square150x150Logo.png'
resize 310 150 "${SRC}" 'appx/Wide310x150Logo.png'
else
echo "ImageMagick is not available - cannot convert icons"
fi
directories:
buildResources: buildResources
output: dist
extraFiles: ['LICENSE', 'LICENSE.ScratchFoundation', 'TRADEMARK', "tools", "external-resources", 'firmwares', "drivers"]
appId: openblock.cc.openblock-desktop
productName: "OpenBlockDesktop"
publish:
- provider: github
artifactName: "OpenBlock-Desktop_v${version}_${os}_${arch}.${ext}"
fileAssociations:
ext: ob
name: OpenBlock project file
role: Editor
icon: buildResources/OpenBlockFile.ico
mac:
category: public.app-category.education
entitlements: buildResources/entitlements.mac.plist
extendInfo:
NSCameraUsageDescription: >-
This app requires camera access when using the video sensing blocks.
NSMicrophoneUsageDescription: >-
This app requires microphone access when recording sounds or detecting loudness.
gatekeeperAssess: true
hardenedRuntime: true
icon: buildResources/OpenBlockDesktop.icns
provisioningProfile: embedded.provisionprofile
target:
- dmg
# - mas
dmg:
title: "OpenBlock-Desktop_${version}"
# mas:
# category: public.app-category.education
# entitlements: buildResources/entitlements.mas.plist
# entitlementsInherit: buildResources/entitlements.mas.inherit.plist
# hardenedRuntime: false
# icon: buildResources/OpenBlockDesktop.icns
# masDev:
# type: development
# provisioningProfile: mas-dev.provisionprofile
win:
icon: buildResources/OpenBlockDesktop.ico
target:
# - appx
- nsis
# appx:
# identityName: "OpenblockTeam.OpenblockDesktop
# publisherDisplayName: "OpenBlock Team"
# publisher: "CN=2EC43DF1-469A-4119-9AB9-568A0A1FF65F"
nsis:
oneClick: false # allow user to choose per-user or per-machine
allowToChangeInstallationDirectory: true
include: buildResources/installer.nsh
# license: LICENSE
linux:
category: Education
icon: buildResources/linux
desktop:
- Encoding: UTF-8
- Name: OpenBlockDesktop
- Icon: openblock-desktop
- Type: Application
- Terminal: false
target:
- deb
deb:
depends: ["libnotify4", "libxtst6", "libnss3"]
{
"main": {
"webpackConfig": "webpack.main.js"
},
"renderer": {
"template": "src/renderer/index.html",
"webpackConfig": "webpack.renderer.js"
}
}
This diff could not be displayed because it is too large.
{
"name": "openblock-desktop",
"productName": "OpenBlockDesktop",
"description": "OpenBlock as a self-contained desktop application",
"author": "Openblock.cc Team <contact@openblock.cc> (http://www.openblock.cc/)",
"version": "2.5.2",
"license": "MIT",
"scripts": {
"postinstall": "npx electron-builder install-app-deps",
"clean": "rimraf ./dist ./static ./external-resources ./tools ./translations ./firmwares ./drivers",
"i18n:src": "mkdirp translations/core && format-message extract --out-file translations/core/en.json src/main/**.js",
"i18n:push": "tx-push-src openblock-editor desktop translations/core/en.json",
"start": "mkdirp ./dist && electron-webpack dev --bail --display-error-details --env.minify=false --no-progress",
"compile": "mkdirp ./dist && electron-webpack --bail --display-error-details --env.minify=false --no-progress",
"fetch:drivers": "rimraf ./drivers && node scripts/download-driver.js",
"fetch:exts": "rimraf ./external-resources && node ./node_modules/openblock-resource/script/download.js --repo=openblockcc/external-resources-v3",
"fetch:firmwares": "rimraf ./firmwares && node ./node_modules/openblock-link/script/download-firmwares.js",
"fetch:tools": "rimraf ./tools && node ./node_modules/openblock-link/script/download-tools.js",
"fetch:static": "rimraf ./static && mkdirp ./static && git clone https://github.com/openblockcc/openblock-assets.git static && rimraf ./static/.git",
"fetch:all": "npm run fetch:drivers && npm run fetch:exts && npm run fetch:firmwares && npm run fetch:tools && npm run fetch:static",
"build": "npm run build:dev",
"build:dev": "npm run compile && npm run doBuild -- --mode=dev",
"build:dir": "npm run compile && npm run doBuild -- --mode=dir",
"build:dist": "npm run compile && npm run doBuild -- --mode=dist",
"build:publish": "npm run compile && npm run doBuild -- --mode=publish",
"doBuild": "node ./scripts/electron-builder-wrapper.js",
"dist": "npm run clean && npm run compile && npm run fetch:all && npm run doBuild -- --mode=dist",
"publish": "npm run clean && npm run compile && npm run fetch:all && npm run doBuild -- --mode=publish",
"test": "npm run test:lint",
"test:lint": "eslint --cache --color --ext .jsx,.js src scripts"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/openblockcc/openblock-desktop.git"
},
"dependencies": {
"bytes": "^3.1.2",
"openblock-link": "^0.2.0-prerelease.20240710015813",
"openblock-resource": "^0.2.0-prerelease.20240708150142",
"postinstall": "^0.7.4",
"source-map-support": "^0.5.19"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.53.1",
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-object-rest-spread": "^7.9.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-async-to-generator": "^7.8.3",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"@electron/remote": "^2.0.5",
"async": "^3.2.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-react-intl": "^7.5.7",
"classnames": "^2.3.1",
"copy-webpack-plugin": "^5.1.1",
"download-github-release": "^0.3.2",
"electron": "^15.3.1",
"electron-builder": "^22.14.13",
"electron-devtools-installer": "^3.2.0",
"electron-fetch": "^1.7.4",
"electron-log": "^4.4.6",
"electron-notarize": "^1.1.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.6.5",
"electron-webpack": "^2.8.2",
"eslint": "^7.0.0",
"eslint-config-scratch": "^6.0.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react": "^7.20.0",
"format-message": "^6.2.3",
"format-message-cli": "^6.2.3",
"fs-extra": "^11.1.0",
"intl": "1.2.5",
"lodash.bindall": "^4.4.0",
"lodash.defaultsdeep": "^4.6.1",
"minilog": "^3.1.0",
"minimist": "^1.2.5",
"mkdirp": "^1.0.4",
"monaco-editor-webpack-plugin": "^1.7.0",
"nets": "^3.2.0",
"node-abort-controller": "^3.0.1",
"openblock-gui": "github:openblockcc/openblock-gui#openblock-desktop-v2.5.2",
"openblock-l10n": "^3.15.20240704111820",
"openblock-parse-release-message": "0.0.4",
"os-locale": "^5.0.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-intl": "2.9.0",
"react-redux": "5.0.7",
"redux": "3.7.2",
"rimraf": "^3.0.2",
"sudo-prompt": "^9.2.1",
"uuid": "^8.3.2",
"webpack": "^4.43.0"
}
}
module.exports = {
rules: {
'no-console': 'off'
}
};
const {notarize} = require('electron-notarize');
const notarizeMacBuild = async function (context) {
// keep this in sync with appId in the electron-builder config
const appId = 'edu.mit.scratch.scratch-desktop';
if (!process.env.AC_USERNAME) {
console.error([
'This build is not notarized and will not run on newer versions of macOS!',
'Notarizing the macOS build requires an Apple ID. To notarize future builds:',
'* Set the environment variable AC_USERNAME to your@apple.id and',
'* Either set AC_PASSWORD or ensure your keychain has an item for "Application Loader: your@apple.id"'
].join('\n'));
return;
}
const appleId = process.env.AC_USERNAME;
const appleIdKeychainItem = `Application Loader: ${appleId}`;
if (process.env.AC_PASSWORD) {
console.log(`Notarizing with Apple ID "${appleId}" and a password`);
} else {
console.log(`Notarizing with Apple ID "${appleId}" and keychain item "${appleIdKeychainItem}"`);
}
const {appOutDir} = context;
const productFilename = context.packager.appInfo.productFilename;
await notarize({
appBundleId: appId,
appPath: `${appOutDir}/${productFilename}.app`,
appleId,
appleIdPassword: process.env.AC_PASSWORD || `@keychain:${appleIdKeychainItem}`
});
};
const afterSign = async function (context) {
const {electronPlatformName} = context;
switch (electronPlatformName) {
case 'mas': // macOS build for Mac App Store
break;
case 'darwin': // macOS build NOT for Mac App Store
await notarizeMacBuild(context);
break;
}
};
module.exports = afterSign;
/* eslint-disable */
const downloadRelease = require('download-github-release');
const path = require('path');
const os = require('os');
const fs = require('fs');
const user = 'openblockcc';
const repo = 'openblock-driver';
const outputdir = path.join(__dirname, '../drivers');
const leaveZipped = false;
function filterRelease (release) {
return release.prerelease === false;
}
function filterAsset(asset) {
return (asset.name.indexOf(os.platform()) >= 0);
}
if (!fs.existsSync(outputdir)) {
fs.mkdirSync(outputdir, {recursive: true});
}
downloadRelease(user, repo, outputdir, filterRelease, filterAsset, leaveZipped)
.then(() => {
console.log('Tools download complete');
})
.catch(err => {
console.error(err.message);
});
/**
* @overview This script runs `electron-builder` with special management of code signing configuration on Windows.
* Running this script with no command line parameters should build all targets for the current platform.
* On Windows, make sure to set CSC_* or WIN_CSC_* environment variables or the NSIS build will fail.
* On Mac, the CSC_* variables are optional but will be respected if present.
* See also: https://www.electron.build/code-signing
*/
const {spawnSync} = require('child_process');
const fs = require('fs');
/**
* Strip any code signing configuration (CSC) from a set of environment variables.
* @param {object} environment - a collection of environment variables which might include code signing configuration.
* @returns {object} - a collection of environment variables which does not include code signing configuration.
*/
const stripCSC = function (environment) {
const {
CSC_LINK: _CSC_LINK,
CSC_KEY_PASSWORD: _CSC_KEY_PASSWORD,
WIN_CSC_LINK: _WIN_CSC_LINK,
WIN_CSC_KEY_PASSWORD: _WIN_CSC_KEY_PASSWORD,
...strippedEnvironment
} = environment;
return strippedEnvironment;
};
/**
* @returns {string} - an `electron-builder` flag to build for the current platform, based on `process.platform`.
*/
const getPlatformFlag = function () {
switch (process.platform) {
case 'win32': return '--windows';
case 'darwin': return '--macos';
case 'linux': return '--linux';
}
throw new Error(`Could not determine platform flag for platform: ${process.platform}`);
};
/**
* Run `electron-builder` once to build one or more target(s).
* @param {object} wrapperConfig - overall configuration object for the wrapper script.
* @param {object} target - the target to build in this call.
* If the `target.name` is `'nsis'` then the environment must contain code-signing config (CSC_* or WIN_CSC_*).
* If the `target.name` is `'appx'` then code-signing config will be stripped from the environment if present.
*/
const runBuilder = function (wrapperConfig, target) {
// the AppX build fails if CSC_* or WIN_CSC_* variables are set
const shouldStripCSC = (target.name.indexOf('appx') === 0) || (!wrapperConfig.doSign);
const childEnvironment = shouldStripCSC ? stripCSC(process.env) : process.env;
if (wrapperConfig.doSign &&
(target.name.indexOf('nsis') === 0) &&
!(childEnvironment.CSC_LINK || childEnvironment.WIN_CSC_LINK)) {
// throw new Error(`Signing NSIS build requires CSC_LINK or WIN_CSC_LINK`);
}
const platformFlag = getPlatformFlag();
let allArgs = [platformFlag, target.name];
if (target.platform === 'darwin') {
allArgs.push(`--c.mac.type=${wrapperConfig.mode === 'dist' ? 'distribution' : 'development'}`);
if (target.name === 'mas-dev') {
allArgs.push('--c.mac.provisioningProfile=mas-dev.provisionprofile');
}
if (wrapperConfig.doSign) {
// really this is "notarize only if we also sign"
allArgs.push('--c.afterSign=scripts/afterSign.js');
} else {
allArgs.push('--c.mac.identity=null');
}
}
if (target.platform === 'win32' && wrapperConfig.mode !== 'dev') {
allArgs.push('--ia32', '--x64');
}
if (!wrapperConfig.doPackage) {
allArgs.push('--dir', '--c.compression=store');
}
if (wrapperConfig.doPublish) {
allArgs.push('--publish', 'always');
} else {
// Prevent electron build from automatically publishing in github action
allArgs.push('--publish', 'never');
}
allArgs = allArgs.concat(wrapperConfig.builderArgs);
console.log(`running electron-builder with arguments: ${allArgs}`);
const result = spawnSync('electron-builder', allArgs, {
env: childEnvironment,
shell: true,
stdio: 'inherit'
});
if (result.error) {
throw result.error;
}
if (result.signal) {
throw new Error(`Child process terminated due to signal ${result.signal}`);
}
if (result.status) {
throw new Error(`Child process returned status code ${result.status}`);
}
};
/**
* @param {object} wrapperConfig - overall configuration object for the wrapper script.
* @returns {Array.<object>} - the default list of targets on this platform. Each item in the array represents one
* call to `runBuilder` for exactly one build target. In theory electron-builder can build two or more targets at the
* same time but doing so limits has unwanted side effects on both macOS and Windows (see function body).
*/
const calculateTargets = function (wrapperConfig) {
const masDevProfile = 'mas-dev.provisionprofile';
const availableTargets = {
macAppStore: {
name: 'mas',
platform: 'darwin'
},
macAppStoreDev: {
name: 'mas-dev',
platform: 'darwin'
},
macDirectDownload: {
name: 'dmg',
platform: 'darwin'
},
// microsoftStore: {
// name: 'appx:ia32 appx:x64',
// platform: 'win32'
// },
windowsDirectDownload: {
name: 'nsis',
platform: 'win32'
},
linuxDirectDownload: {
name: 'deb',
platform: 'linux'
}
};
const targets = [];
console.log(process.platform);
switch (process.platform) {
case 'win32':
// Run in two passes so we can skip signing the AppX for distribution through the MS Store.
// targets.push(availableTargets.microsoftStore);
targets.push(availableTargets.windowsDirectDownload);
break;
case 'darwin':
// Running 'dmg' and 'mas' in the same pass causes electron-builder to skip signing the non-MAS app copy.
// Running them as separate passes means they can both get signed.
// Seems like a bug in electron-builder...
// Running the 'mas' build first means that its output is available while we wait for 'dmg' notarization.
// Add macAppStoreDev here to test a MAS-like build locally. You'll need a Mac Developer provisioning profile.
if (fs.existsSync(masDevProfile)) {
targets.push(availableTargets.macAppStoreDev);
} else {
console.log(`skipping target "${availableTargets.macAppStoreDev.name}": ${masDevProfile} missing`);
}
if (wrapperConfig.doSign) {
targets.push(availableTargets.macAppStore);
} else {
// electron-builder doesn't seem to support this configuration even if mac.type is "development"
console.log(`skipping target "${availableTargets.macAppStore.name}" because code-signing is disabled`);
}
targets.push(availableTargets.macDirectDownload);
break;
case 'linux':
targets.push(availableTargets.linuxDirectDownload);
break;
default:
throw new Error(`Could not determine targets for platform: ${process.platform}`);
}
return targets;
};
const parseArgs = function () {
const scriptArgs = process.argv.slice(2); // remove `node` and `this-script.js`
console.log(scriptArgs);
const builderArgs = [];
let mode = 'dev'; // default
let arch = null;
for (const arg of scriptArgs) {
const modeSplit = arg.split(/--mode(\s+|=)/);
const archSplit = arg.split(/--arch(\s+|=)/);
if (modeSplit.length === 3) {
mode = modeSplit[2];
} else if (archSplit.length === 3) {
arch = archSplit[2];
} else {
builderArgs.push(arg);
}
}
let doPackage;
let doSign;
let doPublish;
switch (mode) {
case 'dev':
doPackage = true;
doSign = false;
doPublish = false;
break;
case 'dir':
doPackage = false;
doSign = false;
doPublish = false;
break;
case 'dist':
doPackage = true;
// doSign = true; // skip code signing before getting a certificate
doSign = false;
doPublish = false;
break;
case 'publish':
doPackage = true;
// doSign = true; // skip code signing before getting a certificate
doSign = false;
doPublish = true;
break;
}
return {
builderArgs,
doPackage, // false = build to directory
doSign,
doPublish,
mode,
arch
};
};
const main = function () {
const wrapperConfig = parseArgs();
console.log(wrapperConfig);
// TODO: allow user to specify targets? We could theoretically build NSIS on Mac, for example.
wrapperConfig.targets = calculateTargets(wrapperConfig);
for (const target of wrapperConfig.targets) {
runBuilder(wrapperConfig, target);
}
};
main();
const fs = require('fs');
const https = require('https');
const path = require('path');
const util = require('util');
const async = require('async');
const libraries = require('./lib/libraries');
const ASSET_HOST = 'cdn.assets.scratch.mit.edu';
const NUM_SIMULTANEOUS_DOWNLOADS = 5;
const OUT_PATH = path.resolve('static', 'assets');
const describe = function (object) {
return util.inspect(object, false, Infinity, true);
};
const collectSimple = function (library, dest, debugLabel = 'Item') {
library.forEach(item => {
let md5Count = 0;
if (item.md5) {
++md5Count;
dest.add(item.md5);
}
if (item.baseLayerMD5) { // 2.0 library syntax for costumes
++md5Count;
dest.add(item.baseLayerMD5);
}
if (item.md5ext) { // 3.0 library syntax for costumes
++md5Count;
dest.add(item.md5ext);
}
if (md5Count < 1) {
console.warn(`${debugLabel} has no MD5 property:\n${describe(item)}`);
} else if (md5Count > 1) {
// is this actually bad?
console.warn(`${debugLabel} has multiple MD5 properties:\n${describe(item)}`);
}
});
return dest;
};
const collectAssets = function (dest) {
collectSimple(libraries.backdrops, dest, 'Backdrop');
collectSimple(libraries.costumes, dest, 'Costume');
collectSimple(libraries.sounds, dest, 'Sound');
libraries.sprites.forEach(sprite => {
if (sprite.costumes) {
collectSimple(sprite.costumes, dest, `Costume for sprite ${sprite.name}`);
}
if (sprite.sounds) {
collectSimple(sprite.sounds, dest, `Sound for sprite ${sprite.name}`);
}
});
return dest;
};
const connectionPool = [];
const fetchAsset = function (md5, callback) {
const myAgent = connectionPool.pop() || new https.Agent({keepAlive: true});
const getOptions = {
host: ASSET_HOST,
path: `/internalapi/asset/${md5}/get/`,
agent: myAgent
};
const urlHuman = `//${getOptions.host}${getOptions.path}`;
https.get(getOptions, response => {
if (response.statusCode !== 200) {
callback(new Error(`Request failed: status code ${response.statusCode} for ${urlHuman}`));
return;
}
const stream = fs.createWriteStream(path.resolve(OUT_PATH, md5), {encoding: 'binary'});
stream.on('error', callback);
response.on('data', chunk => {
stream.write(chunk);
});
response.on('end', () => {
connectionPool.push(myAgent);
stream.end();
console.log(`Fetched ${urlHuman}`);
callback();
});
});
};
const fetchAllAssets = function () {
const allAssets = collectAssets(new Set());
console.log(`Total library assets: ${allAssets.size}`);
async.forEachLimit(allAssets, NUM_SIMULTANEOUS_DOWNLOADS, fetchAsset, err => {
if (err) {
console.error(`Fetch failed:\n${describe(err)}`);
} else {
console.log('Fetch succeeded.');
}
console.log(`Shutting down ${connectionPool.length} agents.`);
while (connectionPool.length > 0) {
connectionPool.pop().destroy();
}
});
};
fetchAllAssets();
const backdrops = require('openblock-gui/src/lib/libraries/backdrops.json');
const costumes = require('openblock-gui/src/lib/libraries/costumes.json');
const sounds = require('openblock-gui/src/lib/libraries/sounds.json');
const sprites = require('openblock-gui/src/lib/libraries/sprites.json');
const libraries = {
backdrops,
costumes,
sounds,
sprites
};
module.exports = libraries;
module.exports = {
root: true,
env: {
node: true
},
extends: ['scratch', 'scratch/es6'],
globals: {
__static: false // electron-webpack provides this constant to access bundled static assets
}
};
const fs = require('fs');
const path = require('path');
const staticAssets = path.resolve(__static, 'assets');
/**
* Allow the storage module to load files bundled in the Electron application.
*/
class ElectronStorageHelper {
constructor (storageInstance) {
this.parent = storageInstance;
}
/**
* Fetch an asset but don't process dependencies.
* @param {AssetType} assetType - The type of asset to fetch.
* @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc.
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
* @return {Promise.<Asset>} A promise for the contents of the asset.
*/
load (assetType, assetId, dataFormat) {
assetId = path.basename(assetId);
dataFormat = path.basename(dataFormat);
return new Promise((resolve, reject) => {
fs.readFile(
path.resolve(staticAssets, `${assetId}.${dataFormat}`),
(err, data) => {
if (err) {
reject(err);
} else {
resolve(new this.parent.Asset(assetType, assetId, dataFormat, data));
}
}
);
});
}
}
module.exports = ElectronStorageHelper;
import minilog from 'minilog';
minilog.enable();
const namespace = (() => {
switch (process.type) {
case 'browser': return 'main';
case 'renderer': return 'web';
default: return process.type; // probably 'worker' for a web worker
}
})();
export default minilog(`app-${namespace}`);
<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
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
module.exports = {
root: true,
env: {
node: true
},
extends: ['scratch', 'scratch/es6', 'scratch/node']
};
const saveFilters = {
JPEG: {
name: 'JPEG Image',
extensions: ['jpg', 'jpeg']
},
MP3: {
name: 'MP3 Sound',
extensions: ['mp3']
},
PNG: {
name: 'PNG Image',
extensions: ['png']
},
SB: {
name: 'Scratch 1 Project',
extensions: ['sb']
},
SB2: {
name: 'Scratch 2 Project',
extensions: ['sb2']
},
SB3: {
name: 'Scratch 3 Project',
extensions: ['sb3']
},
OB: {
name: 'OpenBlock Project',
extensions: ['ob']
},
Sprite2: {
name: 'Scratch 2 Sprite',
extensions: ['sprite2']
},
Sprite3: {
name: 'Scratch 3 Sprite',
extensions: ['sprite3']
},
SVG: {
name: 'SVG Image',
extensions: ['svg']
},
WAV: {
name: 'WAV Sound',
extensions: ['wav']
}
};
const loadFilters = {
...saveFilters,
AllBitmaps: {
name: 'All Bitmaps',
extensions: [
...saveFilters.JPEG.extensions,
...saveFilters.PNG.extensions
]
},
AllImages: {
name: 'All Images',
extensions: [
...saveFilters.JPEG.extensions,
...saveFilters.PNG.extensions,
...saveFilters.SVG.extensions
]
},
AllProjects: {
name: 'All OpenBlock Projects',
extensions: [
...saveFilters.SB3.extensions,
...saveFilters.SB2.extensions,
...saveFilters.SB.extensions,
...saveFilters.OB.extensions
]
},
AllSounds: {
name: 'All Sounds',
extensions: [
...saveFilters.MP3.extensions,
...saveFilters.WAV.extensions
]
},
AllSprites: {
name: 'All Sprites',
extensions: [
...saveFilters.Sprite3.extensions,
...saveFilters.Sprite2.extensions
]
}
};
const filtersByExtension = Object.values(saveFilters).reduce((result, filter) => {
for (const extension of filter.extensions) {
result[extension] = filter;
}
return result;
}, {});
const getFilterForExtension = extNameNoDot =>
filtersByExtension[extNameNoDot] || {
name: `${extNameNoDot.toUpperCase()} Files`,
extensions: [extNameNoDot]
};
export {
saveFilters,
loadFilters,
getFilterForExtension
};
// Include the standard keyboard shortcuts in the edit menu
// so they can be used within the app. Only needed on Mac.
export default app => ([
{
label: 'App', // Always overridden by app name
submenu: [{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: () => app.quit()
}]
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo'
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut'
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectall'
}
]
}
]);
import {app} from 'electron';
import path from 'path';
import os from 'os';
import {execFile, spawn} from 'child_process';
import fs from 'fs-extra';
import sudo from 'sudo-prompt';
import {productName} from '../../package.json';
import OpenBlockLink from 'openblock-link';
import OpenblockResourceServer from 'openblock-resource';
class OpenblockDesktopLink {
constructor () {
this._resourceServer = null;
this.appPath = app.getAppPath();
if (this.appPath.search(/app/g) !== -1) {
// Normal app
this.appPath = path.join(this.appPath, '../../');
} else if (this.appPath.search(/main/g) !== -1) { // eslint-disable-line no-negated-condition
// Start by start script in debug mode.
this.appPath = path.join(this.appPath, '../../');
} else {
// App in dir mode
this.appPath = path.join(this.appPath, '../');
}
const userDataPath = app.getPath(
'userData'
);
this.dataPath = path.join(userDataPath, 'Data');
this._link = new OpenBlockLink(this.dataPath, path.join(this.appPath, 'tools'));
this._resourceServer = new OpenblockResourceServer(this.dataPath,
path.join(this.appPath, 'external-resources'),
app.getLocaleCountryCode());
}
get resourceServer () {
return this._resourceServer;
}
installDriver (callback = null) {
const driverPath = path.join(this.appPath, 'drivers');
if ((os.platform() === 'win32') && (os.arch() === 'x64')) {
execFile('install_x64.bat', [], {cwd: driverPath});
} else if ((os.platform() === 'win32') && (os.arch() === 'ia32')) {
execFile('install_x86.bat', [], {cwd: driverPath});
} else if ((os.platform() === 'darwin')) {
spawn('sh', ['install.sh'], {shell: true, cwd: driverPath});
} else if ((os.platform() === 'linux')) {
sudo.exec(`sh ${path.join(driverPath, 'linux_setup.sh')} yang`, {name: productName},
error => {
if (error) throw error;
if (callback) {
callback();
}
}
);
}
}
clearCache (reboot = true) {
if (fs.existsSync(this.dataPath)) {
fs.rmSync(this.dataPath, {recursive: true, force: true});
}
if (reboot){
app.relaunch();
app.exit();
}
}
start () {
this._link.listen();
// start resource server
this._resourceServer.listen();
}
}
export default OpenblockDesktopLink;
import {app, ipcMain} from 'electron';
import defaultsDeep from 'lodash.defaultsdeep';
import {version} from '../../package.json';
import TelemetryClient from './telemetry/TelemetryClient';
const EVENT_TEMPLATE = {
version,
projectName: '',
language: '',
metadata: {
scriptCount: -1,
spriteCount: -1,
variablesCount: -1,
blocksCount: -1,
costumesCount: -1,
listsCount: -1,
soundsCount: -1
}
};
const APP_ID = 'openblock-desktop';
const APP_VERSION = app.getVersion();
const APP_INFO = Object.freeze({
projectName: `${APP_ID} ${APP_VERSION}`
});
class ScratchDesktopTelemetry {
constructor () {
this._telemetryClient = new TelemetryClient();
}
get didOptIn () {
return this._telemetryClient.didOptIn;
}
set didOptIn (value) {
this._telemetryClient.didOptIn = value;
}
appWasOpened () {
this._telemetryClient.addEvent('app::open', {...EVENT_TEMPLATE, ...APP_INFO});
}
appWillClose () {
this._telemetryClient.addEvent('app::close', {...EVENT_TEMPLATE, ...APP_INFO});
}
projectDidLoad (metadata = {}) {
this._telemetryClient.addEvent('project::load', this._buildMetadata(metadata));
}
projectDidSave (metadata = {}) {
// Since the save dialog appears on the main process the GUI does not wait for the actual save to complete.
// That means the GUI sends this event before we know the file name used for the save, which is where the new
// project title comes from. Instead, just hold on to this metadata pending a `projectSaveCompleted` event
// from the save code on the main process. If the user cancels the save this data will be cleared.
this._pendingProjectSave = metadata;
}
projectSaveCompleted (newProjectTitle) {
const metadata = this._pendingProjectSave;
this._pendingProjectSave = null;
metadata.projectName = newProjectTitle;
this._telemetryClient.addEvent('project::save', this._buildMetadata(metadata));
}
projectSaveCanceled () {
this._pendingProjectSave = null;
}
projectWasCreated (metadata = {}) {
this._telemetryClient.addEvent('project::create', this._buildMetadata(metadata));
}
projectWasUploaded (metadata = {}) {
this._telemetryClient.addEvent('project::upload', this._buildMetadata(metadata));
}
_buildMetadata (metadata) {
const {projectName, language, ...codeMetadata} = metadata;
return defaultsDeep({
projectName,
language,
metadata: codeMetadata
}, EVENT_TEMPLATE);
}
}
// make a singleton so it's easy to share across both Electron processes
const scratchDesktopTelemetrySingleton = new ScratchDesktopTelemetry();
// `handle` works with `invoke`
ipcMain.handle('getTelemetryDidOptIn', () =>
scratchDesktopTelemetrySingleton.didOptIn
);
// `on` works with `sendSync` (and `send`)
ipcMain.on('getTelemetryDidOptIn', event => {
event.returnValue = scratchDesktopTelemetrySingleton.didOptIn;
});
ipcMain.on('setTelemetryDidOptIn', (event, arg) => {
scratchDesktopTelemetrySingleton.didOptIn = arg;
});
ipcMain.on('projectDidLoad', (event, arg) => {
scratchDesktopTelemetrySingleton.projectDidLoad(arg);
});
ipcMain.on('projectDidSave', (event, arg) => {
scratchDesktopTelemetrySingleton.projectDidSave(arg);
});
ipcMain.on('projectWasCreated', (event, arg) => {
scratchDesktopTelemetrySingleton.projectWasCreated(arg);
});
ipcMain.on('projectWasUploaded', (event, arg) => {
scratchDesktopTelemetrySingleton.projectWasUploaded(arg);
});
export default scratchDesktopTelemetrySingleton;
import minimist from 'minimist';
// inspired by yargs' process-argv
export const isElectronApp = () => !!process.versions.electron;
export const isElectronBundledApp = () => isElectronApp() && !process.defaultApp;
export const parseAndTrimArgs = argv => {
// bundled Electron app: ignore 1 from "my-app arg1 arg2"
// unbundled Electron app: ignore 2 from "electron main/index.js arg1 arg2"
// node.js app: ignore 2 from "node src/index.js arg1 arg2"
const ignoreCount = isElectronBundledApp() ? 1 : 2;
const parsed = minimist(argv);
// ignore arguments AFTER parsing to handle cases like "electron --inspect=42 my.js arg1 arg2"
parsed._ = parsed._.slice(ignoreCount);
return parsed;
};
const argv = parseAndTrimArgs(process.argv);
export default argv;
module.exports = {
root: true,
env: {
browser: true,
node: true
},
extends: ['scratch', 'scratch/es6', 'scratch/react'],
settings: {
react: {
version: '16.2' // Prevent 16.3 lifecycle method errors
}
}
};
import {ipcRenderer} from 'electron';
import bindAll from 'lodash.bindall';
import React from 'react';
/**
* Higher-order component to add desktop logic to AppStateHOC.
* @param {Component} WrappedComponent - an AppStateHOC-like component to wrap.
* @returns {Component} - a component similar to AppStateHOC with desktop-specific logic added.
*/
const ScratchDesktopAppStateHOC = function (WrappedComponent) {
class ScratchDesktopAppStateComponent extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleTelemetryModalOptIn',
'handleTelemetryModalOptOut'
]);
this.state = {
// use `sendSync` because this should be set before first render
telemetryDidOptIn: ipcRenderer.sendSync('getTelemetryDidOptIn')
};
}
handleTelemetryModalOptIn () {
ipcRenderer.send('setTelemetryDidOptIn', true);
ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => {
this.setState({telemetryDidOptIn});
});
}
handleTelemetryModalOptOut () {
ipcRenderer.send('setTelemetryDidOptIn', false);
ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => {
this.setState({telemetryDidOptIn});
});
}
render () {
const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean');
return (<WrappedComponent
isTelemetryEnabled={this.state.telemetryDidOptIn}
onTelemetryModalOptIn={this.handleTelemetryModalOptIn}
onTelemetryModalOptOut={this.handleTelemetryModalOptOut}
showTelemetryModal={shouldShowTelemetryModal}
// allow passed-in props to override any of the above
{...this.props}
/>);
}
}
return ScratchDesktopAppStateComponent;
};
export default ScratchDesktopAppStateHOC;
html, body {
background-color: #4D97FF;
color: white;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: bolder;
}
a:active, a:hover, a:link, a:visited {
color: currentColor;
}
a:active, a:hover {
filter: brightness(0.9);
}
.aboutBox {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align:center;
}
.aboutLogo {
max-width: 10rem;
max-height: 10rem;
margin: 1.5rem 1.5rem 0 1.5rem;
}
.aboutText {
margin: 1.5rem;
}
.aboutDetails {
font-size: x-small;
text-align:left;
margin:auto;
}
.aboutFooter {
font-size: small;
}
import React from 'react';
import {productName, version} from '../../package.json';
import logo from '../icon/OpenBlockDesktop.svg';
import styles from './about.css';
const AboutElement = () => (
<div className={styles.aboutBox}>
<div><img
alt={`${productName} icon`}
src={logo}
className={styles.aboutLogo}
/></div>
<div className={styles.aboutText}>
<h2>{productName}</h2>
Version {version}
<table className={styles.aboutDetails}><tbody>
{
['Electron', 'Chrome', 'Node'].map(component => {
const componentVersion = process.versions[component.toLowerCase()];
return <tr key={component}><td>{component}</td><td>{componentVersion}</td></tr>;
})
}
</tbody></table>
</div>
</div>
);
export default <AboutElement />;
/* Adapted from scratch-gui/src/playground/index.css */
html,
body,
.app {
width: 100%;
height: 100%;
margin: 0;
/* Setting min height/width makes the UI scroll below those sizes */
min-width: 1024px;
min-height: 640px; /* Min height to fit sprite/backdrop button */
}
import React from 'react';
import {compose} from 'redux';
import GUI from 'openblock-gui/src/index';
import AppStateHOC from 'openblock-gui/src/lib/app-state-hoc.jsx';
import ScratchDesktopAppStateHOC from './ScratchDesktopAppStateHOC.jsx';
import ScratchDesktopGUIHOC from './ScratchDesktopGUIHOC.jsx';
import styles from './app.css';
const appTarget = document.getElementById('app');
appTarget.className = styles.app || 'app';
GUI.setAppElement(appTarget);
// note that redux's 'compose' function is just being used as a general utility to make
// the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's
// ability to compose reducers.
const WrappedGui = compose(
ScratchDesktopAppStateHOC,
AppStateHOC,
ScratchDesktopGUIHOC
)(GUI);
export default <WrappedGui />;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: #4D97FF;
}
.splash {
color: white;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: xx-large;
font-weight: bolder;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div id="app"><p class="splash">OpenBlock is loading...</p></div>
</body>
</html>
// This file does async imports of the heavy JSX, especially app.jsx, to avoid blocking the first render.
// The main index.html just contains a loading/splash screen which will display while this import loads.
import {ipcRenderer} from 'electron';
import ReactDOM from 'react-dom';
ipcRenderer.on('ready-to-show', () => {
// Start without any element in focus, otherwise the first link starts with focus and shows an orange box.
// We shouldn't disable that box or the focus behavior in case someone wants or needs to navigate that way.
// This seems like a hack... maybe there's some better way to do avoid any element starting with focus?
document.activeElement.blur();
});
const route = new URLSearchParams(window.location.search).get('route') || 'app';
let routeModulePromise;
switch (route) {
case 'loading':
routeModulePromise = import('./loading.jsx');
break;
case 'app':
routeModulePromise = import('./app.jsx');
break;
case 'about':
routeModulePromise = import('./about.jsx');
break;
case 'license':
routeModulePromise = import('./license.jsx');
break;
case 'privacy':
routeModulePromise = import('./privacy.jsx');
break;
}
routeModulePromise.then(routeModule => {
const appTarget = document.getElementById('app');
const routeElement = routeModule.default;
ReactDOM.render(routeElement, appTarget);
});
html, body {
background-color: #4D97FF;
color: white;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
line-height: 150%;
}
a:active, a:hover, a:link, a:visited {
color: currentColor;
}
a:active, a:hover {
filter: brightness(0.9);
}
.licenseBox {
margin: 3rem;
position: absolute;
}
.aboutFooter {
font-size: small;
}
.tabList {
height: 32px;
width: 250px; /* Match width of the toolbox */
display: flex;
align-items: flex-end;
flex-shrink: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 500;
font-size: 0.80rem;
/* Overrides for react-tabs styling */
margin: 0 !important;
border-bottom: 0 !important;
}
.tab {
flex-grow: 1;
height: 80%;
margin-bottom: 0;
border-radius: 0.5rem 0.5rem 0 0;
border: 1px solid hsla(0, 0%, 0%, 0.15);
padding: 0.125rem 1.25rem 0;
font-size: 0.75rem;
background-color: rgb(219, 219, 219);
color: #575e75;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
white-space: nowrap;
}
.tab {
margin-left: -0.5rem;
}
.tab:nth-of-type(1) {
margin-left: 0;
}
/* Use z-indices to force left-on-top for tabs */
.tab:nth-of-type(1) {
z-index: 3;
}
.tab:nth-of-type(2) {
z-index: 2;
}
.tab:nth-of-type(3) {
z-index: 1;
}
.tab:hover {
background-color: rgb(235, 235, 235);
}
.tab.isSelected {
height: 90%;
background-color: white;
color: #4D97FF;
border-bottom: none;
z-index: 4;
}
.tab.active {
background-color: hsla(215, 100%, 65%, 0.15);
}
.tabs {
position: relative;
flex-grow: 1;
display: flex;
flex-direction: column;
border: 1px solid #D9D9D9;
border-top: none;
}
.tabPanel {
position: relative;
flex-grow: 1;
display: none;
background-color: white;
color: #575e75;
padding: 2rem 3rem;
}
.tabPanel h4{
margin: 0.2rem 0;
}
.tabPanel.isSelected {
display: flex;
flex-direction: column;
}
.logo {
height: 80px;
margin: 3rem auto 3rem;
}
.logo:hover {
cursor: pointer;
}
/* eslint-disable max-len */
import React from 'react';
import styles from './license.css';
import bindAll from 'lodash.bindall';
import classNames from 'classnames';
import OpenBlockLogo from '../icon/logo-OpenBlockcc.svg';
import ScratchFoundationLogo from '../icon/logo-ScratchFoundation.svg';
// Insert new copyright information at the head of the array to add a new copyright notice
const copyrightInformations = [
{
id: 'OpenBlock.cc',
logo: OpenBlockLogo,
link: 'https://www.openblock.cc/',
license: 'MIT'
},
{
id: 'Scratch Foundation',
link: 'https://www.scratchfoundation.org/',
logo: ScratchFoundationLogo,
license: 'BSD-3-Clause'
}
];
const licenseContent = {
'MIT': (
<div className={styles.licenseContent}>
<p>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the &quot;Software&quot;), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
</p>
<p>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
</p>
<p>
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</p>
</div>
),
'BSD-3-Clause': (
<div className={styles.licenseContent}>
<p>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
</p>
<p>
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; 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.
</p>
</div>
)
};
class LicenseElement extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleClickTab'
]);
this.state = {
selectedTab: copyrightInformations[0].id
};
}
handleClickLogo (e) {
copyrightInformations.forEach(item => {
if (item.id === e.currentTarget.alt) {
window.open(item.link);
}
});
}
handleClickTab (e) {
this.setState({selectedTab: e.currentTarget.id});
}
buildLicenseTabList () {
return copyrightInformations.map(item => (
<button
key={item.id}
id={item.id}
className={classNames(styles.tab, {
[styles.isSelected]: this.state.selectedTab === item.id
})}
onClick={this.handleClickTab}
>
{item.id}
</button>
));
}
buildLicenseContent () {
return copyrightInformations.map(item => (
<div
key={item.id}
className={classNames(styles.tabPanel, {
[styles.isSelected]: this.state.selectedTab === item.id
})}
>
<img
alt={item.id}
className={styles.logo}
draggable={false}
src={item.logo}
onClick={this.handleClickLogo}
/>
<h4>{item.license} License</h4>
<h4>Copyright &copy; {item.id}</h4>
{licenseContent[item.license]}
</div>
));
}
render () {
const tabList = this.buildLicenseTabList();
const content = this.buildLicenseContent();
return (
<div className={styles.licenseBox}>
<div className={styles.tabList}>
{tabList}
</div>
<div className={styles.tabs}>
{content}
</div>
</div>
);
}
}
export default <LicenseElement />;
html, body {
background-color: transparent;
}
.loadingBox {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
}
.loadingLogo {
max-width: 50rem;
}
import React from 'react';
import {productName} from '../../package.json';
import logo from '../icon/OpenBlockLoading.svg';
import styles from './loading.css';
const LoadingElement = () => (
<div className={styles.loadingBox}>
<div>
<img
alt={`${productName} loading icon`}
src={logo}
className={styles.loadingLogo}
/>
</div>
</div>
);
export default <LoadingElement />;
html, body {
background-color: #4D97FF;
color: white;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
line-height: 150%;
}
.privacyBox {
background-color: white;
color: #575e75;
margin: 3rem;
padding: 2rem 3rem;
}
import {ipcRenderer} from 'electron';
const showPrivacyPolicy = event => {
if (event) {
// Probably a click on a link; don't actually follow the link in the `href` attribute.
event.preventDefault();
}
// tell the main process to open the privacy policy window
ipcRenderer.send('open-privacy-policy-window');
return false;
};
export default showPrivacyPolicy;
const path = require('path');
const makeConfig = require('./webpack.makeConfig.js');
module.exports = defaultConfig =>
makeConfig(
defaultConfig,
{
name: 'main',
useReact: false,
disableDefaultRulesForExtensions: ['js'],
babelPaths: [
path.resolve(__dirname, 'src', 'main')
]
}
);
const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
const util = require('util');
const electronPath = require('electron');
const webpack = require('webpack');
const merge = require('webpack-merge');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const MONACO_DIR = path.resolve(__dirname, './node_modules/monaco-editor');
// PostCss
const autoprefixer = require('autoprefixer');
const postcssVars = require('postcss-simple-vars');
const postcssImport = require('postcss-import');
const isProduction = (process.env.NODE_ENV === 'production');
const electronVersion = childProcess.execSync(`${electronPath} --version`, {encoding: 'utf8'}).trim();
console.log(`Targeting Electron ${electronVersion}`); // eslint-disable-line no-console
const makeConfig = function (defaultConfig, options) {
const babelOptions = {
// Explicitly disable babelrc so we don't catch various config in much lower dependencies.
babelrc: false,
plugins: [
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-async-to-generator',
'@babel/plugin-proposal-object-rest-spread'
],
presets: [
['@babel/preset-env', {targets: {electron: electronVersion}}]
]
};
const sourceFileTest = options.useReact ? /\.jsx?$/ : /\.js$/;
if (options.useReact) {
babelOptions.presets = babelOptions.presets.concat('@babel/preset-react');
babelOptions.plugins.push(['react-intl', {
messagesDir: './translations/messages/'
}]);
}
// TODO: consider adjusting these rules instead of discarding them in at least some cases
if (options.disableDefaultRulesForExtensions) {
defaultConfig.module.rules = defaultConfig.module.rules.filter(rule => {
if (!(rule.test instanceof RegExp)) {
// currently we don't support overriding other kinds of rules
return true;
}
// disable default rules for any file extension listed here
// we will handle these files in some other way (see below)
// OR we want to avoid any processing at all (such as with fonts)
const shouldDisable = options.disableDefaultRulesForExtensions.some(
ext => rule.test.test(`test.${ext}`)
);
const statusWord = shouldDisable ? 'Discarding' : 'Keeping';
console.log(`${options.name}: ${statusWord} electron-webpack default rule for ${rule.test}`);
return !shouldDisable;
});
}
const config = merge.smart(defaultConfig, {
devtool: 'cheap-module-eval-source-map',
mode: isProduction ? 'production' : 'development',
module: {
rules: [
{
test: sourceFileTest,
include: options.babelPaths,
loader: 'babel-loader',
options: babelOptions
},
{ // coped from scratch-gui
test: /\.css$/,
exclude: MONACO_DIR,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]_[local]_[hash:base64:5]',
camelCase: true
}
}, {
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: function () {
return [
postcssImport,
postcssVars,
autoprefixer
];
}
}
}]
},
{
test: /\.(svg|png|wav|gif|jpg|ttf)$/,
loader: 'file-loader',
options: {
outputPath: 'static/assets/'
}
},
{
test: /\.css$/,
include: MONACO_DIR,
use: ['style-loader', 'css-loader']
},
{
test: /node_modules[/\\](iconv-lite)[/\\].+/,
resolve: {
aliasFields: ['main']
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.GA_ID': `"${process.env.GA_ID || 'UA-000000-01'}"`
}),
new webpack.SourceMapDevToolPlugin({
filename: '[file].map'
}),
new MonacoWebpackPlugin({
languages: ['c', 'cpp', 'python', 'lua', 'javascript'],
features: ['!gotoSymbol']
})
].concat(options.plugins || []),
resolve: {
cacheWithContext: false,
symlinks: false,
alias: {
// act like scratch-gui has this line in its package.json:
// "browser": "./src/index.js"
'openblock-gui$': path.resolve(__dirname, 'node_modules', 'openblock-gui', 'src', 'index.js')
}
}
});
// If we're not on CI, enable Webpack progress output
// Note that electron-webpack enables this by default, so use '--no-progress' to avoid double-adding this plugin
if (!process.env.CI) {
config.plugins.push(new webpack.ProgressPlugin());
}
fs.writeFileSync(
`dist/webpack.${options.name}.js`,
`module.exports = ${util.inspect(config, {depth: null})};\n`
);
return config;
};
module.exports = makeConfig;
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const makeConfig = require('./webpack.makeConfig.js');
const getModulePath = moduleName => path.dirname(require.resolve(`${moduleName}/package.json`));
module.exports = defaultConfig =>
makeConfig(
defaultConfig,
{
name: 'renderer',
useReact: true,
disableDefaultRulesForExtensions: ['js', 'jsx', 'css', 'svg', 'png', 'wav', 'gif', 'jpg', 'ttf'],
babelPaths: [
path.resolve(__dirname, 'src', 'renderer'),
/node_modules[\\/]+scratch-[^\\/]+[\\/]+src/,
/node_modules[\\/]+openblock-[^\\/]+[\\/]+src/,
/node_modules[\\/]+pify/,
/node_modules[\\/]+@vernier[\\/]+godirect/
],
plugins: [
new CopyWebpackPlugin([{
from: path.join(getModulePath('openblock-blocks'), 'media'),
to: 'static/blocks-media'
}]),
new CopyWebpackPlugin([{
from: 'extension-worker.{js,js.map}',
context: path.join(getModulePath('openblock-vm'), 'dist', 'web')
}]),
new CopyWebpackPlugin([{
from: path.join(getModulePath('openblock-gui'), 'src', 'lib', 'libraries', '*.json'),
to: 'static/libraries',
flatten: true
}])
]
}
);
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!