Compare commits

..

No commits in common. "master" and "add-arduino-generator" have entirely different histories.

332 changed files with 16755 additions and 33165 deletions

8
.env
View File

@ -1,8 +0,0 @@
REACT_APP_COMPILER_URL=https://compiler.sensebox.de
REACT_APP_BOARD=sensebox-mcu
REACT_APP_BLOCKLY_API=https://api.blockly.sensebox.de
GENERATE_SOURCEMAP=false
# in days
REACT_APP_SHARE_LINK_EXPIRES=30

View File

@ -1,41 +0,0 @@
name: Build and push image
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
#defaults:
# run:
# working-directory: /repo
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0 # all history for all branches and tags
- name: Login to gitea.simonzeyer.de Repo
uses: docker/login-action@v2
with:
registry: gitea.simonzeyer.de
username: ${{ secrets.DOCKER_REPO_USER }}
password: ${{ secrets.DOCKER_REPO_PASSWD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile
push: true
tags: |
gitea.simonzeyer.de/schuelerlabor-cleverlab/smarti:${{ steps.meta.outputs.REPO_VERSION }}

View File

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,14 +0,0 @@
---
name: Short Issue
about: Template for Short Issues
title: ''
labels: ''
assignees: ''
---
### Current behaviour
Describe the current behaviour
### Expected behaviour
Describe how it is supposed to work

1
.gitignore vendored
View File

@ -22,4 +22,3 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
package-lock.json package-lock.json

49
.idea/workspace.xml generated
View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="a119841b-a70a-4b0e-91b6-4da6cd81f169" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/src/components/Tutorial/tutorials.json" beforeDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a119841b-a70a-4b0e-91b6-4da6cd81f169" name="Default Changelist" comment="" />
<created>1599559503505</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1599559503505</updated>
</task>
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@ -1,26 +0,0 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: senseBox Learn- and Programming Environment
message: Please cite this software using these metadata.
type: software
version: 1.0.0
date-released: 2021-09-30
url: "https://github.com/sensebox/React-Ardublockly"
authors:
- given-names: Mario
family-names: Pesch
email: mario.pesch@uni-muenster.de
affiliation: >-
Institute for geoinformatics University of
Muenster
- given-names: Luc
family-names: Niski
affiliation: >-
Institute for geoinformatics University of
Muenster
- given-names: Felix
family-names: Erdmann
email: f.erdmann@reedu.de
affiliation: Reedu GmbH & Co. KG

View File

@ -1,12 +0,0 @@
# specify the node base image with your desired version node:<version>
FROM node:16 as build
WORKDIR /app
copy ./ /app
RUN npm install --verbose
RUN npm run build --verbose
FROM nginx:alpine
COPY --from=build /app/build/ /usr/share/nginx/html
RUN chmod 755 /usr/share/nginx/html/ -R
EXPOSE 80
ENTRYPOINT ["sh", "-c", "cd /usr/share/nginx/html/ && nginx -g 'daemon off;'"]

201
LICENSE
View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

93
NEWS.md
View File

@ -1,93 +0,0 @@
# Blockly 2020
(aktuelles Preview unter: https://deploy-preview-37--blockly-react.netlify.app/)
In den letzten Wochen haben wir eine komplett neue Lern- und Programmierumgebung für die senseBox geschaffen. Die Basis bildet hierbei weiterhin Google Blockly und das Frontend wird über React realisiert. Fast alle Blöcke wurden bereits aus der alten Version in die neue Version migriert.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_880bb55f28e0dbb0bb9c2160d8e50594.png)
## Blockly Core
Nachdem die bisherige Version, die unter [blockly.sensebox.de](https://blockly.sensebox.de) verfügbar ist, auf einen Google Blockly Core von 2016 aufbaut, wurde es Zeit ein großes Update durchzuführen. Durch den neuen Blockly Core lassen sich auch andere Renderer der Blöcke verwenden. In den Einstellungen kannst du zwischen den zwei Renderern Geras und Zelos auswählen. Geras ist der klassische Blockly Renderer während Zelos vor allem für Touchoberfläche optimiert worden ist.
### Typed Variablen
Neue Variablen werden nun direkt mit einem bestimmten Datentyp angelegt. Ein einfacher Check überprüft ob der zurückgegeben Typ eines Blockes mit dem der Variablen kompatibel ist.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_0d505fc2472178182995732af226e736.png)
### Funktionen (funktioniert aktuell nicht!)
Funktionen mit Rückgabe Wert und Eingabeparametern können angelegt werden. Durch die Verwendung von Funktionen lassen sich auch komplexere Programme übersichtlicher darstellen und bearbeiten. Beim Anlegen einer Funktion kann über das Zahnrad weitere Eingabeparameter hinzuefügt werden.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_bd9544b118a1dbe83d149c00678eb39d.png)
### GPS
Der Code für das GPS Modul wurde neu aufgebaut und ermöglicht es deutlich schneller einen GPS Fix zu bekommen. Zusätzlich lassen sich bald! die Koordinaten in zwei verschiedenen Formaten zurückgebenlassen. Zum einen als Kommazahl zum anderen als Zahl ohne nachkommastellen
### MQTT
Zwei einfache Blöcke ermöglichen es nun die Daten über MQTT an einen Broker zu versenden. Zwei Broker sind bereits "vorprogrammiert" (Adafruit IO und DIOTY). Natürlich können auch eigene Broker verwendet werden. Falls ihr gute freie Broker kennt, die wir hinzufügen sollten meldet euch einfach bei uns.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_a9df4b0b9b1e6f39f09cf3b1743caad2.png)
### TTN Mapper
Bisher war es möglich einen "kleinen" TTN Mapper zu bauen, der die Daten als Cayenne Payload versendet hat. Es gibt nun einen Block der es direkt ermöglicht einen vollständigen TTN Mapper zu Programmieren, der die Daten auch auf [TTNMapper](https://ttnmapper.org/) veröffentlichen kann.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_b59691f2ebcf04d8b67a5f4e7fbe70b6.png)
## Fronted
In der Oberfläche gibt es einige Neuigkeiten. Ziel ist es Blockly für die senseBox zu einer vollständigen Lern- und Programmierumgebung weiterzuentwickeln.
Die Codeanzeige ist standardmäßig ausgeblendet kann aber einfach durch eine Klick auf das `</>` Icon hinzugefügt werden.
### Login mit openSenseMap/senseBox Account
Im Menü unter Login könnt ihr euch mit eurem openSenseMap Account anmelden. Sobald ihr angemeldet seid habt ihr die Möglichkeit Projekte online zu speichern.
Sobald der Login mit dem openSenseMap Account erfolgreich war lassen sich die bereits registrierten senseBox unter Account einsehen.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_c3965edc99021339a30b8d6704471e50.png)
Im Block zum senden an die openSenseMap müssen dann auch keine IDs mehr ausgewählt werden sondern die registrierten senseBox können einfach aus dem Dropdown Menü ausgewählt werden.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_57284cea57bfa5df3d55fe456f9d7cfa.png)
### Speichern von Projekten
Nach dem Login über den openSenseMap Account lassen sich Projekte online speichern und wieder abrufen
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_148146b1206fde184afff6edce26b515.png)
### Tutorials
Es gibt jetzt Tutorials! Eine reihe von verschiedenen Tutorials zeigt dir die ersten Schritte in der Programmierung mit Blockly. (Inhalte werden noch ausgebaut)
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_db0e64df48d4c34a9540ffb089e95769.png)
### Gallery
In der Gallery finden sich Beispiele mit verschiedenen Programmen. Die Beispiele können direkt in Blockly geöffnet werden, um Änderungen vorzunehmen oder das Programm direkt auf deine senseBox zu übertragen.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_c61e1fa98f9d840507a8a53b00605484.png)
### Teilen von Programmen
Über den Share Button kann ein Link zum Teilen der aktuellen Blöcke erstellt werden. Wann immer du dein Projekt mit anderen Teilen willst musst du nicht mehr eine XML Datei erstellen und verschicken sondern kannst einen Link erstellen, der direkt zu deinem Programm führt. Beachte, dass vor dem Teilen von Blöcken sämtliche sensiblen Daten, wie zum Beispiel Passwörter, Netzwerknamen, Lora oder openSenseMap Keys entfernt werden sollten. Die Links zum teilen von Programmen laufen nach 30 Tagen ab.
![](https://radosgw.public.os.wwu.de/pad/uploads/upload_a8dba6720fe2fb39cadf129d9bb04a62.png)
## Fehler
Falls ihr Fehler findet legt bitte ein Issue in folgendem Repository an: https://github.com/sensebox/React-Ardublockly/issues

View File

@ -1,25 +1,68 @@
<img src="/src/components/sensebox_logo.svg?raw=true" height="128" alt="senseBox Logo"/> This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
# React Ardublockly ## Available Scripts
This repository contains the source code and documentation of [sensebox-ardublockly](https://sensebox-ardublockly.netlify.app/). In the project directory, you can run:
This project was created with [Create React App](https://github.com/facebook/create-react-app) and represents the continuation or improvement of [blockly.sensebox.de](https://blockly.sensebox.de/ardublockly/?lang=de&board=sensebox-mcu). ### `npm start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
## Getting Started The page will reload if you make edits.<br />
You will also see any lint errors in the console.
1. [Download](https://github.com/sensebox/React-Ardublockly/archive/master.zip) or clone the GitHub Repository ``git clone https://github.com/sensebox/React-Ardublockly`` and checkout to branch ``master``. ### `npm test`
2. install [Node.js v10.xx](https://nodejs.org/en/) on your local machine Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
3. open shell and navigate inside folder ``React-Ardublockly`` ### `npm run build`
* run ``npm install``
* run ``npm start``
4. open [localhost:3000](http://localhost:3000)
## Troubleshoot Builds the app for production to the `build` folder.<br />
Ensure that line 14 in [store.js](https://github.com/sensebox/React-Ardublockly/blob/master/src/store.js#L14) is commented out or otherwise you have installed [Redux DevTools Extension](http://extension.remotedev.io/). It correctly bundles React in production mode and optimizes the build for the best performance.
## Demo The build is minified and the filenames include the hashes.<br />
A demo of the current status of the master branch can be accessed via [https://blockly-react.netlify.app/](https://blockly-react.netlify.app/) :rocket:. Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

View File

@ -1,11 +0,0 @@
# docker-compose.yml
services:
smarti:
mem_limit: 2048m
mem_reservation: 128M
cpus: 2
build:
dockerfile: Dockerfile
ports:
- "80"

14282
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,26 @@
{ {
"name": "blockly-react", "name": "blockly-react",
"version": "1.0.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@blockly/block-plus-minus": "^4.0.4", "@fortawesome/fontawesome-svg-core": "^1.2.30",
"@blockly/field-grid-dropdown": "^2.0.4", "@fortawesome/free-solid-svg-icons": "^5.14.0",
"@blockly/field-slider": "4.0.4", "@fortawesome/react-fontawesome": "^0.1.11",
"@blockly/plugin-scroll-options": "^3.0.5", "@material-ui/core": "^4.11.0",
"@blockly/plugin-typed-variable-modal": "^5.0.6", "@testing-library/jest-dom": "^4.2.4",
"@blockly/workspace-backpack": "^3.0.4", "@testing-library/react": "^9.5.0",
"@blockly/zoom-to-fit": "^3.0.4",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@monaco-editor/react": "^4.3.1",
"@mui/lab": "^5.0.0-alpha.110",
"@mui/material": "^5.10.16",
"@mui/styles": "^5.10.16",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"axios": "^0.22.0", "blockly": "^3.20200625.2",
"blockly": "^9.2.0", "react": "^16.13.1",
"file-saver": "^2.0.5", "react-dom": "^16.13.1",
"markdown-it": "^12.3.2", "react-redux": "^7.2.0",
"mnemonic-id": "^3.2.7", "react-router-dom": "^5.2.0",
"moment": "^2.29.4", "react-scripts": "3.4.1",
"prismjs": "^1.27.0", "redux": "^4.0.5",
"qrcode.react": "^3.1.0", "redux-thunk": "^2.3.0"
"react": "^17.0.2",
"react-cookie-consent": "^7.2.1",
"react-dom": "^17.0.2",
"react-markdown": "^8.0.0",
"react-markdown-editor-lite": "^1.3.3",
"react-mde": "^11.5.0",
"react-rating-stars-component": "^2.2.0",
"react-redux": "^7.2.9",
"react-router-dom": "^5.3.3",
"react-scripts": "^5.0.1",
"react-share": "^4.4.0",
"react-spinners": "^0.13.3",
"reactour": "^1.18.7",
"redux": "^4.2.0",
"redux-thunk": "^2.4.1",
"rehype-raw": "^6.1.1",
"remark-gemoji": "^7.0.1",
"remark-gfm": "^3.0.1",
"styled-components": "^4.4.1",
"uuid": "^8.3.1",
"watchpack": "^2.3.1"
},
"resolutions": {
"//": "See https://github.com/facebook/create-react-app/issues/11773",
"react-error-overlay": "6.0.9"
}, },
"scripts": { "scripts": {
"start": "node_modules/react-scripts/bin/react-scripts.js start", "start": "react-scripts start",
"dev": "set \"REACT_APP_BLOCKLY_API=http://localhost:8080\" && npm start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
@ -66,12 +28,16 @@
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
}, },
"browserslist": [ "browserslist": {
">0.2%", "production": [
"not dead", ">0.2%",
"not op_mini all" "not dead",
], "not op_mini all"
"devDependencies": { ],
"@babel/plugin-proposal-private-property-in-object": "7.21.11" "development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
} }
} }

View File

@ -1 +0,0 @@
/* /index.html 200

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -2,9 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.png" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#4EAF47" /> <meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
@ -20,17 +24,10 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>senseBox Blockly</title> <title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<!-- Matomo Image Tracker-->
<img
src="https://piwik.sensebox.kaufen/matomo.php?idsite=9&amp;rec=1"
style="border: 0; display: none"
alt=""
/>
<!-- End Matomo -->
<div id="root"></div> <div id="root"></div>
<!-- <!--
This HTML file is a template. This HTML file is a template.

View File

@ -3,9 +3,9 @@
"name": "Create React App Sample", "name": "Create React App Sample",
"icons": [ "icons": [
{ {
"src": "favicon.png", "src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/png" "type": "image/x-icon"
}, },
{ {
"src": "logo192.png", "src": "logo192.png",
@ -20,6 +20,6 @@
], ],
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#4EAF47", "theme_color": "#000000",
"background_color": "#4EAF47" "background_color": "#ffffff"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="12.71" height="8.79" viewBox="0 0 12.71 8.79"><title>dropdown-arrow</title><g opacity="0.1"><path d="M12.71,2.44A2.41,2.41,0,0,1,12,4.16L8.08,8.08a2.45,2.45,0,0,1-3.45,0L0.72,4.16A2.42,2.42,0,0,1,0,2.44,2.48,2.48,0,0,1,.71.71C1,0.47,1.43,0,6.36,0S11.75,0.46,12,.71A2.44,2.44,0,0,1,12.71,2.44Z" fill="#231f20"/></g><path d="M6.36,7.79a1.43,1.43,0,0,1-1-.42L1.42,3.45a1.44,1.44,0,0,1,0-2c0.56-.56,9.31-0.56,9.87,0a1.44,1.44,0,0,1,0,2L7.37,7.37A1.43,1.43,0,0,1,6.36,7.79Z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="96px" height="124px">
<style type="text/css">
#background {
fill: none;
}
.arrows {
fill: #000;
stroke: none;
}
.selected>.arrows {
fill: #fff;
}
.checkmark {
fill: #000;
font-family: sans-serif;
font-size: 10pt;
text-anchor: middle;
}
.trash {
fill: #888;
}
.zoom {
fill: none;
stroke: #888;
stroke-width: 2;
stroke-linecap: round;
}
.zoom>.center {
fill: #888;
stroke-width: 0;
}
</style>
<rect id="background" width="96" height="124" x="0" y="0" />
<g>
<path class="arrows" d="M 13,1.5 13,14.5 1.74,8 z" />
<path class="arrows" d="M 17.5,3 30.5,3 24,14.26 z" />
<path class="arrows" d="M 35,1.5 35,14.5 46.26,8 z" />
</g>
<g class="selected" transform="translate(0, 16)">
<path class="arrows" d="M 13,1.5 13,14.5 1.74,8 z" />
<path class="arrows" d="M 17.5,3 30.5,3 24,14.26 z" />
<path class="arrows" d="M 35,1.5 35,14.5 46.26,8 z" />
</g>
<text class="checkmark" x="55.5" y="28">&#10003;</text>
<g class="trash">
<path d="M 2,41 v 6 h 42 v -6 h -10.5 l -3,-3 h -15 l -3,3 z" />
<rect width="36" height="20" x="5" y="50" />
<rect width="36" height="42" x="5" y="50" rx="4" ry="4" />
</g>
<g class="zoom">
<circle r="11.5" cx="16" cy="108" />
<circle r="4.3" cx="16" cy="108" class="center" />
<path d="m 28,108 h3" />
<path d="m 1,108 h3" />
<path d="m 16,120 v3" />
<path d="m 16,93 v3" />
</g>
<g class="zoom">
<circle r="15" cx="48" cy="108" />
<path d="m 48,101.6 v12.8" />
<path d="m 41.6,108 h12.8" />
</g>
<g class="zoom">
<circle r="15" cx="80" cy="108" />
<path d="m 73.6,108 h12.8" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,74 +1,7 @@
.wrapper { .wrapper {
min-height: calc( min-height: 100vh; /* will cover the 100% of viewport */
100vh - 60px
); /* will cover the 100% of viewport - height of footer (padding-bottom) */
overflow: hidden; overflow: hidden;
display: block; display: block;
position: relative; position: relative;
padding-bottom: 60px; /* height of your footer + 30px*/ padding-bottom: 60px; /* height of your footer + 30px*/y
}
.tutorial img {
display: flex;
align-items: center;
max-height: 40vh;
max-width: 100%;
margin: auto;
}
.news img {
display: flex;
align-items: center;
max-height: 40vh;
max-width: 100%;
margin: auto;
}
.tutorial blockquote {
background: #f9f9f9;
border-left: 10px solid#4EAF47;
margin: 1.5em 10px;
padding: 0.5em 10px;
quotes: "\201C""\201D""\2018""\2019";
}
blockquote:before {
color: #4eaf47;
content: open-quote;
font-size: 4em;
line-height: 0.1em;
margin-right: 0.25em;
vertical-align: -0.4em;
}
blockquote p {
display: inline;
}
.tutorial table,
th,
td {
border: 1px solid #ddd;
}
.tutorial th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
border-color: #4eaf47;
color: white;
}
.overlay {
display: flex;
flex-direction: column;
align-items: center;
}
:root {
--url: url('./data/mcu_opacity.png');
}
.blocklySvg {
background-image: var(--url);
background-position: center;
background-repeat: no-repeat;
} }

View File

@ -1,52 +1,41 @@
import React, { Component } from "react"; import React from 'react';
import { Router } from "react-router-dom"; import { BrowserRouter as Router } from 'react-router-dom';
import { createBrowserHistory } from "history";
import { Provider } from "react-redux"; import { Provider } from 'react-redux';
import store from "./store"; import store from './store';
import { loadUser } from "./actions/authActions";
import "./App.css"; import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import { ThemeProvider, StyledEngineProvider, createTheme } from "@mui/material/styles"; import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Routes from './components/Routes';
import Content from "./components/Content"; const theme = createMuiTheme({
const theme = createTheme({
palette: { palette: {
primary: { primary: {
main: "#4EAF47", main: '#4EAF47',
contrastText: "#ffffff", }
}, }
secondary: {
main: "#DDDDDD",
},
button: {
compile: "#e27136",
},
},
}); });
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
render() { function App() {
const customHistory = createBrowserHistory(); return (
return ( <ThemeProvider theme={theme}>
<StyledEngineProvider injectFirst> <Provider store={store}>
<ThemeProvider theme={theme}> <Router>
<Provider store={store}> <div className="wrapper">
<Router history={customHistory}> <Navbar />
<Content /> <div style={{ margin: '0 22px' }}>
</Router> <Routes />
</Provider> </div>
</ThemeProvider> <Footer />
</StyledEngineProvider> </div>
); </Router>
} </Provider>
</ThemeProvider>
);
} }
export default App; export default App;

View File

@ -1,251 +0,0 @@
import {
GET_STATUS,
USER_LOADED,
USER_LOADING,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT_SUCCESS,
LOGOUT_FAIL,
REFRESH_TOKEN_SUCCESS,
} from "../actions/types";
import axios from "axios";
import { returnErrors, returnSuccess } from "./messageActions";
import { setLanguage } from "./generalActions";
// Check token & load user
export const loadUser = () => (dispatch) => {
// user loading
dispatch({
type: USER_LOADING,
});
const config = {
success: (res) => {
dispatch({
type: GET_STATUS,
payload: res.data.user.status,
});
dispatch(setLanguage(res.data.user.language));
dispatch({
type: USER_LOADED,
payload: res.data.user,
});
},
error: (err) => {
if (err.response) {
dispatch(returnErrors(err.response.data.message, err.response.status));
}
var status = [];
if (window.localStorage.getItem("status")) {
status = JSON.parse(window.localStorage.getItem("status"));
}
dispatch({
type: GET_STATUS,
payload: status,
});
dispatch({
type: AUTH_ERROR,
});
},
};
axios
.get(
`${process.env.REACT_APP_BLOCKLY_API}/user`,
config,
dispatch(authInterceptor())
)
.then((res) => {
res.config.success(res);
})
.catch((err) => {
err.config.error(err);
});
};
// Login user
export const login =
({ email, password }) =>
(dispatch) => {
dispatch({
type: USER_LOADING,
});
// Headers
const config = {
headers: {
"Content-Type": "application/json",
},
};
// Request Body
const body = JSON.stringify({ email, password });
axios
.post(`${process.env.REACT_APP_BLOCKLY_API}/user`, body, config)
.then((res) => {
dispatch(setLanguage(res.data.user.language));
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch({
type: GET_STATUS,
payload: res.data.user.status,
});
dispatch(returnSuccess(res.data.message, res.status, "LOGIN_SUCCESS"));
})
.catch((err) => {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"LOGIN_FAIL"
)
);
dispatch({
type: LOGIN_FAIL,
});
var status = [];
if (window.localStorage.getItem("status")) {
status = JSON.parse(window.localStorage.getItem("status"));
}
dispatch({
type: GET_STATUS,
payload: status,
});
});
};
// Logout User
export const logout = () => (dispatch) => {
const config = {
success: (res) => {
dispatch({
type: LOGOUT_SUCCESS,
});
var status = [];
if (window.localStorage.getItem("status")) {
status = JSON.parse(window.localStorage.getItem("status"));
}
dispatch({
type: GET_STATUS,
payload: status,
});
var locale = "en_US";
if (window.localStorage.getItem("locale")) {
locale = window.localStorage.getItem("locale");
} else if (navigator.language === "de-DE") {
locale = "de_DE";
}
dispatch(setLanguage(locale));
dispatch(returnSuccess(res.data.message, res.status, "LOGOUT_SUCCESS"));
},
error: (err) => {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"LOGOUT_FAIL"
)
);
dispatch({
type: LOGOUT_FAIL,
});
var status = [];
if (window.localStorage.getItem("status")) {
status = JSON.parse(window.localStorage.getItem("status"));
}
dispatch({
type: GET_STATUS,
payload: status,
});
},
};
axios
.post("https://api.opensensemap.org/users/sign-out", {}, config)
.then((res) => {
res.config.success(res);
})
.catch((err) => {
if (err.response && err.response.status !== 401) {
err.config.error(err);
}
});
};
export const authInterceptor = () => (dispatch, getState) => {
// Add a request interceptor
axios.interceptors.request.use(
(config) => {
config.headers["Content-Type"] = "application/json";
const token = getState().auth.token;
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
// Add a response interceptor
axios.interceptors.response.use(
(response) => {
// request was successfull
return response;
},
(error) => {
const originalRequest = error.config;
const refreshToken = getState().auth.refreshToken;
if (refreshToken) {
// try to refresh the token failed
if (error.response.status === 401 && originalRequest._retry) {
// router.push('/login');
return Promise.reject(error);
}
// token was not valid and 1st try to refresh the token
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = getState().auth.refreshToken;
// request to refresh the token, in request-body is the refreshToken
axios
.post("https://api.opensensemap.org/users/refresh-auth", {
token: refreshToken,
})
.then((res) => {
if (res.status === 200) {
dispatch({
type: REFRESH_TOKEN_SUCCESS,
payload: res.data,
});
axios.defaults.headers.common["Authorization"] =
"Bearer " + getState().auth.token;
// request was successfull, new request with the old parameters and the refreshed token
return axios(originalRequest)
.then((res) => {
originalRequest.success(res);
})
.catch((err) => {
originalRequest.error(err);
});
}
return Promise.reject(error);
})
.catch((err) => {
// request failed, token could not be refreshed
if (err.response) {
dispatch(
returnErrors(err.response.data.message, err.response.status)
);
}
dispatch({
type: AUTH_ERROR,
});
return Promise.reject(error);
});
}
}
// request status was unequal to 401, no possibility to refresh the token
return Promise.reject(error);
}
);
};

View File

@ -1,15 +0,0 @@
import {
BOARD,
} from "./types";
import mini_opacity from "../data/mini_opacity.png"
import mcu_opacity from "../data/mcu_opacity.png"
export const setBoard = (board) => (dispatch) => {
window.sessionStorage.setItem("board", board);
const root = document.querySelector(':root');
root.style.setProperty('--url', `url(${board === "mcu" ? mcu_opacity : mini_opacity})`);
dispatch({
type: BOARD,
payload: board,
});
};

View File

@ -1,52 +0,0 @@
import {
VISIT,
LANGUAGE,
RENDERER,
SOUNDS,
STATISTICS,
PLATFORM,
} from "./types";
export const visitPage = () => (dispatch) => {
dispatch({
type: VISIT,
});
};
export const setPlatform = (platform) => (dispatch) => {
dispatch({
type: PLATFORM,
payload: platform,
});
};
export const setLanguage = (language) => (dispatch, getState) => {
if (!getState().auth.progress && !getState().auth.isAuthenticated) {
window.localStorage.setItem("locale", language);
}
dispatch({
type: LANGUAGE,
payload: language,
});
};
export const setRenderer = (renderer) => (dispatch) => {
dispatch({
type: RENDERER,
payload: renderer,
});
};
export const setSounds = (sounds) => (dispatch) => {
dispatch({
type: SOUNDS,
payload: sounds,
});
};
export const setStatistics = (showStatistics) => (dispatch) => {
dispatch({
type: STATISTICS,
payload: showStatistics,
});
};

View File

@ -1,32 +0,0 @@
import { GET_ERRORS, CLEAR_MESSAGES, GET_SUCCESS } from './types';
// RETURN Errors
export const returnErrors = (msg, status, id = null) => {
return {
type: GET_ERRORS,
payload: {
msg: msg,
status: status,
id: id
}
};
};
// RETURN Success
export const returnSuccess = (msg, status, id = null) => {
return {
type: GET_SUCCESS,
payload: {
msg: msg,
status: status,
id: id
}
};
};
// CLEAR_MESSAGES
export const clearMessages = () => {
return {
type: CLEAR_MESSAGES
};
};

View File

@ -1,204 +0,0 @@
import { PROJECT_PROGRESS, GET_PROJECT, GET_PROJECTS, PROJECT_TYPE, PROJECT_DESCRIPTION } from './types';
import axios from 'axios';
import { returnErrors, returnSuccess } from './messageActions';
export const setType = (type) => (dispatch) => {
dispatch({
type: PROJECT_TYPE,
payload: type
});
};
export const setDescription = (description) => (dispatch) => {
dispatch({
type: PROJECT_DESCRIPTION,
payload: description
});
};
export const getProject = (type, id) => (dispatch) => {
dispatch({ type: PROJECT_PROGRESS });
dispatch(setType(type));
const config = {
success: res => {
var data = type === 'share' ? 'content' : type;
var project = res.data[data];
if (project) {
dispatch({
type: GET_PROJECT,
payload: project
});
dispatch({
type: PROJECT_DESCRIPTION,
payload: project.description
});
dispatch({ type: PROJECT_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status, 'GET_PROJECT_SUCCESS'));
}
else {
dispatch({ type: PROJECT_PROGRESS });
dispatch(returnErrors(res.data.message, res.status, 'PROJECT_EMPTY'));
}
},
error: err => {
if (err.response) {
dispatch(returnErrors(err.response.data.message, err.response.status, 'GET_PROJECT_FAIL'));
}
dispatch({ type: PROJECT_PROGRESS });
}
};
axios.get(`${process.env.REACT_APP_BLOCKLY_API}/${type}/${id}`, config)
.then(res => {
res.config.success(res);
})
.catch(err => {
err.config.error(err);
});
};
export const getProjects = (type) => (dispatch) => {
dispatch({ type: PROJECT_PROGRESS });
const config = {
success: res => {
var data = type === 'project' ? 'projects' : 'galleries';
var projects = res.data[data];
dispatch({
type: GET_PROJECTS,
payload: projects
});
dispatch({ type: PROJECT_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
},
error: err => {
if (err.response) {
dispatch(returnErrors(err.response.data.message, err.response.status, 'GET_PROJECTS_FAIL'));
}
dispatch({ type: PROJECT_PROGRESS });
}
};
axios.get(`${process.env.REACT_APP_BLOCKLY_API}/${type}`, config)
.then(res => {
res.config.success(res);
})
.catch(err => {
err.config.error(err);
});
};
export const updateProject = (type, id) => (dispatch, getState) => {
var workspace = getState().workspace;
var body = {
xml: workspace.code.xml,
title: workspace.name
};
var project = getState().project;
if (type === 'gallery') {
body.description = project.description;
}
const config = {
success: res => {
var project = res.data[type];
var projects = getState().project.projects;
var index = projects.findIndex(res => res._id === project._id);
projects[index] = project;
dispatch({
type: GET_PROJECTS,
payload: projects
});
if (type === 'project') {
dispatch(returnSuccess(res.data.message, res.status, 'PROJECT_UPDATE_SUCCESS'));
} else {
dispatch(returnSuccess(res.data.message, res.status, 'GALLERY_UPDATE_SUCCESS'));
}
},
error: err => {
if (err.response) {
if (type === 'project') {
dispatch(returnErrors(err.response.data.message, err.response.status, 'PROJECT_UPDATE_FAIL'));
} else {
dispatch(returnErrors(err.response.data.message, err.response.status, 'GALLERY_UPDATE_FAIL'));
}
}
}
};
axios.put(`${process.env.REACT_APP_BLOCKLY_API}/${type}/${id}`, body, config)
.then(res => {
res.config.success(res);
})
.catch(err => {
err.config.error(err);
});
};
export const deleteProject = (type, id) => (dispatch, getState) => {
const config = {
success: res => {
var projects = getState().project.projects;
var index = projects.findIndex(res => res._id === id);
projects.splice(index, 1)
dispatch({
type: GET_PROJECTS,
payload: projects
});
if (type === 'project') {
dispatch(returnSuccess(res.data.message, res.status, 'PROJECT_DELETE_SUCCESS'));
} else {
dispatch(returnSuccess(res.data.message, res.status, 'GALLERY_DELETE_SUCCESS'));
}
},
error: err => {
dispatch(returnErrors(err.response.data.message, err.response.status, 'PROJECT_DELETE_FAIL'));
}
};
axios.delete(`${process.env.REACT_APP_BLOCKLY_API}/${type}/${id}`, config)
.then(res => {
res.config.success(res);
})
.catch(err => {
if (err.response && err.response.status !== 401) {
err.config.error(err);
}
});
};
export const shareProject = (title, type, id) => (dispatch, getState) => {
var body = {
title: title
};
if (type === 'project') {
body.projectId = id;
} else {
body.xml = getState().workspace.code.xml;
}
axios.post(`${process.env.REACT_APP_BLOCKLY_API}/share`, body)
.then(res => {
var shareContent = res.data.content;
if (body.projectId) {
var projects = getState().project.projects;
var index = projects.findIndex(res => res._id === id);
projects[index].shared = shareContent.expiresAt;
dispatch({
type: GET_PROJECTS,
payload: projects
});
}
dispatch(returnSuccess(res.data.message, shareContent._id, 'SHARE_SUCCESS'));
})
.catch(err => {
if (err.response) {
dispatch(returnErrors(err.response.data.message, err.response.status, 'SHARE_FAIL'));
}
});
};
export const resetProject = () => (dispatch) => {
dispatch({
type: GET_PROJECTS,
payload: []
});
dispatch(setType(''));
dispatch(setDescription(''));
};

View File

@ -1,18 +0,0 @@
import axios from 'axios'
const fetchSensorWikiSuccess = sensors => ({
type: 'FETCH_SENSORWIKI_SUCCESS',
payload: { sensors }
})
export const fetchSensors = () => {
return async dispatch => {
try {
let sensors = await axios.get('https://api.sensors.wiki/sensors/all')
dispatch(fetchSensorWikiSuccess(sensors.data))
}
catch(e){
console.log(e)
}
}
}

View File

@ -1,345 +0,0 @@
import {
TUTORIAL_PROGRESS,
GET_TUTORIAL,
GET_TUTORIALS,
TUTORIAL_SUCCESS,
TUTORIAL_ERROR,
TUTORIAL_CHANGE,
TUTORIAL_XML,
TUTORIAL_STEP,
} from "./types";
import axios from "axios";
import { returnErrors, returnSuccess } from "./messageActions";
export const tutorialProgress = () => (dispatch) => {
dispatch({ type: TUTORIAL_PROGRESS });
};
export const getTutorial = (id) => (dispatch, getState) => {
axios
.get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/${id}`)
.then((res) => {
var tutorial = res.data.tutorial;
existingTutorial(tutorial, getState().tutorial.status).then((status) => {
dispatch({
type: TUTORIAL_SUCCESS,
payload: status,
});
dispatch(updateStatus(status));
dispatch({
type: GET_TUTORIAL,
payload: tutorial,
});
dispatch({ type: TUTORIAL_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
});
})
.catch((err) => {
if (err.response) {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"GET_TUTORIAL_FAIL"
)
);
}
dispatch({ type: TUTORIAL_PROGRESS });
});
};
export const getTutorials = () => (dispatch, getState) => {
axios
.get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial`)
.then((res) => {
var tutorials = res.data.tutorials;
existingTutorials(tutorials, getState().tutorial.status).then(
(status) => {
dispatch({
type: TUTORIAL_SUCCESS,
payload: status,
});
dispatch(updateStatus(status));
dispatch({
type: GET_TUTORIALS,
payload: tutorials,
});
dispatch({ type: TUTORIAL_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
}
);
})
.catch((err) => {
if (err.response) {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"GET_TUTORIALS_FAIL"
)
);
}
dispatch({ type: TUTORIAL_PROGRESS });
});
};
export const getAllTutorials = () => (dispatch, getState) => {
axios
.get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getAllTutorials`)
.then((res) => {
var tutorials = res.data.tutorials;
existingTutorials(tutorials, getState().tutorial.status).then(
(status) => {
dispatch({
type: TUTORIAL_SUCCESS,
payload: status,
});
dispatch(updateStatus(status));
dispatch({
type: GET_TUTORIALS,
payload: tutorials,
});
dispatch({ type: TUTORIAL_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
}
);
})
.catch((err) => {
if (err.response) {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"GET_TUTORIALS_FAIL"
)
);
}
dispatch({ type: TUTORIAL_PROGRESS });
});
};
export const getUserTutorials = () => (dispatch, getState) => {
axios
.get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getUserTutorials`)
.then((res) => {
var tutorials = res.data.tutorials;
existingTutorials(tutorials, getState().tutorial.status).then(
(status) => {
dispatch({
type: TUTORIAL_SUCCESS,
payload: status,
});
dispatch(updateStatus(status));
dispatch({
type: GET_TUTORIALS,
payload: tutorials,
});
dispatch({ type: TUTORIAL_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
}
);
})
.catch((err) => {
console.log(err);
if (err.response) {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"GET_TUTORIALS_FAIL"
)
);
}
dispatch({ type: TUTORIAL_PROGRESS });
});
};
export const updateStatus = (status) => (dispatch, getState) => {
if (getState().auth.isAuthenticated) {
// update user account in database - sync with redux store
axios
.put(`${process.env.REACT_APP_BLOCKLY_API}/user/status`, {
status: status,
})
.then((res) => {})
.catch((err) => {
if (err.response) {
// dispatch(returnErrors(err.response.data.message, err.response.status, 'UPDATE_STATUS_FAIL'));
}
});
} else {
// update locale storage - sync with redux store
window.localStorage.setItem("status", JSON.stringify(status));
}
};
export const deleteTutorial = (id) => (dispatch, getState) => {
var tutorial = getState().tutorial;
var id = getState().builder.id;
const config = {
success: (res) => {
var tutorials = tutorial.tutorials;
var index = tutorials.findIndex((res) => res._id === id);
tutorials.splice(index, 1);
dispatch({
type: GET_TUTORIALS,
payload: tutorials,
});
dispatch(
returnSuccess(res.data.message, res.status, "TUTORIAL_DELETE_SUCCESS")
);
},
error: (err) => {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"TUTORIAL_DELETE_FAIL"
)
);
},
};
axios
.delete(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/${id}`, config)
.then((res) => {
res.config.success(res);
})
.catch((err) => {
if (err.response && err.response.status !== 401) {
err.config.error(err);
}
});
};
export const resetTutorial = () => (dispatch) => {
dispatch({
type: GET_TUTORIALS,
payload: [],
});
dispatch({
type: TUTORIAL_STEP,
payload: 0,
});
};
export const tutorialChange = () => (dispatch) => {
dispatch({
type: TUTORIAL_CHANGE,
});
};
export const tutorialCheck = (status, step) => (dispatch, getState) => {
var tutorialsStatus = getState().tutorial.status;
var id = getState().tutorial.tutorials[0]._id;
var tutorialsStatusIndex = tutorialsStatus.findIndex(
(tutorialStatus) => tutorialStatus._id === id
);
var tasksIndex = tutorialsStatus[tutorialsStatusIndex].tasks.findIndex(
(task) => task._id === step._id
);
tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex] = {
...tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex],
type: status,
};
dispatch({
type: status === "success" ? TUTORIAL_SUCCESS : TUTORIAL_ERROR,
payload: tutorialsStatus,
});
dispatch(updateStatus(tutorialsStatus));
dispatch(tutorialChange());
dispatch(returnSuccess("", "", "TUTORIAL_CHECK_SUCCESS"));
};
export const storeTutorialXml = (code) => (dispatch, getState) => {
var tutorial = getState().tutorial.tutorials[0];
if (tutorial) {
var id = tutorial._id;
var activeStep = getState().tutorial.activeStep;
var steps = tutorial.steps;
if (steps && steps[activeStep].type === "task") {
var tutorialsStatus = getState().tutorial.status;
var tutorialsStatusIndex = tutorialsStatus.findIndex(
(tutorialStatus) => tutorialStatus._id === id
);
var tasksIndex = tutorialsStatus[tutorialsStatusIndex].tasks.findIndex(
(task) => task._id === steps[activeStep]._id
);
tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex] = {
...tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex],
xml: code,
};
dispatch({
type: TUTORIAL_XML,
payload: tutorialsStatus,
});
dispatch(updateStatus(tutorialsStatus));
}
}
};
export const tutorialStep = (step) => (dispatch) => {
dispatch({
type: TUTORIAL_STEP,
payload: step,
});
};
const existingTutorials = (tutorials, status) =>
new Promise(function (resolve, reject) {
var newstatus;
new Promise(function (resolve, reject) {
var existingTutorialIds = tutorials.map((tutorial, i) => {
existingTutorial(tutorial, status).then((status) => {
newstatus = status;
});
return tutorial._id;
});
resolve(existingTutorialIds);
}).then((existingTutorialIds) => {
// deleting old tutorials which do not longer exist
if (existingTutorialIds.length > 0) {
status = newstatus.filter(
(status) => existingTutorialIds.indexOf(status._id) > -1
);
}
resolve(status);
});
});
const existingTutorial = (tutorial, status) =>
new Promise(function (resolve, reject) {
var tutorialsId = tutorial._id;
var statusIndex = status.findIndex((status) => status._id === tutorialsId);
if (statusIndex > -1) {
var tasks = tutorial.steps.filter((step) => step.type === "task");
var existingTaskIds = tasks.map((task, j) => {
var tasksId = task._id;
if (
status[statusIndex].tasks.findIndex(
(task) => task._id === tasksId
) === -1
) {
// task does not exist
status[statusIndex].tasks.push({ _id: tasksId });
}
return tasksId;
});
// deleting old tasks which do not longer exist
if (existingTaskIds.length > 0) {
status[statusIndex].tasks = status[statusIndex].tasks.filter(
(task) => existingTaskIds.indexOf(task._id) > -1
);
}
} else {
status.push({
_id: tutorialsId,
tasks: tutorial.steps
.filter((step) => step.type === "task")
.map((task) => {
return { _id: task._id };
}),
});
}
resolve(status);
});

View File

@ -1,354 +0,0 @@
import {
PROGRESS,
JSON_STRING,
BUILDER_CHANGE,
BUILDER_ERROR,
BUILDER_TITLE,
BUILDER_PUBLIC,
BUILDER_DIFFICULTY,
BUILDER_REVIEW,
BUILDER_ID,
BUILDER_ADD_STEP,
BUILDER_DELETE_STEP,
BUILDER_CHANGE_STEP,
BUILDER_CHANGE_ORDER,
BUILDER_DELETE_PROPERTY,
} from "./types";
import data from "../data/hardware.json";
export const changeTutorialBuilder = () => (dispatch) => {
dispatch({
type: BUILDER_CHANGE,
});
};
export const jsonString = (json) => (dispatch) => {
dispatch({
type: JSON_STRING,
payload: json,
});
};
export const tutorialTitle = (title) => (dispatch) => {
dispatch({
type: BUILDER_TITLE,
payload: title,
});
dispatch(changeTutorialBuilder());
};
export const tutorialPublic = (pub) => (dispatch) => {
dispatch({
type: BUILDER_PUBLIC,
payload: pub,
});
dispatch(changeTutorialBuilder());
};
export const tutorialDifficulty = (difficulty) => (dispatch) => {
dispatch({
type: BUILDER_DIFFICULTY,
payload: difficulty,
});
dispatch(changeTutorialBuilder());
};
export const tutorialReview = (review) => (dispatch) => {
dispatch({
type: BUILDER_REVIEW,
payload: review,
});
dispatch(changeTutorialBuilder());
};
export const tutorialSteps = (steps) => (dispatch) => {
dispatch({
type: BUILDER_ADD_STEP,
payload: steps,
});
dispatch(changeTutorialBuilder());
};
export const tutorialId = (id) => (dispatch) => {
dispatch({
type: BUILDER_ID,
payload: id,
});
dispatch(changeTutorialBuilder());
};
export const addStep = (index) => (dispatch, getState) => {
var steps = getState().builder.steps;
var step = {
id: index + 1,
type: "instruction",
headline: "",
text: "",
};
steps.splice(index, 0, step);
dispatch({
type: BUILDER_ADD_STEP,
payload: steps,
});
dispatch(addErrorStep(index));
dispatch(changeTutorialBuilder());
};
export const addErrorStep = (index) => (dispatch, getState) => {
var error = getState().builder.error;
error.steps.splice(index, 0, {});
dispatch({
type: BUILDER_ERROR,
payload: error,
});
};
export const removeStep = (index) => (dispatch, getState) => {
var steps = getState().builder.steps;
steps.splice(index, 1);
dispatch({
type: BUILDER_DELETE_STEP,
payload: steps,
});
dispatch(removeErrorStep(index));
dispatch(changeTutorialBuilder());
};
export const removeErrorStep = (index) => (dispatch, getState) => {
var error = getState().builder.error;
error.steps.splice(index, 1);
dispatch({
type: BUILDER_ERROR,
payload: error,
});
};
export const changeContent =
(content, index, property1, property2) => (dispatch, getState) => {
var steps = getState().builder.steps;
var step = steps[index];
if (property2) {
if (step[property1] && step[property1][property2]) {
step[property1][property2] = content;
} else {
step[property1] = { [property2]: content };
}
} else {
step[property1] = content;
}
dispatch({
type: BUILDER_CHANGE_STEP,
payload: steps,
});
dispatch(changeTutorialBuilder());
};
export const deleteProperty =
(index, property1, property2) => (dispatch, getState) => {
var steps = getState().builder.steps;
var step = steps[index];
if (property2) {
if (step[property1] && step[property1][property2]) {
delete step[property1][property2];
}
} else {
delete step[property1];
}
dispatch({
type: BUILDER_DELETE_PROPERTY,
payload: steps,
});
dispatch(changeTutorialBuilder());
};
export const changeStepIndex = (fromIndex, toIndex) => (dispatch, getState) => {
var steps = getState().builder.steps;
var step = steps[fromIndex];
steps.splice(fromIndex, 1);
steps.splice(toIndex, 0, step);
dispatch({
type: BUILDER_CHANGE_ORDER,
payload: steps,
});
dispatch(changeErrorStepIndex(fromIndex, toIndex));
dispatch(changeTutorialBuilder());
};
export const changeErrorStepIndex =
(fromIndex, toIndex) => (dispatch, getState) => {
var error = getState().builder.error;
var errorStep = error.steps[fromIndex];
error.steps.splice(fromIndex, 1);
error.steps.splice(toIndex, 0, errorStep);
dispatch({
type: BUILDER_ERROR,
payload: error,
});
};
export const setError = (index, property) => (dispatch, getState) => {
var error = getState().builder.error;
if (index !== undefined) {
error.steps[index][property] = true;
} else {
error[property] = true;
}
dispatch({
type: BUILDER_ERROR,
payload: error,
});
dispatch(changeTutorialBuilder());
};
export const deleteError = (index, property) => (dispatch, getState) => {
var error = getState().builder.error;
if (index !== undefined) {
delete error.steps[index][property];
} else {
delete error[property];
}
dispatch({
type: BUILDER_ERROR,
payload: error,
});
dispatch(changeTutorialBuilder());
};
export const setSubmitError = () => (dispatch, getState) => {
var builder = getState().builder;
// if(builder.id === undefined || builder.id === ''){
// dispatch(setError(undefined, 'id'));
// }
if (builder.title === "") {
dispatch(setError(undefined, "title"));
}
if (builder.title === null) {
dispatch(setError(undefined, "title"));
}
var type = builder.steps.map((step, i) => {
// media and xml are directly checked for errors in their components and
// therefore do not have to be checked again
step.id = i + 1;
if (i === 0) {
if (step.requirements && step.requirements.length > 0) {
var requirements = step.requirements.filter((requirement) =>
/^[0-9a-fA-F]{24}$/.test(requirement)
);
if (requirements.length < step.requirements.length) {
dispatch(changeContent(requirements, i, "requirements"));
}
}
if (step.hardware === undefined || step.hardware.length < 1) {
dispatch(setError(i, "hardware"));
} else {
var hardwareIds = data.map((hardware) => hardware.id);
var hardware = step.hardware.filter((hardware) =>
hardwareIds.includes(hardware)
);
if (hardware.length < step.hardware.length) {
dispatch(changeContent(hardware, i, "hardware"));
}
}
}
if (step.headline === undefined || step.headline === "") {
dispatch(setError(i, "headline"));
}
if (step.text === undefined || step.text === "") {
dispatch(setError(i, "text"));
}
return step.type;
});
if (
!(
type.filter((item) => item === "task").length > 0 &&
type.filter((item) => item === "instruction").length > 0
)
) {
dispatch(setError(undefined, "type"));
}
};
export const checkError = () => (dispatch, getState) => {
dispatch(setSubmitError());
var error = getState().builder.error;
if (error.id || error.title || error.type) {
return true;
}
for (var i = 0; i < error.steps.length; i++) {
if (Object.keys(error.steps[i]).length > 0) {
return true;
}
}
return false;
};
export const progress = (inProgress) => (dispatch) => {
dispatch({
type: PROGRESS,
payload: inProgress,
});
};
export const resetTutorial = () => (dispatch, getState) => {
dispatch(jsonString(""));
dispatch(tutorialTitle(""));
var steps = [
{
type: "instruction",
headline: "",
text: "",
hardware: [],
requirements: [],
},
];
dispatch(tutorialSteps(steps));
dispatch({
type: BUILDER_ERROR,
payload: {
steps: [{}],
},
});
};
export const readJSON = (json) => (dispatch, getState) => {
dispatch(resetTutorial());
dispatch({
type: BUILDER_ERROR,
payload: {
steps: json.steps.map(() => {
return {};
}),
},
});
// accept only valid attributes
var steps = json.steps.map((step, i) => {
var object = {
_id: step._id,
type: step.type,
headline: step.headline,
text: step.text,
};
if (i === 0) {
object.hardware = step.hardware;
object.requirements = step.requirements;
}
if (step.xml) {
object.xml = step.xml;
}
if (step.media && step.type === "instruction") {
object.media = {};
if (step.media.picture) {
object.media.picture = step.media.picture;
} else if (step.media.youtube) {
object.media.youtube = step.media.youtube;
}
}
return object;
});
dispatch(tutorialTitle(json.title));
dispatch(tutorialDifficulty(json.difficulty));
dispatch(tutorialSteps(steps));
dispatch(setSubmitError());
dispatch(progress(false));
};

View File

@ -1,68 +1,5 @@
// authentication export const NEW_WORKSPACE = 'NEW_WORKSPACE';
export const USER_LOADING = "USER_LOADING"; export const CREATE_BLOCK = 'CREATE_BLOCK';
export const USER_LOADED = "USER_LOADED"; export const CHANGE_BLOCK = 'CHANGE_BLOCK';
export const AUTH_ERROR = "AUTH_ERROR"; export const DELETE_BLOCK = 'DELETE_BLOCK';
export const LOGIN_SUCCESS = "LOGIN_SUCCESS"; export const CLEAR_STATS = 'CLEAR_STATS';
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
export const LOGOUT_FAIL = "LOGOUT_FAIL";
export const REFRESH_TOKEN_FAIL = "REFRESH_TOKEN_FAIL";
export const REFRESH_TOKEN_SUCCESS = "REFRESH_TOKEN_SUCCESS";
export const NEW_CODE = "NEW_CODE";
export const CHANGE_WORKSPACE = "CHANGE_WORKSPACE";
export const CREATE_BLOCK = "CREATE_BLOCK";
export const MOVE_BLOCK = "MOVE_BLOCK";
export const CHANGE_BLOCK = "CHANGE_BLOCK";
export const DELETE_BLOCK = "DELETE_BLOCK";
export const CLEAR_STATS = "CLEAR_STATS";
export const NAME = "NAME";
export const TUTORIAL_PROGRESS = "TUTORIAL_PROGRESS";
export const GET_TUTORIAL = "GET_TUTORIAL";
export const GET_TUTORIALS = "GET_TUTORIALS";
export const GET_USERTUTORIALS = "GET_USERTUTORIALS";
export const GET_STATUS = "GET_STATUS";
export const TUTORIAL_SUCCESS = "TUTORIAL_SUCCESS";
export const TUTORIAL_ERROR = "TUTORIAL_ERROR";
export const TUTORIAL_CHANGE = "TUTORIAL_CHANGE";
export const TUTORIAL_XML = "TUTORIAL_XML";
export const TUTORIAL_ID = "TUTORIAL_ID";
export const TUTORIAL_STEP = "TUTORIAL_STEP";
export const JSON_STRING = "JSON_STRING";
export const BUILDER_CHANGE = "BUILDER_CHANGE";
export const BUILDER_TITLE = "BUILDER_TITLE";
export const BUILDER_DIFFICULTY = "BUILDER_DIFFICULTY";
export const BUILDER_PUBLIC = "BUILDER_PUBLIC";
export const BUILDER_REVIEW = "BUILDER_REVIEW";
export const BUILDER_ID = "BUILDER_ID";
export const BUILDER_ADD_STEP = "BUILDER_ADD_STEP";
export const BUILDER_DELETE_STEP = "BUILDER_DELETE_STEP";
export const BUILDER_CHANGE_STEP = "BUILDER_CHANGE_STEP";
export const BUILDER_CHANGE_ORDER = "BUILDER_CHANGE_ORDER";
export const BUILDER_DELETE_PROPERTY = "BUILDER_DELETE_PROPERTY";
export const BUILDER_ERROR = "BUILDER_ERROR";
export const PROGRESS = "PROGRESS";
export const VISIT = "VISIT";
export const LANGUAGE = "LANGUAGE";
export const PLATFORM = "PLATFORM";
export const RENDERER = "RENDERER";
export const SOUNDS = "SOUNDS";
export const STATISTICS = "STATISTICS";
// messages
export const GET_ERRORS = "GET_ERRORS";
export const GET_SUCCESS = "GET_SUCCESS";
export const CLEAR_MESSAGES = "CLEAR_MESSAGES";
// projects: share, gallery, project
export const PROJECT_PROGRESS = "PROJECT_PROGRESS";
export const GET_PROJECT = "GET_PROJECT";
export const GET_PROJECTS = "GET_PROJECTS";
export const PROJECT_TYPE = "PROJECT_TYPE";
export const PROJECT_DESCRIPTION = "PROJECT_DESCRIPTION";
//board
export const BOARD = "BOARD";

View File

@ -1,87 +1,43 @@
import { NEW_CODE, CHANGE_WORKSPACE, CREATE_BLOCK, MOVE_BLOCK, CHANGE_BLOCK, DELETE_BLOCK, CLEAR_STATS, NAME } from './types'; import { NEW_WORKSPACE, CREATE_BLOCK, CHANGE_BLOCK, DELETE_BLOCK, CLEAR_STATS } from './types';
import * as Blockly from 'blockly/core';
import { storeTutorialXml } from './tutorialActions';
export const workspaceChange = () => (dispatch) => {
dispatch({
type: CHANGE_WORKSPACE
});
};
export const onChangeCode = () => (dispatch, getState) => {
const workspace = Blockly.getMainWorkspace();
var code = getState().workspace.code;
code.arduino = Blockly.Arduino.workspaceToCode(workspace);
var xmlDom = Blockly.Xml.workspaceToDom(workspace);
code.xml = Blockly.Xml.domToPrettyText(xmlDom);
var selectedBlock = Blockly.getSelected();
if (selectedBlock !== null) {
code.helpurl = selectedBlock.helpUrl
code.tooltip = selectedBlock.tooltip
if (selectedBlock.data) {
code.data = selectedBlock.data
} else {
code.data = null
}
} else if (selectedBlock === null) {
code.tooltip = Blockly.Msg.tooltip_hint
code.helpurl = ''
code.data = null
}
dispatch({
type: NEW_CODE,
payload: code
});
return code;
};
export const onChangeWorkspace = (event) => (dispatch, getState) => { export const onChangeWorkspace = (event) => (dispatch, getState) => {
dispatch(workspaceChange()); var oldWorkspace = getState().workspace.new; // stored 'new workspace' is from now on old
var code = dispatch(onChangeCode()); var newWorkspace = window.Ardublockly.workspace;
dispatch(storeTutorialXml(code.xml));
var stats = getState().workspace.stats;
if (event.type === Blockly.Events.BLOCK_CREATE) {
stats.create += event.ids.length;
dispatch({ dispatch({
type: CREATE_BLOCK, type: NEW_WORKSPACE,
payload: stats payload: {new: newWorkspace, old: oldWorkspace}
}); });
} var stats = getState().workspace.stats;
else if (event.type === Blockly.Events.BLOCK_MOVE) { if (event.type === window.Blockly.Events.CREATE){
stats.move += 1; stats.create += event.ids.length;
dispatch({
type: MOVE_BLOCK,
payload: stats
});
}
else if (event.type === Blockly.Events.BLOCK_CHANGE) {
stats.change += 1;
dispatch({
type: CHANGE_BLOCK,
payload: stats
});
}
else if (event.type === Blockly.Events.BLOCK_DELETE) {
if (stats.create > 0) {
stats.delete += event.ids.length;
dispatch({ dispatch({
type: DELETE_BLOCK, type: CREATE_BLOCK,
payload: stats payload: stats
}); });
} }
} else if (event.type === window.Blockly.Events.CHANGE){
stats.change += 1;
dispatch({
type: CHANGE_BLOCK,
payload: stats
});
}
else if (event.type === window.Blockly.Events.DELETE){
if(stats.create > 0){
stats.delete += event.ids.length;
dispatch({
type: DELETE_BLOCK,
payload: stats
});
}
}
}; };
export const clearStats = () => (dispatch) => { export const clearStats = () => (dispatch) => {
var stats = { var stats = {
create: -1, // initialXML is created automatically, Block is not part of the statistics create: 0,
change: 0, change: 0,
delete: 0, delete: 0
move: -1 // initialXML is moved automatically, Block is not part of the statistics
}; };
dispatch({ dispatch({
type: CLEAR_STATS, type: CLEAR_STATS,
@ -89,9 +45,9 @@ export const clearStats = () => (dispatch) => {
}); });
}; };
export const workspaceName = (name) => (dispatch) => { export const setWorkspace = (workspace) => (dispatch, getState) => {
dispatch({ dispatch({
type: NAME, type: NEW_WORKSPACE,
payload: name payload: {new: workspace, old: getState().workspace.new}
}) });
} };

View File

@ -1,29 +0,0 @@
import React, { Component } from "react";
import withStyles from '@mui/styles/withStyles';
import { alpha } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
const styles = (theme) => ({
alert: {
marginBottom: "20px",
border: `1px solid ${theme.palette.primary.main}`,
padding: "10px 20px",
borderRadius: "4px",
background: alpha(theme.palette.primary.main, 0.3),
color: "rgb(70,70,70)",
},
});
export class Alert extends Component {
render() {
return (
<div className={this.props.classes.alert}>
<Typography>{this.props.children}</Typography>
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(Alert);

View File

@ -0,0 +1,6 @@
#blocklyDiv {
height: 90vH;
width: 50%;
position: absolute;
bottom: 0;
}

View File

@ -21,74 +21,55 @@
* @author samelh@google.com (Sam El-Husseini) * @author samelh@google.com (Sam El-Husseini)
*/ */
import React from "react"; import React from 'react';
import './BlocklyComponent.css';
import Blockly from "blockly/core"; import Blockly from 'blockly/core';
import "blockly/blocks"; //import locale from 'blockly/msg/en';
import Toolbox from "./toolbox/Toolbox"; import 'blockly/blocks';
import { Card } from "@mui/material"; //Blockly.setLocale(locale);
import {
ScrollOptions,
ScrollBlockDragger,
ScrollMetricsManager,
} from "@blockly/plugin-scroll-options";
class BlocklyComponent extends React.Component { class BlocklyComponent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.blocklyDiv = React.createRef(); this.blocklyDiv = React.createRef();
this.toolbox = React.createRef(); this.toolbox = React.createRef();
this.state = { workspace: undefined };
}
componentDidMount() {
const { initialXml, children, ...rest } = this.props;
this.primaryWorkspace = Blockly.inject(this.blocklyDiv.current, {
toolbox: this.toolbox.current,
plugins: {
// These are both required.
blockDragger: ScrollBlockDragger,
metricsManager: ScrollMetricsManager,
},
...rest,
});
// Initialize plugin.
this.setState({ workspace: this.primaryWorkspace });
const plugin = new ScrollOptions(this.workspace);
plugin.init({ enableWheelScroll: true, enableEdgeScroll: false });
if (initialXml) {
Blockly.Xml.domToWorkspace(
Blockly.Xml.textToDom(initialXml),
this.primaryWorkspace
);
} }
}
get workspace() { componentDidMount() {
return this.primaryWorkspace; const { initialXml, children, ...rest } = this.props;
} this.primaryWorkspace = Blockly.inject(
this.blocklyDiv.current,
{
toolbox: this.toolbox.current,
...rest
},
);
setXml(xml) { if (initialXml) {
Blockly.Xml.domToWorkspace( Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(initialXml), this.primaryWorkspace);
Blockly.Xml.textToDom(xml), }
this.primaryWorkspace }
);
}
render() { get workspace() {
return ( return this.primaryWorkspace;
<React.Fragment> }
<Card
ref={this.blocklyDiv} setXml(xml) {
id="blocklyDiv" Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), this.primaryWorkspace);
style={this.props.style ? this.props.style : {}} }
/>
<Toolbox toolbox={this.toolbox} workspace={this.state.workspace} /> render() {
</React.Fragment> const { children } = this.props;
);
} return <React.Fragment>
<div ref={this.blocklyDiv} id="blocklyDiv" />
<xml xmlns="https://developers.google.com/blockly/xml" is="blockly" style={{ display: 'none' }} ref={this.toolbox}>
{children}
</xml>
</React.Fragment>;
}
} }
export default BlocklyComponent; export default BlocklyComponent;

View File

@ -1,74 +0,0 @@
import React, { Component } from 'react';
import * as Blockly from 'blockly/core';
class BlocklySvg extends Component {
constructor(props) {
super(props);
this.state = {
svg: ''
};
}
componentDidMount() {
this.getSvg();
}
componentDidUpdate(props) {
if(props.initialXml !== this.props.initialXml){
this.getSvg();
}
}
getSvg = () => {
const workspace = Blockly.getMainWorkspace();
workspace.clear();
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace);
var canvas = workspace.svgBlockCanvas_.cloneNode(true);
if (canvas.children[0] !== undefined) {
canvas.removeAttribute("transform");
// does not work in react
// var cssContent = Blockly.Css.CONTENT.join('');
var cssContent = '';
for (var i = 0; i < document.getElementsByTagName('style').length; i++) {
if(/^blockly.*$/.test(document.getElementsByTagName('style')[i].id)){
cssContent += document.getElementsByTagName('style')[i].firstChild.data.replace(/\..* \./g, '.');
}
}
// ensure that fill-opacity is 1, because there cannot be a replacing
// https://github.com/google/blockly/pull/3431/files#diff-00254795773903d3c0430915a68c9521R328
cssContent += `.blocklyPath {
fill-opacity: 1;
}
.blocklyPathDark {
display: flex;
}
.blocklyPathLight {
display: flex;
} `;
var css = '<defs><style type="text/css" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[' + cssContent + ']]></style></defs>';
var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox();
var content = new XMLSerializer().serializeToString(canvas);
var xml = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="${bbox.width}" height="${bbox.height}" viewBox="${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}">
${css}">${content}</svg>`;
this.setState({svg: xml});
}
}
render() {
return (
<div
style={{display: 'inline-flex', justifyContent: 'center', transform: 'scale(0.8) translate(0, calc(100% * -0.2 / 2))'}}
dangerouslySetInnerHTML={{ __html: this.state.svg }}
/>
);
};
}
export default BlocklySvg;

View File

@ -1,155 +0,0 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { onChangeWorkspace, clearStats } from "../../actions/workspaceActions";
import BlocklyComponent from "./BlocklyComponent";
import BlocklySvg from "./BlocklySvg";
import * as Blockly from "blockly/core";
import "./blocks/index";
import "./generator/index";
import { ZoomToFitControl } from "@blockly/zoom-to-fit";
import { initialXml } from "./initialXml.js";
import { getMaxInstances } from "./helpers/maxInstances";
import { Backpack } from "@blockly/workspace-backpack";
class BlocklyWindow extends Component {
constructor(props) {
super(props);
this.simpleWorkspace = React.createRef();
}
componentDidMount() {
const workspace = Blockly.getMainWorkspace();
this.props.onChangeWorkspace({});
this.props.clearStats();
workspace.addChangeListener((event) => {
this.props.onChangeWorkspace(event);
// switch on that a block is displayed disabled or not depending on whether it is correctly connected
// for SVG display, a deactivated block in the display is undesirable
if (this.props.blockDisabled) {
Blockly.Events.disableOrphans(event);
}
});
Blockly.svgResize(workspace);
const zoomToFit = new ZoomToFitControl(workspace);
zoomToFit.init();
// Initialize plugin.
const backpack = new Backpack(workspace);
backpack.init();
}
componentDidUpdate(props) {
const workspace = Blockly.getMainWorkspace();
var xml = this.props.initialXml;
if (props.selectedBoard !== this.props.selectedBoard) {
xml = localStorage.getItem("autoSaveXML");
// change board
if(!xml) xml = initialXml;
var xmlDom = Blockly.Xml.textToDom(xml);
Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace);
// var toolbox = workspace.getToolbox();
// workspace.updateToolbox(toolbox.toolboxDef_);
}
// if svg is true, then the update process is done in the BlocklySvg component
if (props.initialXml !== xml && !this.props.svg) {
// guarantees that the current xml-code (this.props.initialXml) is rendered
workspace.clear();
if (!xml) xml = initialXml;
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
}
if (props.language !== this.props.language) {
// change language
xml = localStorage.getItem("autoSaveXML");
if (!xml) xml = initialXml;
xmlDom = Blockly.Xml.textToDom(xml);
Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace);
// var toolbox = workspace.getToolbox();
// workspace.updateToolbox(toolbox.toolboxDef_);
}
Blockly.svgResize(workspace);
}
render() {
return (
<div>
<BlocklyComponent
ref={this.simpleWorkspace}
style={this.props.svg ? { height: 0 } : this.props.blocklyCSS}
readOnly={
this.props.readOnly !== undefined ? this.props.readOnly : false
}
trashcan={
this.props.trashcan !== undefined ? this.props.trashcan : true
}
renderer={this.props.renderer}
sounds={this.props.sounds}
maxInstances={getMaxInstances()}
zoom={{
// https://developers.google.com/blockly/guides/configure/web/zoom
controls:
this.props.zoomControls !== undefined
? this.props.zoomControls
: true,
wheel: false,
startScale: 1,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.2,
}}
grid={
this.props.grid !== undefined && !this.props.grid
? {}
: {
// https://developers.google.com/blockly/guides/configure/web/grid
spacing: 20,
length: 1,
colour: "#4EAF47", // senseBox-green
snap: false,
}
}
media={"/media/blockly/"}
move={
this.props.move !== undefined && !this.props.move
? {}
: {
// https://developers.google.com/blockly/guides/configure/web/move
scrollbars: true,
drag: true,
wheel: true,
}
}
initialXml={
this.props.initialXml ? this.props.initialXml : initialXml
}
></BlocklyComponent>
{this.props.svg && this.props.initialXml ? (
<BlocklySvg initialXml={this.props.initialXml} />
) : null}
</div>
);
}
}
BlocklyWindow.propTypes = {
onChangeWorkspace: PropTypes.func.isRequired,
clearStats: PropTypes.func.isRequired,
renderer: PropTypes.string.isRequired,
sounds: PropTypes.bool.isRequired,
language: PropTypes.string.isRequired,
selectedBoard: PropTypes.string.isRequired,
};
const mapStateToProps = (state) => ({
renderer: state.general.renderer,
sounds: state.general.sounds,
language: state.general.language,
selectedBoard: state.board.board,
});
export default connect(mapStateToProps, { onChangeWorkspace, clearStats })(
BlocklyWindow
);

View File

@ -1,88 +0,0 @@
import Blockly from "blockly";
import { getColour } from "../helpers/colour";
import * as Types from "../helpers/types";
import { selectedBoard } from "../helpers/board";
import { FieldGridDropdown } from "@blockly/field-grid-dropdown";
/**
* DS18B20 Temperatursonde
*
*/
Blockly.Blocks["CleVerLab_dummy1"] = {
init: function () {
this.setColour(getColour().cleverlab);
this.appendDummyInput()
.appendField("tut nichts")
this.setOutput(true, Types.NUMBER.typeName);
this.data = {name: "empty"};
},
};
Blockly.Blocks["CleVerLab_temperature"] = {
init: function () {
this.setColour(getColour().cleverlab);
this.appendDummyInput()
.appendField("Temperatur")
.appendField("Digital Port:")
.appendField(new Blockly.FieldDropdown(selectedBoard().digitalPorts), "DigitalPin");
this.setOutput(true, Types.NUMBER.typeName);
this.data = {name: "ds18b20"};
},
};
/**
* PH Wert
*
*/
Blockly.Blocks["CleVerLab_pH"] = {
init: function () {
this.setColour(getColour().cleverlab);
this.appendDummyInput()
.appendField("pH Wert")
.appendField("Digital Port:")
.appendField(new Blockly.FieldDropdown(selectedBoard().digitalPins), "DigitalPin");
this.setOutput(true, Types.NUMBER.typeName);
this.data = {name: "phoderso"};
},
};
Blockly.Blocks["CleVerLab_cali1"] = {
init: function () {
this.appendDummyInput()
.appendField("Kalibriere pH Sensor");
this.appendValueInput("VAR1", "Number")
.appendField("Referenzlösung pH 4.00 =")
.setAlign(Blockly.ALIGN_RIGHT);
this.appendValueInput("VAR2", "Number2")
.appendField("Referenzlösung pH 7.00 =")
.setAlign(Blockly.ALIGN_RIGHT);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(getColour().cleverlab);
this.setOutput(true, Types.NUMBER.typeName);
this.data = {name: "dsasda"};
},
};
/**
* Pump
*
*/
Blockly.Blocks['CleVerLab_pump'] = {
init: function() {
this.setColour(getColour().cleverlab);
var dropdown = new Blockly.FieldDropdown([
[ 'START','HIGH'],
[ 'STOPP','LOW']
]);
this.appendDummyInput()
.appendField(dropdown, "Mode")
.appendField(" Pumpe ")
.appendField(new Blockly.FieldDropdown(selectedBoard().digitalPins), "DigitalPin");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
//this.setOutput(true, "Number");
this.setTooltip('');
this.setHelpUrl('');
}
};

View File

@ -1,61 +0,0 @@
import Blockly from 'blockly/core';
import { selectedBoard } from '../helpers/board'
import * as Types from '../helpers/types'
import { getColour } from '../helpers/colour';
Blockly.Blocks['io_tone'] = {
init: function () {
this.appendDummyInput()
.appendField(Blockly.Msg.ARD_SETTONE)
.appendField(new Blockly.FieldDropdown(
selectedBoard().digitalPins), "TONEPIN");
this.appendValueInput("FREQUENCY")
.setCheck(Types.getCompatibleTypes('int'))
.appendField(Blockly.Msg.ARD_TONEFREQ);
this.appendDummyInput()
.appendField("Hz");
this.setInputsInline(true);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setColour(getColour().audio);
this.setTooltip(Blockly.Msg.ARD_TONE_TIP);
this.setHelpUrl('https://www.arduino.cc/en/Reference/tone');
},
/**
* Called whenever anything on the workspace changes.
* It checks frequency values and sets a warning if out of range.
* @this Blockly.Block
*/
onchange: function (event) {
if (!this.workspace || event.type === Blockly.Events.MOVE ||
event.type === Blockly.Events.UI) {
return; // Block deleted or irrelevant event
}
var freq = Blockly.Arduino.valueToCode(
this, "FREQUENCY", Blockly.Arduino.ORDER_ATOMIC)
if (freq < 31 || freq > 65535) {
this.setWarningText(Blockly.Msg.ARD_TONE_WARNING, 'io_tone');
} else {
this.setWarningText(null, 'io_tone');
}
}
};
Blockly.Blocks['io_notone'] = {
init: function () {
this.appendDummyInput()
.appendField(Blockly.Msg.ARD_NOTONE)
.appendField(new Blockly.FieldDropdown(
selectedBoard().digitalPins), "TONEPIN");
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setColour(getColour().audio);
this.setTooltip(Blockly.Msg.ARD_NOTONE_TIP);
this.setHelpUrl('https://www.arduino.cc/en/Reference/noTone');
},
/** @return {!string} The type of input value for the block, an integer. */
getBlockType: function () {
return Blockly.Types.NUMBER;
}
};

View File

@ -1,31 +1,4 @@
import "./loops"; import './loops';
import "./sensebox"; import './sensebox';
import "./logic"; import './logic';
import "./sensebox-sensors"; import './sensebox-sensors';
import "./sensebox-telegram";
import "./sensebox-osem";
import "./sensebox-web";
import "./sensebox-display";
import "./sensebox-motors";
import "./sensebox-lora";
import "./sensebox-led";
import "./sensebox-rtc";
import "./sensebox-ntp";
import "./sensebox-ble";
import "./sensebox-sd";
import "./mqtt";
import "./text";
import "./io";
import "./audio";
import "./math";
import "./map";
import "./procedures";
import "./serial";
import "./time";
import "./variables";
import "./lists";
import "./watchdog";
import "./webserver";
import "./CleVerLab"
import "../helpers/types";

View File

@ -1,280 +0,0 @@
/**
* @license Licensed under the Apache License, Version 2.0 (the "License"):
* http://www.apache.org/licenses/LICENSE-2.0
*/
/**
* @fileoverview Blocks for Arduino Digital and Analogue input and output
* functions. The Arduino function syntax can be found at
* http://arduino.cc/en/Reference/HomePage
*
* TODO: maybe change this to a "PIN" BlocklyType
*/
import Blockly from "blockly/core";
import { selectedBoard } from "../helpers/board";
import * as Types from "../helpers/types";
Blockly.Blocks["io_digitalwrite"] = {
/**
* Block for creating a 'set pin' to a state.
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/DigitalWrite");
this.setColour(250);
this.appendValueInput("STATE")
.appendField(Blockly.Msg.ARD_DIGITALWRITE)
.appendField(
new Blockly.FieldDropdown(selectedBoard().digitalPins),
"PIN"
)
.appendField(Blockly.Msg.ARD_WRITE_TO)
.setCheck(Types.BOOLEAN.checkList);
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setTooltip(Blockly.Msg.ARD_DIGITALWRITE_TIP);
},
/**
* Updates the content of the the pin related fields.
* @this Blockly.Block
*/
updateFields: function () {
Blockly.Arduino.Boards.refreshBlockFieldDropdown(
this,
"PIN",
"digitalPins"
);
},
};
Blockly.Blocks["io_digitalread"] = {
/**
* Block for creating a 'read pin'.
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/DigitalRead");
this.setColour(250);
this.appendDummyInput()
.appendField(Blockly.Msg.ARD_DIGITALREAD)
.appendField(
new Blockly.FieldDropdown(selectedBoard().digitalPins),
"PIN"
);
this.setOutput(true, "boolean");
this.setTooltip(Blockly.Msg.ARD_DIGITALREAD_TIP);
},
/** @return {!string} The type of return value for the block, an integer. */
getBlockType: function () {
return Types.BOOLEAN;
},
/**
* Updates the content of the the pin related fields.
* @this Blockly.Block
*/
updateFields: function () {
Blockly.Arduino.Boards.refreshBlockFieldDropdown(
this,
"PIN",
"digitalPins"
);
},
};
Blockly.Blocks["io_builtin_led"] = {
/**
* Block for setting built-in LED to a state.
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/DigitalWrite");
this.setColour(250);
this.appendValueInput("STATE")
.appendField(Blockly.Msg.ARD_BUILTIN_LED)
.appendField(
new Blockly.FieldDropdown(selectedBoard().builtinLed),
"BUILT_IN_LED"
)
.appendField(Blockly.Msg.ARD_WRITE_TO)
.setCheck(Types.BOOLEAN.compatibleTypes);
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setTooltip(Blockly.Msg.ARD_BUILTIN_LED_TIP);
},
/**
* Updates the content of the the pin related fields.
* @this Blockly.Block
*/
updateFields: function () {
Blockly.Arduino.Boards.refreshBlockFieldDropdown(
this,
"BUILT_IN_LED",
"builtinLed"
);
},
/** @return {!string} The type of input value for the block, an integer. */
getBlockType: function () {
return Types.BOOLEAN;
},
};
Blockly.Blocks["io_analogwrite"] = {
/**
* Block for creating a 'set pin' to an analogue value.
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/AnalogWrite");
this.setColour(250);
this.appendValueInput("NUM")
.appendField(Blockly.Msg.ARD_ANALOGWRITE)
.appendField(new Blockly.FieldDropdown(selectedBoard().pwmPins), "PIN")
.appendField(Blockly.Msg.ARD_WRITE_TO)
.setCheck(Types.NUMBER.compatibleTypes);
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setTooltip(Blockly.Msg.ARD_ANALOGWRITE_TIP);
},
/**
* Updates the content of the the pin related fields.
* @this Blockly.Block
*/
updateFields: function () {
Blockly.Arduino.Boards.refreshBlockFieldDropdown(this, "PIN", "pwmPins");
},
/** @return {!string} The type of input value for the block, an integer. */
getBlockType: function () {
return Types.NUMBER;
},
};
Blockly.Blocks["io_analogread"] = {
/**
* Block for reading an analogue input.
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/AnalogRead");
this.setColour(250);
this.appendDummyInput()
.appendField(Blockly.Msg.ARD_ANALOGREAD)
.appendField(
new Blockly.FieldDropdown(selectedBoard().analogPins),
"PIN"
);
this.setOutput(true, Types.NUMBER.typeName);
this.setTooltip(Blockly.Msg.ARD_ANALOGREAD_TIP);
},
/** @return {!string} The type of return value for the block, an integer. */
getBlockType: function () {
return Types.NUMBER.typeName;
},
/**
* Updates the content of the the pin related fields.
* @this Blockly.Block
*/
updateFields: function () {
Blockly.Arduino.Boards.refreshBlockFieldDropdown(this, "PIN", "analogPins");
},
};
Blockly.Blocks["io_highlow"] = {
/**
* Block for creating a pin state.
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/Constants");
this.setColour(250);
this.appendDummyInput().appendField(
new Blockly.FieldDropdown([
[Blockly.Msg.ARD_HIGH, "HIGH"],
[Blockly.Msg.ARD_LOW, "LOW"],
]),
"STATE"
);
this.setOutput(true, Types.BOOLEAN.typeName);
this.setTooltip(Blockly.Msg.ARD_HIGHLOW_TIP);
},
/** @return {!string} The type of return value for the block, an integer. */
getBlockType: function () {
return Types.BOOLEAN;
},
};
Blockly.Blocks["io_pulsein"] = {
/**
* Block for measuring the duration of a pulse in an input pin.
* @this Blockly.Block
*/
init: function () {
this.jsonInit({
type: "math_foo",
message0: Blockly.Msg.ARD_PULSE_READ,
args0: [
{
type: "input_value",
name: "PULSETYPE",
check: Types.BOOLEAN.compatibleTypes,
},
{
type: "field_dropdown",
name: "PULSEPIN",
options: selectedBoard().digitalPins,
},
],
output: Types.NUMBER.typeName,
inputsInline: true,
colour: 250,
tooltip: Blockly.Msg.ARD_PULSE_TIP,
helpUrl: "https://www.arduino.cc/en/Reference/PulseIn",
});
},
/** @return {!string} The type of input value for the block, an integer. */
getBlockType: function () {
return Types.NUMBER.typeName;
},
};
Blockly.Blocks["io_pulsetimeout"] = {
/**
* Block for measuring (with a time-out) the duration of a pulse in an input
* pin.
* @this Blockly.Block
*/
init: function () {
this.jsonInit({
type: "math_foo",
message0: Blockly.Msg.ARD_PULSE_READ_TIMEOUT,
args0: [
{
type: "input_value",
name: "PULSETYPE",
check: Types.BOOLEAN.compatibleTypes,
},
{
type: "field_dropdown",
name: "PULSEPIN",
options: selectedBoard().digitalPins,
},
{
type: "input_value",
name: "TIMEOUT",
check: Types.NUMBER.compatibleTypes,
},
],
output: Types.NUMBER.typeName,
inputsInline: true,
colour: 250,
tooltip: Blockly.Msg.ARD_PULSETIMEOUT_TIP,
helpUrl: "https://www.arduino.cc/en/Reference/PulseIn",
});
},
/** @return {!string} The type of input value for the block, an integer. */
getBlockType: function () {
return Types.NUMBER.typeName;
},
};

View File

@ -1,54 +0,0 @@
import Blockly, { FieldDropdown } from "blockly/core";
import * as Types from "../helpers/types";
import { getColour } from "../helpers/colour";
Blockly.Blocks["lists_create_empty"] = {
/**
* Elapsed time in milliseconds block definition
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/Millis");
this.setColour(getColour().arrays);
this.appendDummyInput().appendField("create List with");
this.appendValueInput("NUMBER");
this.appendDummyInput()
.appendField("Items of Type")
.appendField(new FieldDropdown(Types.VARIABLE_TYPES), "type");
this.setOutput(true, Types.ARRAY.typeName);
this.setTooltip(Blockly.Msg.ARD_TIME_MILLIS_TIP);
},
};
Blockly.Blocks["array_getIndex"] = {
/**
* Elapsed time in milliseconds block definition
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/Millis");
this.setColour(getColour().arrays);
this.appendDummyInput().appendField(
new Blockly.FieldVariable("X", null, ["Array"], "Array"),
"FIELDNAME"
);
this.setOutput(true, Types.ARRAY.typeName);
this.setTooltip(Blockly.Msg.ARD_TIME_MILLIS_TIP);
},
};
Blockly.Blocks["lists_length"] = {
/**
* Elapsed time in milliseconds block definition
* @this Blockly.Block
*/
init: function () {
this.setHelpUrl("http://arduino.cc/en/Reference/Millis");
this.setColour(getColour().arrays);
this.appendValueInput("ARRAY")
.appendField("length of")
.setCheck(Types.ARRAY.compatibleTypes);
this.setOutput(true, Types.NUMBER.typeName);
this.setTooltip(Blockly.Msg.ARD_TIME_MILLIS_TIP);
},
};

View File

@ -1,651 +1,61 @@
import Blockly from "blockly/core"; import { defineBlocksWithJsonArray } from 'blockly';
import { getColour } from "../helpers/colour"; import Blockly from 'blockly/core';
import * as Types from "../helpers/types";
import { getCompatibleTypes } from "../helpers/types";
Blockly.Blocks["controls_if"] = { defineBlocksWithJsonArray([
/** // If/else block that does not use a mutator.
* Block for if/elseif/else condition. {
* @this Blockly.Block type: 'control_if',
*/ message0: '%{BKY_CONTROLS_IF_MSG_IF} %1',
init: function () { args0: [
this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL); {
this.setColour(getColour().logic); type: 'input_value',
this.appendValueInput("IF0") name: 'IF0',
.setCheck(Types.getCompatibleTypes("boolean")) check: 'Boolean'
.appendField(Blockly.Msg.CONTROLS_IF_MSG_IF); }
this.appendStatementInput("DO0").appendField(
Blockly.Msg.CONTROLS_IF_MSG_THEN
);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setMutator(
new Blockly.Mutator(["controls_if_elseif", "controls_if_else"])
);
this.setTooltip(Blockly.Msg.CONTROLS_IF_TOOLTIP_1);
this.elseifCount_ = 0;
this.elseCount_ = 0;
},
/**
* Create XML to represent the number of else-if and else inputs.
* @return {Element} XML storage element.
* @this Blockly.Block
*/
mutationToDom: function () {
if (!this.elseifCount_ && !this.elseCount_) {
return null;
}
var container = document.createElement("mutation");
if (this.elseifCount_) {
container.setAttribute("elseif", this.elseifCount_);
}
if (this.elseCount_) {
container.setAttribute("else", 1);
}
return container;
},
/**
* Parse XML to restore the else-if and else inputs.
* @param {!Element} xmlElement XML storage element.
* @this Blockly.Block
*/
domToMutation: function (xmlElement) {
this.elseifCount_ = parseInt(xmlElement.getAttribute("elseif"), 10) || 0;
this.elseCount_ = parseInt(xmlElement.getAttribute("else"), 10) || 0;
this.updateShape_();
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
* @return {!Blockly.Block} Root block in mutator.
* @this Blockly.Block
*/
decompose: function (workspace) {
var containerBlock = workspace.newBlock("controls_if_if");
containerBlock.initSvg();
var connection = containerBlock.nextConnection;
for (var i = 1; i <= this.elseifCount_; i++) {
var elseifBlock = workspace.newBlock("controls_if_elseif");
elseifBlock.initSvg();
connection.connect(elseifBlock.previousConnection);
connection = elseifBlock.nextConnection;
}
if (this.elseCount_) {
var elseBlock = workspace.newBlock("controls_if_else");
elseBlock.initSvg();
connection.connect(elseBlock.previousConnection);
}
return containerBlock;
},
/**
* Reconfigure this block based on the mutator dialog's components.
* @param {!Blockly.Block} containerBlock Root block in mutator.
* @this Blockly.Block
*/
compose: function (containerBlock) {
var clauseBlock = containerBlock.nextConnection.targetBlock();
// Count number of inputs.
this.elseifCount_ = 0;
this.elseCount_ = 0;
var valueConnections = [null];
var statementConnections = [null];
var elseStatementConnection = null;
while (clauseBlock) {
switch (clauseBlock.type) {
case "controls_if_elseif":
this.elseifCount_++;
valueConnections.push(clauseBlock.valueConnection_);
statementConnections.push(clauseBlock.statementConnection_);
break;
case "controls_if_else":
this.elseCount_++;
elseStatementConnection = clauseBlock.statementConnection_;
break;
default:
throw new Error("Unknown block type.");
}
clauseBlock =
clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock();
}
this.updateShape_();
// Reconnect any child blocks.
for (var i = 1; i <= this.elseifCount_; i++) {
Blockly.Mutator.reconnect(valueConnections[i], this, "IF" + i);
Blockly.Mutator.reconnect(statementConnections[i], this, "DO" + i);
}
Blockly.Mutator.reconnect(elseStatementConnection, this, "ELSE");
},
/**
* Store pointers to any connected child blocks.
* @param {!Blockly.Block} containerBlock Root block in mutator.
* @this Blockly.Block
*/
saveConnections: function (containerBlock) {
var clauseBlock = containerBlock.nextConnection.targetBlock();
var i = 1;
var inputDo;
while (clauseBlock) {
switch (clauseBlock.type) {
case "controls_if_elseif":
var inputIf = this.getInput("IF" + i);
inputDo = this.getInput("DO" + i);
clauseBlock.valueConnection_ =
inputIf && inputIf.connection.targetConnection;
clauseBlock.statementConnection_ =
inputDo && inputDo.connection.targetConnection;
i++;
break;
case "controls_if_else":
inputDo = this.getInput("ELSE");
clauseBlock.statementConnection_ =
inputDo && inputDo.connection.targetConnection;
break;
default:
throw new Error("Unknown block type.");
}
clauseBlock =
clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock();
}
},
/**
* Modify this block to have the correct number of inputs.
* @private
* @this Blockly.Block
*/
updateShape_: function () {
// Delete everything.
if (this.getInput("ELSE")) {
this.removeInput("ELSE");
}
var j = 1;
while (this.getInput("IF" + j)) {
this.removeInput("IF" + j);
this.removeInput("DO" + j);
j++;
}
// Rebuild block.
for (var i = 1; i <= this.elseifCount_; i++) {
this.appendValueInput("IF" + i)
.setCheck(Types.getCompatibleTypes("boolean"))
.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
this.appendStatementInput("DO" + i).appendField(
Blockly.Msg.CONTROLS_IF_MSG_THEN
);
}
if (this.elseCount_) {
this.appendStatementInput("ELSE").appendField(
Blockly.Msg.CONTROLS_IF_MSG_ELSE
);
}
},
};
Blockly.Blocks["controls_if_if"] = {
/**
* Mutator block for if container.
* @this Blockly.Block
*/
init: function () {
this.setColour(getColour().logic);
this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF);
this.setNextStatement(true);
this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP);
this.contextMenu = false;
},
};
Blockly.Blocks["controls_if_elseif"] = {
/**
* Mutator bolck for else-if condition.
* @this Blockly.Block
*/
init: function () {
this.setColour(getColour().logic);
this.appendDummyInput().appendField(
Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF
);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP);
this.contextMenu = false;
},
};
Blockly.Blocks["controls_if_else"] = {
/**
* Mutator block for else condition.
* @this Blockly.Block
*/
init: function () {
this.setColour(getColour().logic);
this.appendDummyInput().appendField(
Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE
);
this.setPreviousStatement(true);
this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP);
this.contextMenu = false;
},
};
Blockly.defineBlocksWithJsonArray([
// BEGIN JSON EXTRACT
// Block for boolean data type: true and false.
{
type: "logic_boolean",
message0: "%1",
args0: [
{
type: "field_dropdown",
name: "BOOL",
options: [
["%{BKY_LOGIC_BOOLEAN_TRUE}", "TRUE"],
["%{BKY_LOGIC_BOOLEAN_FALSE}", "FALSE"],
], ],
}, message1: '%{BKY_CONTROLS_IF_MSG_THEN} %1',
], args1: [
output: Types.BOOLEAN.typeName, {
style: "logic_blocks", type: 'input_statement',
tooltip: "%{BKY_LOGIC_BOOLEAN_TOOLTIP}", name: 'DO0'
helpUrl: "%{BKY_LOGIC_BOOLEAN_HELPURL}", }
},
{
type: "controls_ifelse",
message0: "%{BKY_CONTROLS_IF_MSG_IF} %1",
args0: [
{
type: "input_value",
name: "IF0",
check: Types.getCompatibleTypes("boolean"),
},
],
message1: "%{BKY_CONTROLS_IF_MSG_THEN} %1",
args1: [
{
type: "input_statement",
name: "DO0",
},
],
message2: "%{BKY_CONTROLS_IF_MSG_ELSE} %1",
args2: [
{
type: "input_statement",
name: "ELSE",
},
],
previousStatement: null,
nextStatement: null,
style: "logic_blocks",
tooltip: "%{BKYCONTROLS_IF_TOOLTIP_2}",
helpUrl: "%{BKY_CONTROLS_IF_HELPURL}",
extensions: ["controls_if_tooltip"],
},
// Block for comparison operator.
{
type: "logic_compare",
message0: "%1 %2 %3",
args0: [
{
type: "input_value",
name: "A",
},
{
type: "field_dropdown",
name: "OP",
options: [
["=", "EQ"],
["\u2260", "NEQ"],
["\u200F<", "LT"],
["\u200F\u2264", "LTE"],
["\u200F>", "GT"],
["\u200F\u2265", "GTE"],
], ],
}, previousStatement: null,
{ nextStatement: null,
type: "input_value", colour: '#b063c5',
name: "B", tooltip: '%{BKYCONTROLS_IF_TOOLTIP_2}',
}, helpUrl: '%{BKY_CONTROLS_IF_HELPURL}',
], extensions: ['controls_if_tooltip']
inputsInline: true, },
output: Types.BOOLEAN.typeName, {
style: "logic_blocks", type: 'controls_ifelse',
helpUrl: "%{BKY_LOGIC_COMPARE_HELPURL}", message0: '%{BKY_CONTROLS_IF_MSG_IF} %1',
extensions: ["logic_compare", "logic_op_tooltip"], args0: [
}, {
// Block for logical operations: 'and', 'or'. type: 'input_value',
{ name: 'IF0',
type: "logic_operation", check: 'Boolean'
message0: "%1 %2 %3", }
args0: [
{
type: "input_value",
name: "A",
check: Types.getCompatibleTypes("boolean"),
},
{
type: "field_dropdown",
name: "OP",
options: [
["%{BKY_LOGIC_OPERATION_AND}", "AND"],
["%{BKY_LOGIC_OPERATION_OR}", "OR"],
], ],
}, message1: '%{BKY_CONTROLS_IF_MSG_THEN} %1',
{ args1: [
type: "input_value", {
name: "B", type: 'input_statement',
check: Types.getCompatibleTypes("boolean"), name: 'DO0'
}, }
], ],
inputsInline: true, message2: '%{BKY_CONTROLS_IF_MSG_ELSE} %1',
output: Types.BOOLEAN.typeName, args2: [
style: "logic_blocks", {
helpUrl: "%{BKY_LOGIC_OPERATION_HELPURL}", type: 'input_statement',
extensions: ["logic_op_tooltip"], name: 'ELSE'
}, }
// Block for negation. ],
{ previousStatement: null,
type: "logic_negate", nextStatement: null,
message0: "%{BKY_LOGIC_NEGATE_TITLE}", colour: '#b063c5',
args0: [ tooltip: '%{BKYCONTROLS_IF_TOOLTIP_2}',
{ helpUrl: '%{BKY_CONTROLS_IF_HELPURL}',
type: "input_value", extensions: ['controls_if_tooltip']
name: "BOOL",
check: Types.getCompatibleTypes("boolean"),
},
],
output: Types.BOOLEAN.typeName,
style: "logic_blocks",
tooltip: "%{BKY_LOGIC_NEGATE_TOOLTIP}",
helpUrl: "%{BKY_LOGIC_NEGATE_HELPURL}",
},
// Block for null data type.
{
type: "logic_null",
message0: "%{BKY_LOGIC_NULL}",
output: null,
style: "logic_blocks",
tooltip: "%{BKY_LOGIC_NULL_TOOLTIP}",
helpUrl: "%{BKY_LOGIC_NULL_HELPURL}",
},
// Block for ternary operator.
{
type: "logic_ternary",
message0: "%{BKY_LOGIC_TERNARY_CONDITION} %1",
args0: [
{
type: "input_value",
name: "IF",
check: Types.getCompatibleTypes("boolean"),
},
],
message1: "%{BKY_LOGIC_TERNARY_IF_TRUE} %1",
args1: [
{
type: "input_value",
name: "THEN",
check: Types.getCompatibleTypes("boolean"),
},
],
message2: "%{BKY_LOGIC_TERNARY_IF_FALSE} %1",
args2: [
{
type: "input_value",
name: "ELSE",
check: Types.getCompatibleTypes("boolean"),
},
],
output: null,
style: "logic_blocks",
tooltip: "%{BKY_LOGIC_TERNARY_TOOLTIP}",
helpUrl: "%{BKY_LOGIC_TERNARY_HELPURL}",
extensions: ["logic_ternary"],
},
]); // END JSON EXTRACT (Do not delete this comment.)
Blockly.Blocks["logic_compare"] = {
/**
* Block for comparison operator.
* @this Blockly.Block
*/
init: function () {
var OPERATORS = this.RTL
? [
["=", "EQ"],
["\u2260", "NEQ"],
[">", "LT"],
["\u2265", "LTE"],
["<", "GT"],
["\u2264", "GTE"],
]
: [
["=", "EQ"],
["\u2260", "NEQ"],
["<", "LT"],
["\u2264", "LTE"],
[">", "GT"],
["\u2265", "GTE"],
];
this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);
this.setColour(getColour().logic);
this.setOutput(true, Types.BOOLEAN.typeName);
this.appendValueInput("A");
this.appendValueInput("B").appendField(
new Blockly.FieldDropdown(OPERATORS),
"OP"
);
this.setInputsInline(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function () {
var op = thisBlock.getFieldValue("OP");
var TOOLTIPS = {
EQ: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,
NEQ: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,
LT: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,
LTE: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,
GT: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,
GTE: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE,
};
return TOOLTIPS[op];
});
},
/**
* Called whenever anything on the workspace changes.
* Prevent mismatched types from being compared.
* @param {!Blockly.Events.Abstract} e Change event.
* @this Blockly.Block
*/
onchange: function (e) {
var blockA = this.getInputTargetBlock("A");
var blockB = this.getInputTargetBlock("B");
if (blockA === null && blockB === null) {
this.getInput("A").setCheck(null);
this.getInput("B").setCheck(null);
} }
if (blockA !== null && blockB === null) { ]);
this.getInput("A").setCheck(
getCompatibleTypes(blockA.outputConnection.check_[0])
);
this.getInput("B").setCheck(
getCompatibleTypes(blockA.outputConnection.check_[0])
);
}
if (blockB !== null && blockA === null) {
this.getInput("B").setCheck(
getCompatibleTypes(blockB.outputConnection.check_[0])
);
this.getInput("A").setCheck(
getCompatibleTypes(blockB.outputConnection.check_[0])
);
}
},
};
Blockly.Blocks["switch_case"] = {
init: function () {
this.setColour(getColour().logic);
this.setPreviousStatement(true);
this.setTooltip(Blockly.Msg.cases_tooltip);
this.setNextStatement(true);
this.appendValueInput("CONDITION").appendField(Blockly.Msg.cases_switch);
this.appendValueInput("CASECONDITION0").appendField(
Blockly.Msg.cases_condition
);
this.appendStatementInput("CASE0").appendField(Blockly.Msg.cases_do);
this.setMutator(new Blockly.Mutator(["case_incaseof", "case_default"]));
this.caseCount_ = 0;
this.defaultCount_ = 0;
},
mutationToDom: function () {
if (!this.caseCount_ && !this.defaultCount_) {
return null;
}
var container = document.createElement("mutation");
if (this.caseCount_) {
container.setAttribute("case", this.caseCount_);
}
if (this.defaultCount_) {
container.setAttribute("default", 1);
}
return container;
},
domToMutation: function (xmlElement) {
this.caseCount_ = parseInt(xmlElement.getAttribute("case"), 10);
this.defaultCount_ = parseInt(xmlElement.getAttribute("default"), 10);
for (var x = 0; x <= this.caseCount_; x++) {
this.appendValueInput("CASECONDITION" + x).appendField(
Blockly.Msg.cases_condition
);
this.appendStatementInput("CASE" + x).appendField(Blockly.Msg.cases_do);
}
if (this.defaultCount_) {
this.appendStatementInput("ONDEFAULT").appendField("default");
}
},
decompose: function (workspace) {
var containerBlock = workspace.newBlock("control_case");
containerBlock.initSvg();
var connection = containerBlock.getInput("STACK").connection;
for (var x = 1; x <= this.caseCount_; x++) {
var caseBlock = workspace.newBlock("case_incaseof");
caseBlock.initSvg();
connection.connect(caseBlock.previousConnection);
connection = caseBlock.nextConnection;
}
if (this.defaultCount_) {
var defaultBlock = Blockly.Block.obtain(workspace, "case_default");
defaultBlock.initSvg();
connection.connect(defaultBlock.previousConnection);
}
return containerBlock;
},
compose: function (containerBlock) {
//Disconnect all input blocks and remove all inputs.
if (this.defaultCount_) {
this.removeInput("ONDEFAULT");
}
this.defaultCount_ = 0;
for (var x = this.caseCount_; x > 0; x--) {
this.removeInput("CASECONDITION" + x);
this.removeInput("CASE" + x);
}
this.caseCount_ = 0;
var caseBlock = containerBlock.getInputTargetBlock("STACK");
while (caseBlock) {
switch (caseBlock.type) {
case "case_incaseof":
this.caseCount_++;
var caseconditionInput = this.appendValueInput(
"CASECONDITION" + this.caseCount_
).appendField(Blockly.Msg.cases_condition);
var caseInput = this.appendStatementInput(
"CASE" + this.caseCount_
).appendField(Blockly.Msg.cases_do);
if (caseBlock.valueConnection_) {
caseconditionInput.connection.connect(caseBlock.valueConnection_);
}
if (caseBlock.statementConnection_) {
caseInput.connection.connect(caseBlock.statementConnection_);
}
break;
case "case_default":
this.defaultCount_++;
var defaultInput =
this.appendStatementInput("ONDEFAULT").appendField("default");
if (caseBlock.statementConnection_) {
defaultInput.connection.connect(caseBlock.statementConnection_);
}
break;
default:
throw new Error("Unknown block type.");
}
caseBlock =
caseBlock.nextConnection && caseBlock.nextConnection.targetBlock();
}
},
saveConnections: function (containerBlock) {
var caseBlock = containerBlock.getInputTargetBlock("STACK");
var x = 1;
while (caseBlock) {
switch (caseBlock.type) {
case "case_incaseof":
var caseconditionInput = this.getInput("CASECONDITION" + x);
var caseInput = this.getInput("CASE" + x);
caseBlock.valueConnection_ =
caseconditionInput &&
caseconditionInput.connection.targetConnection;
caseBlock.statementConnection_ =
caseInput && caseInput.connection.targetConnection;
x++;
break;
case "case_default":
var defaultInput = this.getInput("ONDEFAULT");
caseBlock.satementConnection_ =
defaultInput && defaultInput.connection.targetConnection;
break;
default:
throw new Error("Unknown block type");
}
caseBlock =
caseBlock.nextConnection && caseBlock.nextConnection.targetBlock();
}
},
};
Blockly.Blocks["control_case"] = {
init: function () {
this.setColour(getColour().logic);
this.appendDummyInput().appendField(Blockly.Msg.cases_switch);
this.appendStatementInput("STACK");
this.setTooltip("--Placeholder--");
this.contextMenu = false;
},
};
Blockly.Blocks["case_incaseof"] = {
init: function () {
this.setColour(getColour().logic);
this.appendDummyInput().appendField(Blockly.Msg.cases_add);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip("--Placeholder--");
this.contextMenu = false;
},
};
Blockly.Blocks["case_default"] = {
init: function () {
this.setColour(getColour().logic);
this.appendValueInput("default").appendField("default");
this.setPreviousStatement(true);
this.setNextStatement(false);
this.setTooltip(
"This function will run if there aren't any matching cases."
);
this.contextMenu = false;
},
};

View File

@ -1,213 +1,51 @@
import Blockly from 'blockly'; import Blockly from 'blockly';
import { getColour } from '../helpers/colour';
import { getCompatibleTypes } from '../helpers/types'
import * as Types from '../helpers/types';
Blockly.Blocks['controls_whileUntil'] = { Blockly.defineBlocksWithJsonArray([
/** {
* Block for 'do while/until' loop. type: 'controls_for',
* @this Blockly.Block message0: 'count with %1 from %2 to %3 by adding %4',
*/ args0: [
init: function () { {
var OPERATORS = type: 'field_variable',
[[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE, 'WHILE'], name: 'VAR',
[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL, 'UNTIL']]; variable: null,
this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL); variableTypes: ['Number'],
this.setColour(getColour().loops); defaultType: 'Number',
this.appendValueInput('BOOL') createNewVariable: true,
.setCheck(getCompatibleTypes('boolean')) showOnlyVariableAssigned: false,
.appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE'); },
this.appendStatementInput('DO') {
.appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO); type: 'input_value',
this.setPreviousStatement(true); name: 'FROM',
this.setNextStatement(true); check: 'Number',
// Assign 'this' to a variable for use in the tooltip closure below. align: 'RIGHT',
var thisBlock = this; },
this.setTooltip(function () { {
var op = thisBlock.getFieldValue('MODE'); type: 'input_value',
var TOOLTIPS = { name: 'TO',
'WHILE': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE, check: 'Number',
'UNTIL': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL align: 'RIGHT',
}; },
return TOOLTIPS[op]; {
}); type: 'input_value',
} name: 'BY',
}; check: 'Number',
align: 'RIGHT',
Blockly.Blocks['controls_for'] = { },
/** ],
* Block for 'for' loop. message1: '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1',
* @this Blockly.Block args1: [
*/ {
init: function () { type: 'input_statement',
this.jsonInit({ name: 'DO',
"message0": Blockly.Msg.CONTROLS_FOR_TITLE, },
"args0": [ ],
{ inputsInline: false,
"type": "field_variable", previousStatement: null,
"name": "VAR", nextStatement: null,
"defaultType": Types.NUMBER.typeName, helpUrl: '%{BKY_CONTROLS_FOR_HELPURL}',
"variable": null extensions: ['contextMenu_newGetVariableBlock', 'controls_for_tooltip'],
},
{
"type": "input_value",
"name": "FROM",
"check": getCompatibleTypes('int'),
"align": "RIGHT"
},
{
"type": "input_value",
"name": "TO",
"check": getCompatibleTypes('int'),
"align": "RIGHT"
},
{
"type": "input_value",
"name": "BY",
"check": getCompatibleTypes('int'),
"align": "RIGHT"
}
],
"inputsInline": true,
"previousStatement": null,
"nextStatement": null,
"colour": getColour().loops,
"helpUrl": Blockly.Msg.CONTROLS_FOR_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function () {
return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace('%1',
thisBlock.getFieldValue('VAR'));
});
}
};
Blockly.Blocks['controls_forEach'] = {
/**
* Block for 'for each' loop.
* @this Blockly.Block
*/
init: function () {
this.jsonInit({
"message0": Blockly.Msg.CONTROLS_FOREACH_TITLE,
"args0": [
{
"type": "field_variable",
"name": "VAR",
"defaultType": Types.NUMBER.typeName,
"variable": null
},
{
"type": "input_value",
"name": "LIST",
"check": getCompatibleTypes('Array')
}
],
"previousStatement": null,
"nextStatement": null,
"colour": getColour().loops,
"helpUrl": Blockly.Msg.CONTROLS_FOREACH_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function () {
return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace('%1',
thisBlock.getFieldValue('VAR'));
});
}, },
customContextMenu: Blockly.Blocks['controls_for'].customContextMenu, ]);
/** @returns {!string} The type of the variable used in this block */
getVarType: function (varName) {
return Blockly.Types.NUMBER;
}
};
Blockly.Blocks['controls_flow_statements'] = {
/**
* Block for flow statements: continue, break.
* @this Blockly.Block
*/
init: function () {
var OPERATORS =
[[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK, 'BREAK'],
[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE, 'CONTINUE']];
this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);
this.setColour(getColour().loops);
this.appendDummyInput()
.appendField(new Blockly.FieldDropdown(OPERATORS), 'FLOW');
this.setPreviousStatement(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function () {
var op = thisBlock.getFieldValue('FLOW');
var TOOLTIPS = {
'BREAK': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK,
'CONTINUE': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE
};
return TOOLTIPS[op];
});
},
/**
* Called whenever anything on the workspace changes.
* Add warning if this flow block is not nested inside a loop.
* @param {!Blockly.Events.Abstract} e Change event.
* @this Blockly.Block
*/
onchange: function (e) {
var legal = false;
// Is the block nested in a loop?
var block = this;
do {
if (this.LOOP_TYPES.indexOf(block.type) !== -1) {
legal = true;
break;
}
block = block.getSurroundParent();
} while (block);
if (legal) {
this.setWarningText(null);
} else {
this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING);
}
},
/**
* List of block types that are loops and thus do not need warnings.
* To add a new loop type add this to your code:
* Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop');
*/
LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach',
'controls_for', 'controls_whileUntil']
};
Blockly.Blocks['controls_repeat_ext'] = {
/**
* Block for repeat n times (external number).
* @this Blockly.Block
*/
init: function () {
this.jsonInit({
"message0": Blockly.Msg.CONTROLS_REPEAT_TITLE,
"args0": [
{
"type": "input_value",
"name": "TIMES",
"check": getCompatibleTypes('int'),
}
],
"previousStatement": null,
"nextStatement": null,
"colour": getColour().loops,
"tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,
"helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
}
};

Some files were not shown because too many files have changed in this diff Show More