MERGE FE AND API ==== #monorepo

pull/336/head
Rick Carlino 2017-06-29 13:54:02 -05:00
parent 17317e2465
commit feb89b237d
419 changed files with 49964 additions and 11 deletions

52
.gitignore vendored
View File

@ -1,17 +1,18 @@
**.orig
_yardoc
.bundle
.config
.DS_Store
.rspec
.rvmrc
.vscode/
.yardoc
*.gem
*.log
*.pem
*.rbc
*.rbc
**.orig
*journal
.DS_Store
.bundle
.config
.rspec
.rvmrc
.vscode/
.yardoc
/.bundle
/config/application.yml
/coverage/
@ -24,14 +25,14 @@
/spec/tmp
/tmp
/vendor/bundle
InstalledFiles
_yardoc
api_docs.md
capybara-*.html
config/database.yml
coverage
doc/
erd.pdf
InstalledFiles
latest_corpus.ts
lib/bundler/man
log/*.log
node_modules/
@ -47,4 +48,33 @@ spec/reports
test/tmp
test/version_tmp
tmp
latest_corpus.ts
# =====
frontend/*.js.map
frontend/.DS_Store
frontend/.vscode
frontend//src/config.json
frontend/bower_components
frontend/build
frontend/coverage
frontend/dist
frontend/jest
frontend/.nvmrc
frontend/node_modules
frontend/bundle.*.js*
frontend/front_page.*.js*
frontend/verify.*.js*
frontend/public/app-index.js
frontend/public/app-resources/*.css*
frontend/public/app-resources/*.js*
frontend/public/app-resources/chunks/*.js*
frontend/public/app/*
frontend/public/front_page.js
frontend/public/index.html
frontend/public/password_reset.html
frontend/public/tos_update.html
frontend/public/verify.html
frontend/public/*.eot
frontend/*.log
frontend/*.log.*

View File

@ -0,0 +1 @@
To get started, <a href="https://www.clahub.com/agreements/FarmBot/farmbot-web-frontend">sign the Contributor License Agreement</a>.

View File

@ -0,0 +1,5 @@
# Expected Behavior
# Actual Behavior
# Steps to Reproduce

21
frontend/LICENSE 100644
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Farmbot.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

63
frontend/README.md 100644
View File

@ -0,0 +1,63 @@
[![Build Status](https://travis-ci.org/FarmBot/farmbot-web-frontend.svg?branch=master)](https://travis-ci.org/FarmBot/farmbot-web-frontend)
[![codebeat badge](https://codebeat.co/badges/73a8b8b6-2683-4bea-a759-e3a07210e4ca)](https://codebeat.co/projects/github-com-rickcarlino-farmbot-web-frontend-master)
[![Coverage Status](https://coveralls.io/repos/github/FarmBot/farmbot-web-frontend/badge.svg?branch=master)](https://coveralls.io/github/FarmBot/farmbot-web-frontend?branch=master) (we're working on it)
# Do I need this?
This repository is intended for *software developers* who wish to modify the frontend of the FarmBot Web App or host it on their own server. **If you are not a developer**, you are highly encouraged to use the free hosted web app at [my.farmbot.io](http://my.farmbot.io/).
If you would like to report a problem with the web app, please [submit an issue](https://github.com/FarmBot/farmbot-web-frontend/issues/new).
# FarmBot Web Frontend
This is the Javascript / HTML / CSS of the FarmBot web app. It depends on a [backend API](https://github.com/FarmBot/Farmbot-Web-API) (my.farmbot.io by default).
# Developer Setup
**[LATEST STABLE VERSION IS HERE](https://github.com/FarmBot/farmbot-web-frontend/releases)** :star: :star: :star:
0. [Install node](https://nodejs.org/en/download/) if you haven't already.
1. [Install Google Chrome](https://www.google.com/chrome/) for best app experience.
2. `git clone https://github.com/FarmBot/farmbot-web-frontend.git`
3. `cd farmbot-web-frontend`
4. `npm install`
5. `npm start`
6. Visit `http://localhost:8080/`
# Deploy to Production
**NOTE:** The [Web API](https://github.com/FarmBot/Farmbot-Web-API) deployment will automatically build the latest version of the frontend and mount it in the web server. The instructions below are intended for reference purposes, or for users who wish to host their frontend code on a different server than their API.
1. (optional, usually not needed) If you have an NPM module that needs to get baked into the build, pass the NPM modules name in as `NPM_ADDON=foo`
2. run `npm run build`
3. Copy the contents of `/app` into your webserver and it will be accessible via `/`.
4. Visit `/` on your web server to verify installation.
5. [Submit an issue](https://github.com/FarmBot/farmbot-web-frontend/issues/new?title=Installation%20Failure) if you hit problems during the installation.
# Debugging external devices (DEV ONLY)
[Weinre](https://www.npmjs.com/package/weinre) is included in this project.
To utilize it, head over to the `/src` directory of the app, add a file called
`config.json`, and populate it with this:
```
{
"ip_address": "YOUR-IP-ADDRESS"
}
```
Then, in your console, `weinre --boundHost YOUR-IP-ADDRESS --httpPort 8081`.
This should run in tandem with the rest of your project.
Then navigate to http://YOUR-IP-ADDRESS:8081/client/#anonymous.
After adding the `config.json`, you may be required to `npm start` again.
# Want to Help?
Check out the [Low Hanging Fruit](https://github.com/FarmBot/farmbot-web-frontend/search?l=typescript&q=TODO&utf8=%E2%9C%93).
Also, if you're experiencing UI/UX issues, please include any possible specifications (device type, device OS, and device browser) to help in the debugging process. Bonus points for GIFs and screenshots. :fist:
# Translating the app into your language
Thanks for your interest in internationalizing the FarmBot web app! To add translations:
1. Fork this repo
2. Create a `yy.js` file in ``/public/app-resources/languages/`` where `yy` is your language's [language code](http://www.science.co.il/Language/Locale-codes.php). Eg: `ru` for Russian. If your language already has a file, then you can skip this step.
3. Search the application for calls to `t()`. Any file that imports `from "i18next"` will have strings that require translation.
4. When you have updated or added new translations, commit/push your changes and submit a pull request.

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

23
frontend/notes.md 100644
View File

@ -0,0 +1,23 @@
# Encoder Scaling
|GCode|Param|Name|
|---|---|---|---|
|F15 |115|Enc. scaling|
|F16 |116|Enc. scaling|
|F17?|117|Enc. scaling|
|F05?|105|Enc. type|
|F06?|106|Enc. type|
|F07?|107|Enc. type|
|F22 (write 36)|Add to FarmBot JS|Enable X2|
|F22 (write 37)|Add to FarmBot JS|Invert X2|
|????|???|Set Home X|
|????|???|Set Home Y|
|????|???|Set Home Z|
# MCU Reset button
```
{
kind: "factory_reset"
args: {package: "arduino_firmware" || "farmbot_os"}
}
```

View File

View File

@ -0,0 +1,118 @@
{
"name": "farmbot-web-frontend",
"version": "1.1.0",
"description": "Farmbot web frontend.",
"main": "dist/entry.js",
"repository": {
"type": "git",
"url": "https://github.com/farmbot/farmbot-web-frontend"
},
"scripts": {
"coverage": "midori coverage/remapped/html/index.html",
"build": "node_modules/webpack/bin/webpack.js --config tools/webpack.config.prd.js --display-error-details",
"start": "webpack-dev-server --config tools/webpack.config.dev.js --content-base public/ --host 0.0.0.0",
"test": "jest --coverage --no-cache && cat ./coverage/remapped/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"just_test": "jest --coverage --no-cache"
},
"keywords": [
"farmbot"
],
"author": "farmbot.io",
"license": "MIT",
"optionalDependencies": {
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
"@blueprintjs/core": "^1.20.0",
"@blueprintjs/labs": "^0.1.0",
"@types/deep-freeze": "^0.1.0",
"@types/enzyme": "^2.7.8",
"@types/fastclick": "^1.0.28",
"@types/handlebars": "^4.0.31",
"@types/history": "^2.0.39",
"@types/i18next": "^2.3.32",
"@types/jest": "^19.2.2",
"@types/lodash": "^4.14.64",
"@types/markdown-it": "0.0.1",
"@types/mqtt": "0.0.32",
"@types/node": "^6.0.63",
"@types/react": "^0.14.57",
"@types/react-color": "^2.11.0",
"@types/react-dom": "^0.14.18",
"@types/react-redux": "^4.4.32",
"@types/react-router": "^3.0.0",
"@types/redux": "^3.6.31",
"axios": "^0.14.0",
"boxed_value": "^1.0.0",
"coveralls": "^2.13.0",
"css-loader": "^0.25.0",
"deep-freeze": "^0.0.1",
"enzyme": "^2.8.1",
"extract-text-webpack-plugin": "^2.0.0-beta.5",
"farmbot": "4.0.7",
"farmbot-toastr": "^1.0.2",
"fastclick": "^1.0.6",
"file-loader": "^0.10.0",
"handlebars": "^4.0.5",
"i18next": "^3.4.3",
"imports-loader": "^0.7.0",
"jest": "^19.0.2",
"json-loader": "^0.5.4",
"lodash": "^3.10.1",
"markdown-it": "^8.2.1",
"markdown-it-emoji": "^1.3.0",
"moment": "2.15.2",
"node-sass": "^3.10.0",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"react": "^15.5.4",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-test-utils": "^15.5.1",
"react-color": "^2.11.1",
"react-dom": "^15.5.4",
"react-redux": "^4.4.1",
"react-router": "^3.0.0",
"react-test-renderer": "^15.5.4",
"redux": "^3.3.1",
"redux-immutable-state-invariant": "^1.2.3",
"redux-thunk": "^2.0.1",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.0",
"ts-jest": "^19.0.9",
"ts-loader": "^1.0.0",
"tslint": "4.5.1",
"typescript": "2.3",
"url-loader": "^0.5.7",
"webpack": "^2.2.0-rc.3",
"webpack-uglify-js-plugin": "^1.1.9",
"weinre": "^2.0.0-pre-I0Z7U9OV",
"yarn": "^0.23.4"
},
"devDependencies": {
"jscpd": "^0.6.10",
"webpack-notifier": "^1.5.0"
},
"jest": {
"setupFiles": [
"./src/unmock_i18next.ts",
"./src/__test_support__/locastorage.js"
],
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"testResultsProcessor": "<rootDir>/node_modules/ts-jest/coverageprocessor.js",
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*.{ts,tsx}"
],
"coverageReporters": [
"html",
"json"
]
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<title>The change you wanted was rejected (422)</title>
<style>
body {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
}
div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 4em 0 4em;
}
h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
body > p {
width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
<body>
<!-- This file lives in public/422.html -->
<div class="dialog">
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
<p>If you are the application owner check the logs for more information.</p>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<style type="text/css">
.st0{fill:#316131;}
.st1{fill:#498C49;}
.st2{fill:#497F49;}
.st3{fill:#DBE5DB;}
.st4{fill:#417F41;}
.st5{fill:none;stroke:#FFFFFF;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st6{fill:#00FFFF;}
.st7{fill:#5DB25D;}
.st8{fill:none;stroke:#5DB25D;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st9{fill:none;stroke:#529952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st10{fill:#529952;}
.st11{fill:#FFFFFF;}
.st12{fill:#DBE8DB;}
.st13{fill:#75CB76;}
.st14{fill:#5E9F5F;}
.st15{fill:#57A357;}
.st16{fill:#9BE085;}
.st17{fill:#74B56F;}
</style>
<g>
<path class="st0" d="M424,125.9c-3.7-4.8-8.8-8.3-14.4-10c1.1-31.5-5.3-56-10.9-71c-6-16.1-16.7-36.5-33.5-42.1
c-2.5-0.8-5.1-1.4-7.9-1.6l0,0l0,0c-17-1.3-36.9,9.9-47.2,16.7c-12.8,8.4-31.5,23.5-48.7,47.8c-2.5-0.9-5.1-1.5-7.7-1.7
c-11.8-0.9-22.8,5.3-28.2,15.7c-4.2,8.2-7.9,16.9-10.8,25.7c-9.4,27.8-11.9,54.2-7.5,80.5c-3.1,0-6.2,0.4-9.1,1.4
c-24.6-23.9-51.2-33.8-67.1-35.1c-7.4-0.6-14,0.6-19.5,3.4c-11,5.8-18.7,18.8-22.8,38.7c-2.3,11.2-4.2,28.8-0.7,50.3
c-2.9,2.2-5.4,4.9-7.3,8.1c-4.2,7.1-5.1,15.6-2.4,23.4c2.3,6.7,5.2,13.3,8.5,19.6c12.8,24.4,30,42.1,52.7,54.1
c14.9,7.9,30.3,12.2,44.6,15.1c12.3,34.5,17.8,63.8,17.5,93.7l0.1,0c0,0.4-0.1,0.8-0.1,1.2c-1.6,20,13.9,37.6,34.5,39.2
c21.7,1.7,38.8-13.1,40.5-35.1l0-0.2c0.6-8,6.3-78.3,27.2-176c49-16,95.6-42.7,118.9-111.8c3-8.8,5.3-17.8,6.9-26.8
C431.1,140.7,429,132.4,424,125.9z"/>
<path class="st15" d="M204.4,211.4l-6.6,6.7c-1.5,1.5-4,1.4-5.3-0.3c-2.8-3.6-5.7-6.9-8.7-10c-25-25.9-54.3-34.1-61.1-30.5
c-6.9,3.6-16.8,32.3-9.8,67.6c0.8,4.1,1.9,8.4,3.2,12.7c0.6,2-0.7,4.1-2.8,4.5l-9.3,1.5c-2,0.3-3.3,2.4-2.6,4.4
c1.9,5.4,4.2,10.8,7,16.2C133,331.2,173.2,339,210,344.2c19-35.1,33.3-70.6,8.8-117.5c-2.8-5.4-5.9-10.3-9.3-14.9
C208.3,210,205.8,209.9,204.4,211.4z"/>
<path class="st15" d="M401,139.1l-12.9-0.1c-2.9,0-5.1-2.6-4.7-5.5c0.9-6.2,1.4-12.2,1.7-18c1.9-49.4-17.6-86.1-27.7-89.5
c-10.1-3.4-47.9,13.9-76.3,54.4c-3.3,4.7-6.5,9.8-9.5,15.2c-1.4,2.5-4.7,3.3-7.1,1.5l-10.3-7.7c-2.3-1.7-5.5-1-6.8,1.6
c-3.6,7-6.8,14.4-9.4,22.3c-23.2,68.8,7.1,116,36.8,157.3c52.7-14.5,101.5-33.9,124.7-102.7c2.7-7.9,4.6-15.7,6-23.3
C405.9,141.6,403.8,139.1,401,139.1z"/>
<g>
<path class="st12" d="M127.8,225.6c-1.9-0.2-3.5-1.7-3.6-3.7c-0.5-8.8,0.2-15.7,0.9-19.9c0.3-2.2,2.4-3.6,4.5-3.3
c2.2,0.3,3.6,2.4,3.3,4.5c-0.6,3.9-1.3,10.2-0.8,18.2c0.1,2.2-1.5,4.1-3.7,4.2C128.2,225.6,128,225.6,127.8,225.6z"/>
</g>
<g>
<path class="st3" d="M301.8,86.4c-0.9-0.1-1.7-0.4-2.4-1c-2-1.5-2.3-4.4-0.8-6.4C311.2,63,324,53.3,330.5,49
c2.1-1.4,4.9-0.8,6.3,1.3c1.4,2.1,0.8,4.9-1.3,6.3c-6.1,4-18,13.1-29.9,28.2C304.7,85.9,303.2,86.5,301.8,86.4z"/>
</g>
<g>
<path class="st16" d="M239.7,379l-13.1,59.9c0,7.5,0.1,15-0.6,22.7c-0.5,6.1,4.6,12.2,11.9,12.8c8.6,0.7,13.5-4.6,14.1-12.6
c0.2-2.1,8.8-121.9,50.4-272.6c0.6-2.3,2-4.3,3.8-5.8c5.5-4.4,15.2-10.7,28.7-13c23.3-4,38.1-19.8,38.7-20.4
c1.6-1.8,1.5-4.5-0.2-6.1c-1.8-1.6-4.5-1.5-6.1,0.2c-0.1,0.1-13.3,14.3-33.8,17.8c-8.4,1.4-15.5,4.2-21.3,7.3
c-1.8,0.9-3.8-0.7-3.2-2.6c7.9-26.5,16.8-52.2,26.8-76.2c0,0,1.9-4.5-2.5-6c-4.7-1.6-5.9,3.7-5.9,3.7
c-10.1,24.3-19.5,49.6-28.1,75.3c-0.6,1.9-3.2,2.1-4.1,0.3c-2.9-5.8-7.1-12.1-13.1-18.1c-1.8-1.8-4.6-1.7-6.3,0.2
c-1.5,1.7-1.3,4.4,0.3,6.1c9.8,9.9,14.1,21,15.9,27.7c0.6,2.1,0.5,4.3-0.1,6.4C291.8,185.8,255.6,297.8,239.7,379z"/>
<ellipse transform="matrix(4.779642e-02 -0.9989 0.9989 4.779642e-02 120.0343 385.4608)" class="st16" cx="262.2" cy="129.8" rx="4.3" ry="4.3"/>
</g>
<path class="st17" d="M238.6,384.4c-14-30.7-31.3-69.2-47.1-98.2c-1.1-2.1-1.6-4.5-1.3-6.8c0.9-7,3.6-17.5,10.9-27.9
c1.3-1.9,1.1-4.6-0.7-6c-2-1.6-4.8-1.2-6.3,0.9c-4.8,6.8-7.9,13.6-9.8,19.7c-0.6,1.8-3.1,2.1-4.1,0.5
c-10.6-17.7-18.3-28.2-19.3-29.6c0,0-2.5-4.3-6.6-1.6c-3.5,2.3-0.9,6-0.9,6c0.2,0.4,7.4,11.2,17.1,30.3c0.9,1.8-0.8,3.8-2.7,3.2
c-6.2-1.9-13.5-3.3-21.9-3.2c-2,0-3.9,1.4-4.3,3.4c-0.6,2.8,1.6,5.3,4.3,5.3c12.7-0.2,23,3.7,29.3,6.9c2.3,1.2,4.2,3.1,5.2,5.5
c21,47.2,46.1,100.9,45.6,166C231.9,410.8,236,397.9,238.6,384.4z"/>
<g>
<ellipse transform="matrix(7.841469e-02 -0.9969 0.9969 7.841469e-02 -120.2107 351.1246)" class="st12" cx="129.8" cy="240.6" rx="4.5" ry="4.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,170 @@
// var HelperNamespace = (function () {
// /**
// * @desc Build a list of all the files that are children of the root isDirectory
// * @param {string} dir The rot isDirectory
// * @param {list} filelist The list of the directories/files already detected
// * @param {string} ext The extension to filter for the files
// */
// function walkSync(dir, filelist, ext) {
// var path = path || require('path');
// var fs = fs || require('fs'),
// files = fs.readdirSync(dir);
// filelist = filelist || [];
// files.forEach(function (file) {
// if (fs.statSync(path.join(dir, file)).isDirectory()) {
// filelist = walkSync(path.join(dir, file), filelist, ext);
// }
// else {
// if (file.indexOf(ext) > 0)
// filelist.push(path.join(dir, file));
// }
// });
// return filelist;
// };
// /**
// * @desc search in the file in parameter to detect the tags
// */
// function searchInFile(path) {
// /* some tags could be missed in this regex
// We detect all the tags in the code
// regex is matching '.t("")' or '{t("")}' or ' t("")' or '(t("")' */
// var REGEX = /[\.|\{|(|\s]t\(\"([\w|\s|\{|\}|\(|\)]*)[\"|,].*\)/g;
// var fs = fs || require('fs')
// // load the file
// var fileContent = fs.readFileSync(path, 'utf8');
// let strArray = [];
// //match all the groups
// var match = REGEX.exec(fileContent);
// while (match != null) {
// strArray.push(match[1])
// match = REGEX.exec(fileContent);
// }
// return strArray;
// }
// /**
// * Get all the tags in the files with extension .ts of the current project
// */
// function getAllTags() {
// const srcPath = __dirname + '/../../..';
// var listFilteredFiles = walkSync(srcPath, [], '.ts');
// var allTags = listFilteredFiles.map(x => searchInFile(x));
// //flatten list of list in a simple list
// var flattenedTags = [].concat.apply([], allTags);
// //distinct
// var uniq = [...new Set(flattenedTags)];
// var sorted = uniq.sort(function (a, b) { return a.localeCompare(b); });
// return sorted;
// }
// /**
// * For debugging
// */
// function logAllTags() {
// console.dir(getAllTags());
// }
// /**
// * Create the translation file or update it with new tags
// * The tags are in the following order:
// * 1. New tags in English that need to be translated (ASC)
// * 2. Tags already translated, and kept because it match an existing tag in src (ASC)
// * 3. Tags already in the file before but not found at the moment in the src (ASC)
// * @param {string} lang The short name of the language. for the language in parameter
// */
// function createOrUpdateTranslationFile(lang) {
// lang = lang || 'en';
// //check current file entry
// const langFilePath = __dirname + '/' + lang + '.js';
// var fs = fs || require('fs')
// try {
// var columnsResult = HelperNamespace.getAllTags();
// var jsonCurrentTagData = {};
// columnsResult.forEach(function (column) {
// jsonCurrentTagData[column] = column;
// });
// var ordered = {};
// var fileContent;
// try {
// //check the file can be openned
// var stats = fs.statSync(langFilePath);
// // load the file
// var fileContent = fs.readFileSync(langFilePath, 'utf8');
// console.log("Current file content: ");
// console.log(fileContent);
// }
// catch (e) { // do this
// console.log("we will create the file: " + langFilePath);
// //If there is no current file, we will create it
// };
// try {
// if (fileContent != undefined) {
// var jsonContent = fileContent
// .replace("module.exports = ", "")
// //regex to delete all comments // and :* in the JSON file
// .replace(/(\/\*(\n|\r|.)*\*\/)|(\/\/.*(\n|\r))/g, "");
// var jsonParsed = JSON.parse(jsonContent);
// Object.keys(jsonParsed).sort().forEach(function (key) {
// ordered[key] = jsonParsed[key];
// });
// }
// } catch (e) {
// console.log("file: " + langFilePath + " contains an error: " + e);
// //If there is an error with the current file content, abort
// return;
// }
// // merge new tags with existing translation
// var result = {};
// var unexistingTag = {};
// // all current tags in English
// for (var key in jsonCurrentTagData) result[key] = jsonCurrentTagData[key];
// for (var key in ordered) {
// // replace current tag with an existing translation
// if (result.hasOwnProperty(key)) {
// delete result[key];
// result[key] = ordered[key];
// }
// // if the tag doesnt exist but a translation exists,
// // put the key/value at the end of the json
// else {
// unexistingTag[key] = ordered[key];
// }
// }
// for (var key in unexistingTag) result[key] = unexistingTag[key];
// var stringJson = JSON.stringify(result, null, " ");
// var newFileContent = "module.exports = " + stringJson;
// fs.writeFileSync(langFilePath, newFileContent);
// } catch (e) {
// console.log("file: " + langFilePath + ". error append: " + e);
// }
// }
// //public functions
// return {
// logAllTags: logAllTags,
// getAllTags: getAllTags,
// createOrUpdateTranslationFile: createOrUpdateTranslationFile
// };
// })();
// // Need to run this cmd in this folder: node _helper.js
// var language = process.argv[2];
// HelperNamespace.createOrUpdateTranslationFile(language)

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "BESCHLEUNIGEN FÜR (Schritte)",
"Account Settings": "Konto-Einstellungen",
"Add": "Hinzufügen",
"Add Farm Event": "Farm Event hinzufügen",
"Age": "Alter",
"Agree to Terms of Service": "Den Nutzungsbedingungen zustimmen",
"ALLOW NEGATIVES": "NEGATIVE WERTE ZULASSEN",
"BACK": "ZURÜCK",
"Bot ready": "Bot bereit",
"CALIBRATE {{axis}}": "{{axis}} KALIBRIEREN",
"CALIBRATION": "KALIBRIERUNG",
"calling FarmBot with credentials": "verbinde zu FarmBot mit Zugangsdaten",
"Camera": "Kamera",
"Choose a species": "Wähle eine Art",
"Confirm Password": "Passwort bestätigen",
"CONTROLLER": "CONTROLLER",
"Copy": "Kopieren",
"Could not download sync data": "Konnte Sync-Daten nicht herunterladen",
"Create Account": "Konto anlegen",
"Create An Account": "Ein Konto anlegen",
"Crop Info": "Pflanzen-Info",
"Data Label": "Daten Label",
"Day {{day}}": "Tag {{day}}",
"days old": "Tage alt",
"Delete": "Löschen",
"DELETE ACCOUNT": "KONTO LÖSCHEN",
"Delete this plant": "Diese Pflanze löschen",
"Designer": "Designer",
"DEVICE": "GERÄT",
"downloading device credentials": "Geräte-Zugangsdaten herunterladen",
"Drag and drop into map": "Auf Karte ziehen und fallen lassen",
"DRAG STEP HERE": "Schritt hier fallen lassen",
"Edit": "Bearbeiten",
"EDIT": "BEARBEITEN",
"Edit Farm Event": "Farm-Event bearbeiten",
"Email": "Email",
"ENABLE ENCODERS": "ENCODER AKTIVIEREN",
"Enter Email": "Email eingeben",
"Enter Password": "Passwort eingeben",
"Error establishing socket connection": "Fehler beim Einrichten der Socket-Verbindung",
"Execute Script": "Skript ausführen",
"EXECUTE SCRIPT": "SKRIPT AUSFÜHREN",
"Execute Sequence": "Sequenz ausführen",
"EXECUTE SEQUENCE": "SEQUENZ AUSFÜHREN",
"Factory Reset": "Fabrik-Einstellungen wiederherstellen",
"Farm Events": "Farm-Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Passwort vergessen",
"GO": "Los",
"I Agree to the Terms of Service": "Ich stimme den Nutzungsbedingungen zu",
"I agree to the terms of use": "Ich stimme den Nutzungsbedingungen zu",
"If Statement": "Wenn-Schleife",
"IF STATEMENT": "WENN-SCHLEIFE",
"Import coordinates from": "Koordination importieren von",
"initiating connection": "Verbindung initialisieren",
"INVERT ENDPOINTS": "ENDPUNKTE INVERTIEREN",
"INVERT MOTORS": "MOTOREN INVERTIEREN",
"LENGTH (m)": "LÄNGE (m)",
"Location": "Ort",
"Login": "Login",
"Logout": "Logout",
"Message": "Nachricht",
"Move Absolute": "absolut bewegen",
"MOVE ABSOLUTE": "ABSOLUT BEWEGEN",
"MOVE AMOUNT (mm)": "BETRAG BEWEGEN (mm)",
"Move Relative": "relativ bewegen",
"MOVE RELATIVE": "RELATIV BEWEGEN",
"NAME": "NAME",
"NETWORK": "NETZWERK",
"never connected to device": "noch nie mit Gerät verbunden",
"New Password": "Neues Passwort",
"no": "nein",
"Not Connected to bot": "Nicht mit Bot verbunden",
"Old Password": "Altes Passwort",
"Operator": "Betreiber",
"Package Name": "Packet-Name",
"Parameters": "Parameter",
"Password": "Passwort",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin-Modus",
"Pin Number": "Pin-Nummer",
"Plant Info": "Pflanzen-Info",
"Plants": "Pflanzen",
"Problem Loading Terms of Service": "Probleme beim Laden der Nutzungsbedingnungen",
"Read Pin": "Pin lesen",
"READ PIN": "PIN LESEN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimen",
"Repeats Every": "Wiederholt alle",
"Request sent": "Anfrage gesendet",
"Reset": "Zurücksetzen",
"RESET": "ZURÜCKSETZEN",
"Reset Password": "Passwort zurücksetzen",
"Reset your password": "Setze dein Passwort zurück",
"RESTART": "NEUSTART",
"RESTART FARMBOT": "FARMBOT NEUSTARTEN",
"Save": "Seichern",
"SAVE": "SPEICHERN",
"Send Message": "Nachricht senden",
"SEND MESSAGE": "NACHRICHT SENDEN",
"Send Password reset": "Passwort-Zurücksetzen gesendet",
"Sequence": "Sequenz",
"Sequence Editor": "Sequenzen-Editor",
"Sequence or Regimen": "Sequenz oder Regimen",
"Sequences": "Sequenzen",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "Ausschalten",
"SHUTDOWN FARMBOT": "FARMBOT AUSSCHALTEN",
"SLOT": "SLOT",
"Socket Connection Established": "Socket-Verbindung hergestellt",
"Speed": "Geschwindigkeit",
"Started": "Gestartet",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Schritte per MM",
"Sync Required": "Sync benötigt",
"Take a Photo": "Mache ein Foto",
"Take Photo": "Mache Foto",
"TAKE PHOTO": "MACHE FOTO",
"TEST": "TEST",
"Time": "Zeit",
"Time in milliseconds": "Zeit in Millisekunden",
"TIMEOUT AFTER (seconds)": "TIMEOUT NACH (Sekunden)",
"TOOL": "GERÄT",
"TOOL NAME": "GERÄTE-NAMEN",
"TOOLBAY NAME": "GERÄTEHALTER-NAMEN",
"Tried to delete Farm Event": "Versucht Farm-Event zu löschen",
"Tried to delete plant": "Versucht Pflanze zu löschen",
"Tried to save Farm Event": "Versucht Farm-Event zu speichern",
"Tried to save plant": "Versucht Pflanze zu speichern",
"Tried to update Farm Event": "Versucht Farm-Event zu aktualisieren",
"Unable to delete sequence": "Sequenz konnte nicht gelöscht werden",
"Unable to download device credentials": "Geräte-Zugangsdaten konnten nicht heruntergeladen werden",
"Until": "Bis",
"UP TO DATE": "Aktuell",
"UPDATE": "UPDATE",
"Value": "Wert",
"Variable": "Variable",
"Verify Password": "Passwort bestätigen",
"Version": "Version",
"Wait": "Warte",
"WAIT": "WARTE",
"Weed Detector": "Beikraut-Detektor",
"Week": "Wocke",
"Write Pin": "Schreibe Pin",
"WRITE PIN": "SCHREIBE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X-ACHSE",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y-ACHSE",
"yes": "ya",
"Your Name": "Dein Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z-ACHSE"
}

View File

@ -0,0 +1,3 @@
module.exports = {
// Nothing to do here...
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACELERAR PARA (pasos)",
"Account Settings": "Ajustes de cuenta",
"Add": "Añadir",
"Add Farm Event": "Añadir Farm Event",
"Age": "Edad",
"Agree to Terms of Service": "Aceptar los Términos del Servicio",
"ALLOW NEGATIVES": "PERMITIR NEGATIVOS",
"BACK": "ATRÁS",
"Bot ready": "Bot listo",
"CALIBRATE {{axis}}": "CALIBRAR {{axis}}",
"CALIBRATION": "CALIBRACIÓN",
"calling FarmBot with credentials": "llamando a FarmBot con credenciales",
"Camera": "Camera",
"Choose a species": "Elige una especie",
"Confirm Password": "Confirmar Contraseña",
"CONTROLLER": "CONTROLADOR",
"Copy": "Copiar",
"Could not download sync data": "No se pudieron descargar los datos de sincronización",
"Create Account": "Crear Cuenta",
"Create An Account": "Crear Una Cuenta",
"Crop Info": "Información del Cultivo",
"Data Label": "Etiqueta de Datos",
"days old": "days old",
"Delete": "Eliminar",
"DELETE ACCOUNT": "ELIMINAR CUENTA",
"Delete this plant": "Eliminar esta planta",
"Designer": "Diseñador",
"DEVICE": "DISPOSITIVO",
"downloading device credentials": "descargando credenciales del dispositivo",
"Drag and drop into map": "Arrastra y suelta dentro del mapa",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Editar",
"EDIT": "EDITAR",
"Edit Farm Event": "Editar Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ACTIVAR CODIFICADORES",
"Enter Email": "Introducir Email",
"Error establishing socket connection": "Error estableciendo la conexión con el socket",
"Execute Script": "Ejecutar Script",
"EXECUTE SCRIPT": "EJECUTAR SCRIPT",
"Execute Sequence": "Ejecutar Secuencia",
"EXECUTE SEQUENCE": "EJECUTAR SECUECNIA",
"Factory Reset": "Restauración de fábrica",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Olvidar contraseña",
"GO": "IR",
"I Agree to the Terms of Service": "Estoy de acuerdo con los Términos del Servicio",
"I agree to the terms of use": "Estoy de acuerdo con los términos de uso",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Importar coordenadas desde",
"initiating connection": "iniciando conexión",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVENTIR MOTORES",
"LENGTH (m)": "LONGITUD (m)",
"Location": "Localización",
"Login": "Login",
"Logout": "Logout",
"Message": "Mensage",
"Move Absolute": "Mover Absoluto",
"MOVE ABSOLUTE": "MOVER ABSOLUTO",
"MOVE AMOUNT (mm)": "MOVER CANTIDAD (mm)",
"Move Relative": "Mover Relativo",
"MOVE RELATIVE": "MOVER RELATIVO",
"NAME": "NOMBRE",
"NETWORK": "RED",
"never connected to device": "nunca se conectó al dispositivo",
"New Password": "Nueva Contraseña",
"no": "no",
"Not Connected to bot": "No Conectado al bot",
"Old Password": "Última Contraseña",
"Operator": "Operador",
"Package Name": "Nombre del Paquete",
"Parameters": "Parámetros",
"Password": "Contraseña",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Modo Pin",
"Pin Number": "Número Pin",
"Plant Info": "Información de la Planta",
"Plants": "Plantas",
"Problem Loading Terms of Service": "Problemas Cargando los Términos del Servicio",
"Read Pin": "Leer Pin",
"READ PIN": "LEER PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repetir Cada",
"Request sent": "Enviar petición",
"Reset": "Reiniciar",
"RESET": "REINICIAR",
"Reset Password": "Reiniciar Password",
"Reset your password": "Reinicia tu contraseña",
"RESTART": "VOLVER A INICIAR",
"RESTART FARMBOT": "VOLVER A INICIAR FARMBOT",
"Save": "Guarda",
"SAVE": "GUARDAR",
"Send Message": "Enviar Mensaje",
"SEND MESSAGE": "ENVIAR MENSAJE",
"Send Password reset": "Enviar reinicio de Contraseña",
"Sequence": "Secuencia",
"Sequence Editor": "Editor de Secuencia",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Secuencias",
"Server Port": "Puerto del Servidor",
"Server URL": "URL del Servidor",
"SHUTDOWN": "APAGAR",
"SHUTDOWN FARMBOT": "APAGAR FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Conexión con el Socket Establecida",
"Speed": "Velocidad",
"Started": "Iniciado",
"Starts": "Inicios",
"STATUS": "ESTADO",
"Steps per MM": "Passos para MM",
"Sync Required": "Sincronización Requerida",
"Take a Photo": "Tomar una Foto",
"Take Photo": "Tomar Foto",
"TAKE PHOTO": "TOMAR FOTO",
"TEST": "PRUEBA",
"Time": "Tiempo",
"Time in milliseconds": "Tiempo en milisegundos",
"TIMEOUT AFTER (seconds)": "TIMEOUT DESPUES DE (segundos)",
"TOOL": "HERRAMIENTAS",
"TOOL NAME": "NOMBRE DE LA HERRAMIENTA",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Se intentó eliminar el Farm Event",
"Tried to delete plant": "Se intentó eliminar la planta",
"Tried to save Farm Event": "Se intentó guardar el Farm Event",
"Tried to save plant": "Se intentó guardar la planta",
"Tried to update Farm Event": "Se intentó actualizar Farm Event",
"Unable to delete sequence": "No se pudo eliminar la secuencia",
"Unable to download device credentials": "No se pudieron descargar las credenciales del dispositivo",
"Until": "HASTA",
"UP TO DATE": "ACTUALIZADO",
"UPDATE": "ACTUALIZAR",
"Value": "Valor",
"Variable": "Variable",
"Verify Password": "Verificar Contraseña",
"Version": "Version",
"Wait": "Esperar",
"WAIT": "ESPERAR",
"Weed Detector": "Detector de Hierba",
"Week": "Semana",
"Write Pin": "Escribir Pin",
"WRITE PIN": "ESCRIBIR PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "EJE X",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "EJE Y",
"yes": "yes",
"Your Name": "Tu nombre",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "EJE Z",
"Day {{day}}": "Día {{day}}",
"Enter Password": "Introducir Contraseña"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

View File

@ -0,0 +1,199 @@
module.exports = {
"Add Farm Event": "Ajouter un évènement sur la ferme",
"Age": "Age",
"Agree to Terms of Service": "Accepter les condition d'utilisation",
"CALIBRATE {{axis}}": "CALIBRER {{axis}}",
"Choose a species": "Choisir une espèce",
"Confirm Password": "Confirmer le mot de passe",
"Crop Info": "Information sur la culture",
"days old": "jours",
"Delete this plant": "Supprimer cette plante",
"Drag and drop into map": "Glisser/déposer sur la carte",
"Edit Farm Event": "Editer l'évenement sur la ferme",
"Enter Email": "Entrez votre e-mail",
"Execute Script": "Exectuter le script",
"EXECUTE SCRIPT": "EXECUTER LE SCRIPT",
"Execute Sequence": "Executer la séquence",
"EXECUTE SEQUENCE": "EXECUTER LA SEQUENCE",
"Factory Reset": "Retour aux paramètres d'usine",
"Farm Events": "Evènements sur la ferme",
"Forgot Password": "Mot de passe oublié",
"GO": "GO",
"I Agree to the Terms of Service": "J'accepte les Conditions du Service",
"I agree to the terms of use": "J'accepte les Conditions d'utilisation",
"If Statement": "Condition Si",
"Import coordinates from": "Importer les coordonées depuis",
"Location": "Location",
"Move Absolute": "Deplacer en valeur Absolue",
"Move Relative": "Deplacer en valeur Relative",
"NAME": "NOM",
"Operator": "Operateur",
"Package Name": "Nom du paquet",
"Plant Info": "Information sur la plante",
"Problem Loading Terms of Service": "Problème avec le chargement des Conditions du Service",
"Read Pin": "Lire le code PIN",
"Regimens": "Régime",
"Reset": "Réinitialiser",
"RESET": "REINITIALISER",
"Reset Password": "Réinitiliser le mot de passe",
"Reset your password": "Réinitiliser votre mot de passe",
"Send Message": "Envoyer un message",
"Send Password reset": "Envoyer la réinitialisation du mot de passe",
"SLOT": "FENTE",
"Started": "Démarré",
"STATUS": "STATUT",
"Steps per MM": "Pas par MM",
"Take a Photo": "Prendre une Photo",
"Take Photo": "Prendre une Photo",
"TAKE PHOTO": "PRENDRE UNE PHOTO",
"TEST": "TEST",
"TOOL": "OUTIL",
"TOOL NAME": "NOM DE L'OUTIL",
"TOOLBAY NAME": "NOM GROUPE OUTIL",
"Tried to delete Farm Event": "A tenté de supprimer l'évènement sur la ferme",
"Tried to save Farm Event": "A tenté de sauvegarder l'évènement sur la ferme",
"Tried to update Farm Event": "A tenté de mettre à jour l'évènement sur la ferme",
"UP TO DATE": "A JOUR",
"UPDATE": "MISE A JOUR",
"Variable": "Variable",
"Wait": "Attendre",
"Weed Detector": "Detecteur de mauvaise herbe",
"Write Pin": "Ecrire le code PIN",
"X": "X",
"Y": "Y",
"Z": "Z",
"ACCELERATE FOR (steps)": "Accélérer pour (étape)",
"ALLOW NEGATIVES": "AUTORISER NEGATIFS",
"Account Settings": "Options du compte",
"Add": "Ajouter",
"BACK": "Retour",
"Bot ready": "Robot près",
"CALIBRATION": "CALIBRAGE",
"CONTROLLER": "CONTROLLER",
"Camera": "Caméra",
"Copy": "Copier",
"Could not download sync data": "Echec de la synchronisation des données",
"Create Account": "Créer compte",
"Create An Account": "Créer un compte",
"DELETE ACCOUNT": "SUPPRIMER LE COMPTE",
"DEVICE": "APPAREIL",
"DRAG STEP HERE": "DEPLACER L'ETAPE ICI",
"Data Label": "Nom des données",
"Day {{day}}": "Jour {{day}}",
"Delete": "Supprimer",
"Designer": "Designer",
"EDIT": "MODIFIER",
"ENABLE ENCODERS": "Activer l'encodeur",
"Edit": "Modifier",
"Email": "Email",
"Enter Password": "Entrer le mot de passe",
"Error establishing socket connection": "Erreur durant l'établissement de la connexion",
"FIRMWARE": "LOGICIEL",
"IF STATEMENT": "SI DECLARATION",
"INVERT ENDPOINTS": "INVERSER LES EMBOUTS",
"INVERT MOTORS": "INVERSER LES MOTEURS",
"LENGTH (m)": "LONGUEUR (m)",
"Login": "Identifiant",
"Logout": "Déconnexion",
"MOVE ABSOLUTE": "DEPLACEMENT ABSOLUS",
"MOVE AMOUNT (mm)": "LONGUEUR DU DEPLACEMENT (mm)",
"MOVE RELATIVE": "DEPLACEMENT RELATIF",
"Message": "Message",
"NETWORK": "RESEAU",
"New Password": "Nouveau mot de passe",
"Not Connected to bot": "Pas connecté au robot",
"Old Password": "Ancien mot de passe",
"Parameters": "Paramètres",
"Password": "Mot de passe",
"Pin Mode": "Mode Pin",
"Pin Number": "Numéro de Pin",
"Pin {{num}}": "Pin {{num}}",
"Plants": "Plantes",
"READ PIN": "LIRE LE PIN",
"RESTART": "REDEMARRER",
"RESTART FARMBOT": "REDEMARRER FARMBOT",
"Regimen Name": "Nom du régime",
"Repeats Every": "Répéter",
"Request sent": "Demande envoyée",
"SAVE": "SAUVEGARDER",
"SEND MESSAGE": "ENVOYER UN MESSAGE",
"SHUTDOWN": "ETEINDRE",
"SHUTDOWN FARMBOT": "ETEINDRE FARMBOT",
"Save": "Sauvegarder",
"Sequence": "Séquence",
"Sequence Editor": "Editeur de Séquence",
"Sequence or Regimen": "Séquence ou Régime",
"Sequences": "Séquences",
"Server Port": "Port du serveur",
"Server URL": "URL du serveur",
"Socket Connection Established": "La connexion est établie",
"Speed": "Vitesse",
"Starts": "Démarrer",
"Sync Required": "Sync Neécessaire",
"TIMEOUT AFTER (seconds)": "TIMEOUT APRES (secondes)",
"Time": "Temps",
"Time in milliseconds": "Temps en millisecondes",
"Tried to delete plant": "Suppression de la plante en cours",
"Tried to save plant": "Sauvetage de la plante en cours",
"Unable to delete sequence": "Impossible de supprimer la séquence",
"Unable to download device credentials": "Impossible de télécharger les certificats",
"Until": "Jusqu'à",
"Value": "Valeur",
"Verify Password": "Vérifier le mot de paasse",
"Version": "Version",
"WAIT": "ATTENDEZ",
"WRITE PIN": "ECRIRE LE CODE PIN",
"Week": "Semaine",
"X (mm)": "X (mm)",
"X AXIS": "AXE X",
"Y (mm)": "Y (mm)",
"Y AXIS": "AXE Y",
"Your Name": "Votre Nom",
"Z (mm)": "Z (mm)",
"Z AXIS": "AXE Z",
"calling FarmBot with credentials": "contact de FarmBot avec vos certificats",
"downloading device credentials": "téléchargement des certificats de l'appareil",
"initiating connection": "lancement de la connexion",
"never connected to device": "jamais connecté à l'appareil",
"no": "non",
"yes": "oui",
"13 Plants": "13 Plantes",
"142 Plants": "142 Plantes",
"18 Plants": "18 Plantes",
"22 Plants": "22 Plantes",
"459 Plants": "459 Plantes",
"68 Plants": "68 Plantes",
"Add Group": "Ajouter un groupe",
"Add Zone": "Ajouter une zone",
"Add parent groups": "Ajouter des groupes parent",
"CALIBRATE {{target}}": "CALIBRER {{target}}",
"COORDINATES": "COORDONEES",
"Calendar": "Calendrier",
"Change Password": "Changer le mot de passe",
"Clear Logs": "Vider les Logs",
"Delete Account": "Supprimer le compte",
"EXECUTE": "EXECUTER",
"FARMBOT NAME": "NOM DU FARMBOT",
"Farmbot encountered an error": "Farmbot a rencontré une erreur",
"Groups": "Groupes",
"LHS": "LHS",
"Loading": "Chargement",
"Logs": "Logs",
"MESSAGE": "MESSAGE",
"My Groups": "Mes Groupes",
"Name": "Nom",
"OPERATOR": "OPERATEUR",
"OS Auto Updates": "Mise à jour automatique du SE",
"Plants in this group": "Plantes dans ce groupe",
"RHS": "RHS",
"Reset password": "Réinitialiser le mot de passe",
"STEPS PER MM": "ETAPE PAR MM",
"Select from map to add": "Choisir parmi la carte à ajouter",
"Size": "Taille",
"Start": "Démarrer",
"Stop": "Arrêt",
"Sub Sequence": "Sous Séquence",
"TIME": "TEMPS",
"Test": "Test",
"Zones": "Zones"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

View File

@ -0,0 +1,199 @@
module.exports = {
"Add Farm Event": "Aggiungi Evento agricolo",
"Age": "Età",
"Agree to Terms of Service": "Accetto i Termini di servizio",
"CALIBRATE {{axis}}": "CALIBRA {{axis}}",
"Choose a species": "Scegliere una specie",
"Confirm Password": "Confermare la password",
"Crop Info": "Info coltivazione",
"days old": "giorni",
"Delete this plant": "Eliminare la pianta",
"Drag and drop into map": "Clicca e trascina sulla mappa",
"Edit Farm Event": "Modifica Evento agricolo",
"Enter Email": "Inserisci indirizzo email",
"Execute Script": "Esegui script",
"EXECUTE SCRIPT": "ESEGUI SCRIPT",
"Execute Sequence": "Esegui sequenza",
"EXECUTE SEQUENCE": "ESEGUI SEQUENZA",
"Factory Reset": "Ripristino impostazioni di fabbrica",
"Farm Events": "Eventi agricoli",
"Forgot Password": "Password dimenticata",
"GO": "PROCEDI",
"I Agree to the Terms of Service": "Ho compreso ed accetto i Termini di servizio",
"I agree to the terms of use": "Ho compreso ed accetto le Condizioni d'uso",
"If Statement": "Comando if",
"Import coordinates from": "Importa coordinate da",
"Location": "Posizione",
"Move Absolute": "Fai Movimento assoluto",
"Move Relative": "Fai Movimento relativo",
"NAME": "NOME",
"Operator": "Operatore",
"Package Name": "Nome pacchetto",
"Plant Info": "Informazioni pianta",
"Problem Loading Terms of Service": "Si è verificato un problema durante il caricamente dei Termini di Servizio",
"Read Pin": "Leggi Pin",
"Regimens": "Regimi",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Recupero password",
"Reset your password": "Recupera la tua password",
"Send Message": "Inviare messaggio",
"Send Password reset": "Invia richiesta di Recupero password",
"SLOT": "SLOT",
"Started": "Avviato",
"STATUS": "STATUS",
"Steps per MM": "Passi per MM",
"Take a Photo": "Scatta una foto",
"Take Photo": "Scatta foto",
"TAKE PHOTO": "SCATTA FOTO",
"TEST": "TEST",
"TOOL": "STRUMENTO",
"TOOL NAME": "NOME STRUMENTO",
"TOOLBAY NAME": "NOME SCOMPARTO STRUMENTI",
"Tried to delete Farm Event": "Eseguito tentativo di eliminare Evento agricolo",
"Tried to save Farm Event": "Eseguito tentativo di salvare Evento agricolo",
"Tried to update Farm Event": "Eseguito tentativo di aggiornare Evento agricolo",
"UP TO DATE": "AGGIORNATO",
"UPDATE": "AGGIORNARE",
"Variable": "Variabile",
"Wait": "Attendere",
"Weed Detector": "Rilevatore infestanti",
"Write Pin": "Inserire Pin",
"X": "X",
"Y": "Y",
"Z": "Z",
"ACCELERATE FOR (steps)": "ACCELERA PER (steps)",
"ALLOW NEGATIVES": "PERMETTI VALORI NEGATIVI",
"Account Settings": "Impostazioni Account",
"Add": "Aggiungi",
"BACK": "INDIETRO",
"Bot ready": "Bot pronto",
"CALIBRATION": "CALIBRAZIONE",
"CONTROLLER": "CONTROLLER",
"Camera": "Fotocamera",
"Copy": "Copia",
"Could not download sync data": "Impossibile scaricare dati di sincronizzazione",
"Create Account": "Crea account",
"Create An Account": "Creare nuovo account",
"DELETE ACCOUNT": "ELIMINA ACCOUNT",
"DEVICE": "DISPOSITIVO",
"DRAG STEP HERE": "TRASCINARE QUI I COMANDI",
"Data Label": "Etichetta dati",
"Day {{day}}": "Giorno {{day}}",
"Delete": "Elimina",
"Designer": "Designer",
"EDIT": "MODIFICA",
"ENABLE ENCODERS": "ATTIVA ENCODER",
"Edit": "Modifica",
"Email": "Indirizzo email",
"Enter Password": "Inserisci password",
"Error establishing socket connection": "Errore durante la connessione dei socket",
"FIRMWARE": "FIRMWARE",
"IF STATEMENT": "COMANDO SE ",
"INVERT ENDPOINTS": "INVERTI FINECORSA",
"INVERT MOTORS": "INVERTI MOTORI",
"LENGTH (m)": "LUNGHEZZA (m)",
"Login": "Accedi",
"Logout": "Esci",
"MOVE ABSOLUTE": "FAI MOVIMENTO ASSOLUTO",
"MOVE AMOUNT (mm)": "QUANTITÁ MOVIMENTO (mm)",
"MOVE RELATIVE": "FAI MOVIMENTO RELATIVO",
"Message": "Messaggio",
"NETWORK": "RETE",
"New Password": "Nuova password",
"Not Connected to bot": "Non connesso al robot",
"Old Password": "Vecchia password",
"Parameters": "Parametri",
"Password": "Password",
"Pin Mode": "Modalità PIN",
"Pin Number": "Numero PIN",
"Pin {{num}}": "PIN {{num}}",
"Plants": "Piante",
"READ PIN": "LEGGI PIN",
"RESTART": "RIAVVIO",
"RESTART FARMBOT": "RIAVVIO FARMBOT",
"Regimen Name": "Nome regime",
"Repeats Every": "Ripeti ogni",
"Request sent": "Richiesta inviata",
"Save": "SALVA",
"SEND MESSAGE": "INVIA MESSAGGIO",
"SHUTDOWN": "ARRESTO",
"SHUTDOWN FARMBOT": "SPEGNI FARMBOT",
"Save": "Salva",
"Sequence": "Sequenza",
"Sequence Editor": "Editor di Sequenze",
"Sequence or Regimen": "Sequenza o Regime",
"Sequences": "Sequenze",
"Server Port": "Porta del server",
"Server URL": "URL del server",
"Socket Connection Established": "Connessione stabilita con socket",
"Speed": "Velocità",
"Starts": "Starts",
"Sync Required": "Sincronizzazione richiesta",
"TIMEOUT AFTER (seconds)": "TIMEOUT DOPO (secondi)",
"Time": "Durata",
"Time in milliseconds": "Durata in millisecondi",
"Tried to delete plant": "Eseguito Tentativo di eliminare pianta",
"Tried to save plant": "Eseguito tentativo di salvare pianta",
"Unable to delete sequence": "Impossibile eliminare la sequenza",
"Unable to download device credentials": "Impossibile scaricare credenziali del dispositivo",
"Until": "Fino al",
"Value": "Valore",
"Verify Password": "Confermare la password",
"Version": "Versione",
"WAIT": "ATTENDI",
"WRITE PIN": "SCRIVI PIN",
"Week": "Settimana",
"X (mm)": "X (mm)",
"X AXIS": "ASSE X",
"Y (mm)": "Y (mm)",
"Y AXIS": "ASSE Y",
"Your Name": "Il tuo nome",
"Z (mm)": "Z (mm)",
"Z AXIS": "ASSE Z",
"calling FarmBot with credentials": "autenticazione su FarmBot con credenziali",
"downloading device credentials": "scaricamento in corso credenziali del dispositivo",
"initiating connection": "inizializzazione connessione in corso",
"never connected to device": "mai connesso al dispositivo",
"no": "no",
"yes": "sì",
"13 Plants": "13 piante",
"142 Plants": "142 piante",
"18 Plants": "18 piante",
"22 Plants": "22 piante",
"459 Plants": "459 piante",
"68 Plants": "68 piante",
"Add Group": "Aggiungere gruppo",
"Add Zone": "Aggiungere zona",
"Add parent groups": "Aggiungere gruppi genitori",
"CALIBRATE {{target}}": "CALIBRARE {{target}}",
"COORDINATES": "COORDINATE",
"Calendar": "Calendario",
"Change Password": "Modificare password",
"Logs": "Leggere i log",
"Delete Account": "Cancellare account",
"EXECUTE": "ESEGUI",
"FARMBOT NAME": "NOME PROPRIO DEL FARMBOT",
"Farmbot encountered an error": "Si è verificato un errore su Farmbot",
"Groups": "Gruppi",
"LHS": "LHS",
"Loading": "Caricamento in corso",
"Logs": "Log",
"MESSAGE": "MESSAGGIO",
"My Groups": "Gruppi personalizzati",
"Name": "Nome",
"OPERATOR": "OPERATORE",
"OS Auto Updates": "Aggiornare automaticamente SO",
"Plants in this group": "Piante in questo gruppo",
"RHS": "RHS",
"Reset password": "Resettare la password",
"STEPS PER MM": "PASSI PER MM",
"Select from map to add": "Per aggiungere selezionare dalla mappa",
"Size": "Dimensione",
"Start": "Avviare",
"Stop": "Arrestare",
"Sub Sequence": "Sottosequenza",
"TIME": "DURATA",
"Test": "Test",
"Zones": "Zone"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELEREER VOOR (stappen)",
"Account Settings": "Instellingen",
"Add": "Toevoegen",
"Add Farm Event": "Evenement Toevoegen",
"Age": "Leeftijd",
"Agree to Terms of Service": "Ga akkoord met de Algemene Voorwaarden",
"ALLOW NEGATIVES": "NEGATIEVEN TOESTAAN",
"BACK": "TERUG",
"Bot ready": "Bot gereed",
"CALIBRATE {{axis}}": "CALIBREER {{axis}}",
"CALIBRATION": "CALIBRATIE",
"calling FarmBot with credentials": "FarmBot aanroepen met credentials",
"Camera": "Camera",
"Choose a species": "Kies een soort",
"Confirm Password": "Bevestig Wachtwoord",
"CONTROLLER": "CONTROLLER",
"Copy": "Kopieer",
"Could not download sync data": "Kon sync data niet downloaden",
"Create Account": "Account Aanmaken",
"Create An Account": "Maak Een Account",
"Crop Info": "Gewas Info",
"Data Label": "Datalabel",
"Day {{day}}": "Dag {{day}}",
"days old": "dagen oud",
"Delete": "Verwijder",
"DELETE ACCOUNT": "VERWIJDER ACCOUNT",
"Delete this plant": "Verwijder deze plant",
"Designer": "Ontwerper",
"DEVICE": "APPARAAT",
"downloading device credentials": "apparaatcredentials aan het downloaden",
"Drag and drop into map": "Sleep en laat vallen in kaart",
"DRAG STEP HERE": "SLEEP STAP HIER",
"Edit": "Wijzig",
"EDIT": "WIJZIG",
"Edit Farm Event": "Wijzig Evenement",
"Email": "Email",
"ENABLE ENCODERS": "ENCODERS INSCHAKELEN",
"Enter Email": "Voer Email In",
"Enter Password": "Voer Wachtwoord In",
"Error establishing socket connection": "Fout bij opzetten van socket-verbinding",
"Execute Script": "Script Uitvoeren",
"EXECUTE SCRIPT": "SCRIPT UITVOEREN",
"Execute Sequence": "Actiereeks Uitvoeren",
"EXECUTE SEQUENCE": "ACTIEREEKS UITVOEREN",
"Factory Reset": "Fabrieksinstellingen",
"Farm Events": "Evenementen",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Wachtwoord Vergeten",
"GO": "GA",
"I Agree to the Terms of Service": "Ik ga Akkoord met de Algemene Voorwaarden",
"I agree to the terms of use": "Ik ga akkoord met de gebruiksvoorwaarden",
"If Statement": "Conditionele Keuze",
"IF STATEMENT": "CONDITIONELE KEUZE",
"Import coordinates from": "Importeer coördinaten van",
"initiating connection": "verbinding initialiseren",
"INVERT ENDPOINTS": "EINDPUNTEN OMKEREN",
"INVERT MOTORS": "MOTOREN OMKEREN",
"LENGTH (m)": "LENGTE (m)",
"Location": "Lokatie",
"Login": "Inloggen",
"Logout": "Uitloggen",
"Message": "Bericht",
"Move Absolute": "Absoluut Verplaatsen",
"MOVE ABSOLUTE": "ABSOLUUT VERPLAATSEN",
"MOVE AMOUNT (mm)": "VERPLAATSAFSTAND (mm)",
"Move Relative": "Relatief Verplaatsen",
"MOVE RELATIVE": "RELATIEF VERPLAATSEN",
"NAME": "NAAM",
"NETWORK": "NETWERK",
"never connected to device": "nooit verbonden met apparaat",
"New Password": "Nieuw Wachtwoord",
"no": "nee",
"Not Connected to bot": "Niet Verbonden met bot",
"Old Password": "Oud Wachtwoord",
"Operator": "Operator",
"Package Name": "Package Naam",
"Parameters": "Parameters",
"Password": "Wachtwoord",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Modus",
"Pin Number": "Pin Nummer",
"Plant Info": "Plant Info",
"Plants": "Planten",
"Problem Loading Terms of Service": "Probleem Bij Laden Van Algemene Voorwaarden",
"Read Pin": "Lees Pin",
"READ PIN": "LEES PIN",
"Regimen Name": "Naam Regime",
"Regimens": "Regimes",
"Repeats Every": "Herhaal Elke",
"Request sent": "Verzoek verstuurd",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Wachtwoord",
"Reset your password": "Reset je wachtwoord",
"RESTART": "HERSTARTEN",
"RESTART FARMBOT": "HERSTART FARMBOT",
"Save": "Opslaan",
"SAVE": "OPSLAAN",
"Send Message": "Stuur Bericht",
"SEND MESSAGE": "STUUR BERICHT",
"Send Password reset": "Stuur Wachtwoord-reset",
"Sequence": "Actiereeks",
"Sequence Editor": "Actiereeks Ontwerper",
"Sequence or Regimen": "Actiereeks of Regime",
"Sequences": "Actiereeksen",
"Server Port": "Serverpoort",
"Server URL": "Server URL",
"SHUTDOWN": "UITZETTEN",
"SHUTDOWN FARMBOT": "FARMBOT UITZETTEN",
"SLOT": "SLEUF",
"Socket Connection Established": "Socket-verbinding Opgezet",
"Speed": "Snelheid",
"Started": "Gestart",
"Starts": "Begint",
"STATUS": "STATUS",
"Steps per MM": "Stappen per MM",
"Sync Required": "Sync Vereist",
"Take a Photo": "Neem een Foto",
"Take Photo": "Neem Foto",
"TAKE PHOTO": "NEEM FOTO",
"TEST": "TEST",
"Time": "Tijd",
"Time in milliseconds": "Tijd in milliseconden",
"TIMEOUT AFTER (seconds)": "AFBREKEN NA (seconden)",
"TOOL": "KOPPELSTUK",
"TOOL NAME": "NAAM KOPPELSTUK",
"TOOLBAY NAME": "HOUDER NAAM",
"Tried to delete Farm Event": "Probeerde een Evenement te verwijderen",
"Tried to delete plant": "Probeerde een plant te verwijderen",
"Tried to save Farm Event": "Probeerde een Evenement op te slaan",
"Tried to save plant": "Probeerde een plant op te slaan",
"Tried to update Farm Event": "Probeerde een Evenement bij te werken",
"Unable to delete sequence": "Kan actiereeks niet verwijderen",
"Unable to download device credentials": "Kan apparaat-credentials niet downloaden",
"Until": "Tot",
"UP TO DATE": "BIJGEWERKT",
"UPDATE": "BIJWERKEN",
"Value": "Waarde",
"Variable": "Variabele",
"Verify Password": "Verifieer Wachtwoord",
"Version": "Versie",
"Wait": "Wacht",
"WAIT": "WACHT",
"Weed Detector": "Onkruid Detector",
"Week": "Week",
"Write Pin": "Schrijf Pin",
"WRITE PIN": "SCHRIJF PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AS",
"yes": "ja",
"Your Name": "Jouw Naam",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AS"
}

View File

@ -0,0 +1,210 @@
module.exports = {
//WARNINGS & Capitalized text
"ACCELERATE FOR (steps)": "ACELERAR n (passos)",
"ALLOW NEGATIVES": "PERMITIR NEGATIVAS",
"BACK": "VOLTAR",
"CALIBRATE {{axis}}": "CALIBRAR {{axis}}",
"CALIBRATION": "CALIBRAÇÃO",
"CONTROLLER": "CONTROLADOR",
"DELETE ACCOUNT": "EXCLUIR A CONTA",
"DEVICE": "DISPOSITIVO",
"DRAG STEP HERE": "COLOQUE AS ETAPAS AQUI",
"EDIT": "EDITAR",
"ENABLE ENCODERS": "HABILITAR CODIFICADORES",
"EXECUTE SCRIPT": "EXECUTAR ROTINA",
"EXECUTE SEQUENCE": "EXECUTAR SEQUÊNCIA",
"FIRMWARE": "FIRMWARE",
"IF STATEMENT": "DECLARAÇÃO DO TIPO `SE` (PROGRAMAÇÃO)",
"INVERT ENDPOINTS": "INVERTER EXTREMIDADES",
"INVERT MOTORS": "INVERTER MOTORES",
"LENGTH (m)": "COMPRIMENTO (m)",
"MAX SPEED (mm/s)": "VELOCIDADE MÁXIMA (mm/s)",
"MOVE ABSOLUTE": "MOVER COM PRECISÃO",
"MOVE AMOUNT (mm)": "DISTÂNCIA DO MOVIMENTO (mm)",
"MOVE RELATIVE": "MOVER (MENOR PRECISÃO)",
"NAME": "NOME",
"NETWORK": "REDE",
"READ PIN": "EFETUAR LEITURA DO PIN",
"RESET": "RESETAR TUDO",
"RESTART FARMBOT": "REINICIAR O FARMBOT",
"RESTART": "REINICIAR",
"SAVE": "SALVAR",
"SEND MESSAGE": "ENVIAR MENSAGEM",
"SHUTDOWN FARMBOT": "DESLIGAR O FARMBOT",
"SHUTDOWN": "DESLIGAR",
"TEST": "TESTAR",
"TOOL NAME": "NOME DA FERRAMENTA",
"TIMEOUT AFTER (seconds)": "TEMPO LIMITE DE X (segundos)",
"UP TO DATE": "ATUALIZADO",
"UPDATE": "ATUALIZAR",
"WAIT": "AGUARDAR",
"WRITE PIN": "GRAVAR Nº PIN",
"X AXIS": "EIXO X",
"Y AXIS": "EIXO Y",
"Z AXIS": "EIXO Z",
//ACCOUNT & LOGIN TEXTS
"Account Settings": "Configurações de Conta",
"Change Password": "Alterar Senha",
"Confirm Password": "Confirmar Senha",
"Create Account": "Criar conta",
"Create An Account": "Criar uma conta",
"Email": "E-mail",
"Enter Email": "Digite seu e-mail",
"Enter Password": "Digite sua Senha",
"Enter your password to delete your account.": "Digite sua senha pra excluir a conta.",
"Forgot Password": "Esqueci minha senha",
"Login": "Entrar",
"Logout": "Encerrar Sessão",
"New Password": "Nova Senha",
"Old Password": "Senha Anterior",
"Password": "Senha",
"Pin {{num}}": "PIN - Número de Identificação Pessoal {{num}}",
"Please enter a valid label and pin number.": "Por favor insira um marcador e um número PIN válidos.",
"Reset Password": "Redefinir Senha",
"Send Password reset": "Solicitar nova senha",
"Verfy Password": "Verifique sua senha",
"You have been logged out.": "Sessão encerrada.",
"Your Name": "Seu Nome",
//UPDATE TEXT
"Auto Updates?": "Atualizações Automáticas?",
//STATUS & ERROR TEXTS
"Add a div with id `root` to the page first.": "Primeiramente adicione um elemento div com a id `root` á página.",
"Bot ready": "Robô pronto",
"calling FarmBot with credentials": "conectando ao Fambot com as credenciais",
"Camera": "Câmera",
"Could not download firmware update information.": "Não foi possível transferir as informações de atualização do Firmware do dispositivo.",
"Could not download OS update information.": "Não foi possível transferir as informações de atualização do Sistema Operacional.",
"Could not download sync data": "Não foi possível transferir os dados de sincronização",
"Could not fetch bot status. Is FarmBot online?": "Não foi possível verificar o estado do farmbot. Verifique se o mesmo se encontra conectado",
"Couldn\'t save device.": "Não foi possível salvar dados sobre o o dispositivo",
"downloading device credentials": "transferindo credenciais do dispositivo",
"Error establishing socket connection": "Erro ao estabelecer conexão com os soquetes",
"Error while saving device.": "Erro ao salvar detalhes do dispositivo.",
"Factory Reset": "Redefinir Todas as Configurações para o padrão de Fábrica",
"Farmbot Didn't Get That!": "Comando inválido!",
"FarmBot sent a malformed message. ": "O FarmBot está enviando mensagens truncadas. ",
"initiating connection": "iniciando conexão",
"never connected to device": "nunca conectado ao dispositivo",
"No logs yet.": "Sem registros para exibir.",
"Not Connected to bot": "Não está conectado ao FarmBot",
"Please upgrade FarmBot OS and log back in.": "Por favor faça um upgrade do Sistema Operacional e tente novamente.",
"Socket Connection Established": "Conexão com os soquetes estabelecida",
"ToolBay saved.": "Compartimento de Ferramentas salvo comm sucesso.",
"Tools saved.": "Ferramentas salvas com sucesso.",
"Tried to delete plant, but couldn't.": "Não foi possível excluir esta planta.",
"Tried to move plant, but couldn't.": "Não foi possível mover esta planta.",
"Tried to save plant, but couldn't.": "Não foi possível salvar os dados desta planta.",
"Unable to download device credentials": "Não foi possível transferir as credenciais do dispositivo",
"User successfully updated.": "Usuário atualizado.",
"You may need to upgrade FarmBot OS. ": "Talvez você tenha efetuar um upgrade do Sistema Operacional do Farmbot. ",
//OTHERS/GENERAL WORDS
"Add": "Adicionar",
"Copy": "Copiar",
"Day {{day}}": "Dia {{day}}",
"Data Label": "Etiqueta de Dados",
"Delete": "Excluir",
"Edit": "Editar",
"Execute Script": "Executar Rotina",
"Execute Sequence": "Executar Sequência",
"If Statement": "Declaração do Tipo `Se` (Programação)",
"Message": "Mensagem",
"Move Absolute": "Mover com Precisão",
"Move Relative": "Mover (Sem precisão)",
"no": "não",
"Operator": "Operador",
"Package Name": "Nome do Pacote",
"Parameters": "Parâmetros",
"Pin Mode": "Modo PIN",
"Pin Number": "Número PIN",
"Read Pin": "Efetuar a leitura do PIN",
"Repeats Every": "Repete a cada",
"Reset": "Redefinir",
"Save": "Salvar",
"Save & Run": "Salvar & Executar",
"Send Message": "Enviar Mensagem",
"Sequence": "Sequência",
"Sequence Editor": "Editor de Sequências",
"Sequences": "Sequências",
"Server Port": "Porta do Servidor",
"Server URL": "URL do Servidor",
"Speed": "Velocidade",
"Starts": "Inicia em",
"Steps per MM": "Passos por unidade MM",
"Time": "Tempo",
"Time in milliseconds": "Tempo em milisegundos",
"Until": "Até",
"Variable": "Variável",
"Value": "Valor",
"Version": "Versão",
"Wait": "Aguardar",
"Weed Detector": "Detector de Ervas Daninhas",
"Week": "Semana",
"We're sorry to see you go. :(": "É uma pena que você tenha de ir. :( ",
"Write Pin": "Gravar Nº PIN",
"X (mm)": "X (mm)",
"X-Offset": "Deslocamento do Eixo X",
"Y (mm)": "Y (mm)",
"Y-Offset": "Deslocamento do Eixo Y",
"yes": "sim",
"Z (mm)": "Z (mm)",
"Z-Offset": "Deslocamento do Eixo Z",
//REGIMEN & SEQUENCES
"Could not download regimens.": "Não foi possível transferir a programação para o FarmBot.",
"Sequence or Regimen": "Sequência ou Regime",
"Regimen Name": "Nome da Programação",
"Regimen deleted.": "Programação excluída.",
"Regimen saved.": "Programação Salva.",
"Saved '{{SequenceName}}'": "'{{SequenceName}}' Salvo(a) com sucesso",
"Select a regimen or create one first.": "Selecione uma programação. Você deve criar a sua, se ainda não tiver feito isso.",
"Select a sequence from the dropdown first.": "Selecione uma sequência da lista primeiro.",
"This regimen is currently empty.": "Esta programação não possui nenhuma sequência agendada.",
"Unable to delete regimen.": "Erro ao deletar programação.",
"Unable to delete sequence": "Erro ao deletar sequência.",
"Unable to save '{{SequenceName}}'": "Não foi possível salvar '{{SequenceName}}'",
"Unable to save regimen.": "A programação não pode ser salva.",
// BELOW LINES ARE FOR TEST PURPOSES ONLY - DO NOT UNCOMMENT UNLESS YOU KNOW WHAT YOU ARE DOING!
//ACCOUNT MESSAGES
//`User could not be updated: ${e.message}` : "`Não foi possível atualizar o usuário: ${e.message}`,
//"saves new user password" : "salva a nova senha do usuário",
//GENERAL TEXTS
//"Absolute movement" : "Movimento Preciso"
//`Change settings` : `Alterar configurações`,
//`Detect Weeds` : `Detectar Ervas Daninhas`,
//"Impossible" :"Impossível",
//"Relative movement" : "Movimento Simples"
//"Setting toggle" : "Configuração de alternadores",
//`This widget` : `este widget`,
//TIPS & ERRORS
//`Use these manual control buttons to move FarmBot in realtime. Press the arrows for relative movements or type in new coordinates and press GO for an absolute movement. Tip: Press the Home button when you are done so FarmBot is ready to get back to work.` : `Utilize estes botões de controle manual para movimentar o FarmBot em tempo real. Pressione as setas para movimentar ou insira novas cordenadas e aperte o botão IR para um movimento mais preciso. Dica: Aperte o botão de Início quando terminar para que o Farmbot saiba que deve voltar ao trabalho.`,
//`Press the edit button to update and save your webcam URL.` : `Pressione o botão de editar para atualizar e salvar a URL de sua webcam.`,
//`Use these toggle switches to control FarmBot's peripherals in realtime. To edit and create new peripherals, press the EDIT button. Make sure to turn things off when you're done!` : `Use esses controles para controlar os periféricos do Farmbot em tempo real. Para editar e criar novos periféricos, pressione o botão EDITAR. Tenha certeza de desligar tudo quando acabar!`
//`Tried to connect to null bot.You probably meant to set a bot first.` : `Tentou conectar a um farmbot inexistente. Você provavelmente esqueceu de configurar seu farmbot.`
//"request sent to device." : " solicitação enviada ao dispositivo."
//`This will restart FarmBot's Raspberry` : `Isto irá reiniciar o equipamento Raspberry do FarmBot`,
//`This will shutdown FarmBot's Raspberry Pi`. : `Isto irá desligar o Raspberry Pi do FarmBot`.
//`ToolBay could not be updated: ${e.message}` : `O Compartimento de Ferramentas não pode ser atualizado: ${e.message}`
//`Toolbays are where you store your FarmBot Tools. Each Toolbay has Slots that you can put your Tools in, which should be reflective of your real FarmBot hardware configuration.` : `Toolbays (Compartimento de Ferramentas) é onde você armazena as Ferramentas do FarmBot. Cada Compartimento tem um slot (fendas) nas quais você pode colocar suas Ferramentas. Essas ferramentas devem ser iguais às da configuração de hardware do seu FarmBot.`
//`This is a list of all your FarmBot Tools. Click the Edit button to add, edit, or delete tools.` : `Esta é uma lista de todas as suas Ferramentas do FarmBot. Clique em Editar para adicionar, editar ou excluir ferramentas.`
//REGIMEN & SEQUENCES
//`Use this tool to schedule sequences to run on one or many days of your regimen.` : `Use esta ferramenta para agendar sequências a serem executadas em um ou mais dias de sua programação.`,
//`You don't have any Regimens yet. Click "Add" from the Regimens widget to create and edit your first Regimen.` : `Você ainda não criou nenhuma programação. Clique em "Adicionar" a partir do widget de programações para criar e editar sua primeira programação.`,
//`Regimens allow FarmBot to take care of a plant throughout its entire life. A regimen consists of many sequences that are scheduled to run based on the age of the plant. Regimens are applied to plants from the farm designer (coming soon) and can be re-used on many plants growing at the same or different times. Multiple regimens can be applied to any one plant.` : `As programações permitem que o FarmBot cuide de uma planta ao longo de toda a vida dela. Uma programação consiste em várias sequências que tem sua execução agendada de acordo com a idade da planta. As programações são aplicadas às plantas a partir do Planejador de hortas (disponível em breve) e podem ser reutilizadas em várias plantas de idades iguais ou diferentes. Várias programações podem ser atribuídas à uma mesma planta.`,
//`Add sequences to this Regimen by using the "scheduler"` : `Adicione Sequências à esta programação utilizando o "agendador"`,
//`This is a list of all of your regimens. Click one to begin editing it.` : `Esta é uma lista de todas as suas programações. Clique em uma delas para editá-la.`,
//`Here is the list of all of your sequences. Click one to edit.` : `Aqui fica a lista de suas sequências. Clique em uma para editá-la.`
//`Drag and drop commands here to create sequences for watering, planting seeds, measuring soil properties, and more. Press the Test button to immediately try your sequence with FarmBot. You can also edit, copy, and delete existing sequences; assign a color; and give your commands custom names.` : `Arraste e solte comandos aqui para criar sequências de irrigação, plantio de sementes, medição de propriedades do solo e outras. Aperte o obtão Testar para testar sua sequência com o FarmBot. Você também pode editar, copiar e excluir sequências existentes; atribuir cores; e dar nomes personalizados aos seus comandos.`
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "УСКОРЕНИЕ (шаги)",
"ALLOW NEGATIVES": "РАЗРЕШИТЬ ОТРИЦАТЕЛЬНЫЕ",
"Account Settings": "Настройки Аккаунта",
"Add": "Добавить",
"Add Farm Event": "Добавить событие",
"Age": "Возраст",
"Agree to Terms of Service": "Я согласен с правилами и положениями",
"BACK": "НАЗАД",
"Bot ready": "Робот Готов",
"CALIBRATE {{axis}}": "КАЛИБРОВАТЬ {{axis}}",
"CALIBRATION": "КАЛИБРОВКА",
"CONTROLLER": "Контроллер",
"Camera": "Камера",
"Choose a species": "Выберите вид",
"Confirm Password": "Повторите пароль",
"Copy": "Копировать",
"Could not download sync data": "Невозможно загрузить синхронизированную информацию",
"Create Account": "Создать Аккаунт",
"Create An Account": "Создать Аккаунт",
"Crop Info": "Инфо об Урожае",
"DELETE ACCOUNT": "УДАЛИТЬ АККАУНТ",
"DEVICE": "УСТРОЙСТВО",
"DRAG STEP HERE": "ПЕРЕТЯНИ СЮДА",
"Data Label": "Data Label",
"Day {{day}}": "день {{day}}",
"Delete": "Удалить",
"Delete this plant": "Удалить это растение",
"Designer": "Дизайнер",
"Drag and drop into map": "Перетащи на карту",
"EDIT": "РЕДАКТИРОВАТЬ",
"ENABLE ENCODERS": "ВКЛЮЧИТЬ ЭНКОДЕРЫ",
"EXECUTE SCRIPT": "ВЫПОЛНИТЬ СЦЕНАРИЙ",
"EXECUTE SEQUENCE": "ВЫПОЛНИТЬ ПОСЛЕДОВАТЕЛЬНОСТЬ",
"Edit": "Редактировать",
"Edit Farm Event": "Редактировать событие",
"Email": "Email",
"Enter Email": "Введите Email",
"Enter Password": "Введите пароль",
"Error establishing socket connection": "Ошибка нестабильное соеденение",
"Execute Script": "Выполнить Скрипт",
"Execute Sequence": "Выполнить Последовательность",
"FIRMWARE": "ПРОШИВКА",
"Factory Reset": "Сброс устройства",
"Farm Events": "События",
"Forgot Password": "Забыл пароль",
"GO": "СТАРТ",
"I Agree to the Terms of Service": "Я согласен с правилами и положениями",
"I agree to the terms of use": "Я согласен с правилами использования",
"IF STATEMENT": "ЕСЛИ STATEMENT",
"INVERT ENDPOINTS": "ИНВЕНТИРОВАТЬ ENDSTOPS",
"INVERT MOTORS": "ИНВЕНТИРОВАТЬ МОТОРЫ",
"If Statement": "Если Statement",
"Import coordinates from": "Импортировать координаты из",
"LENGTH (m)": "Длинна (м)",
"Location": "Координаты",
"Login": "Войти",
"Logout": "Выход",
"MOVE ABSOLUTE": "АБСОЛЮТНОЕ ПЕРЕМЕЩЕНИЕ",
"MOVE AMOUNT (mm)": "Переместить на (мм)",
"MOVE RELATIVE": "ОТНОСИТЕЛЬНОЕ ПЕРЕМЕЩЕНИЕ",
"Message": "Сообщение",
"Move Absolute": "Абсолютное Перемещение",
"Move Relative": "Относительное Перемещение",
"NAME": "ИМЯ",
"NETWORK": "NETWORK",
"New Password": "Новый пароль",
"Not Connected to bot": "Нет соеденения с роботом",
"Old Password": "Текущий пароль",
"Operator": "Оператор",
"Package Name": "Имя пакета",
"Parameters": "Параметры",
"Password": "Пароль",
"Pin Mode": "Режим Pin",
"Pin Number": "Номер Pin",
"Pin {{num}}": "Pin {{num}}",
"Plant Info": "Инфо о растении",
"Plants": "Растения",
"Problem Loading Terms of Service": "Проблема загрузки правил и соглашений",
"READ PIN": "СЧИТАТЬ PIN",
"RESET": "СБРОС",
"RESTART": "ПЕРЕЗАГРУЗКА",
"RESTART FARMBOT": "ПЕРЕЗАГРУЗИТЬ РОБОТА",
"Read Pin": "Считать Pin",
"Regimen Name": "Название режима",
"Regimens": "Режимы",
"Repeats Every": "Повторять каждые",
"Request sent": "Запрос отправлен",
"Reset": "Сброс",
"Reset Password": "Сброс Пароля",
"Reset your password": "Сбросить ваш пароль",
"SAVE": "СОХРАНИТЬ",
"SEND MESSAGE": "ОТПРАВИТЬ СООБЩЕНИЕ",
"SHUTDOWN": "ВЫКЛЮЧИТЬ",
"SHUTDOWN FARMBOT": "ВЫКЛЮЧИТЬ РОБОТА",
"SLOT": "СЛОТ",
"STATUS": "СТАТУС",
"Save": "Сохранить",
"Send Message": "Отправить сообщение",
"Send Password reset": "Send Password reset",
"Sequence": "Последовательность",
"Sequence Editor": "Редактор Последовательностей",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Последовательности",
"Server Port": "Порт сервера",
"Server URL": "Сервер URL",
"Socket Connection Established": "Соеденение не стабильно",
"Speed": "Скорость",
"Started": "Запущено",
"Starts": "Запускается",
"Steps per MM": "Шагов за мм",
"Sync Required": "Требуется синхронизация",
"TAKE PHOTO": "СДЕЛАТЬ ФОТО",
"TEST": "ТЕСТ",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (секунд)",
"TOOL": "НАСАДКА",
"TOOL NAME": "НАЗВАНИЕ НАСАДКИ",
"TOOLBAY NAME": "ДЕРЖАТЕЛЬ НАСАДОК",
"Take Photo": "Сделать фото",
"Take a Photo": "Сделать фото",
"Time": "Время",
"Time in milliseconds": "Время в миллисекундах",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "До",
"Value": "Значение",
"Variable": "Переменная",
"Verify Password": "Повторите Пароль",
"Version": "Версия",
"WAIT": "ОЖДИНАИЕ",
"WRITE PIN": "ЗАПИСАТЬ PIN",
"Wait": "Ожидание",
"Weed Detector": "Детектор Сорняков",
"Week": "Неделя",
"Write Pin": "Записать Pin",
"X": "X",
"X (mm)": "X (мм)",
"X AXIS": "Ось X",
"Y": "Y",
"Y (mm)": "Y (мм)",
"Y AXIS": "Ось Y",
"Your Name": "Ваше Имя",
"Z": "Z",
"Z (mm)": "Z (мм)",
"Z AXIS": "Ось Z",
"calling FarmBot with credentials": "запрос данных",
"days old": "дней",
"downloading device credentials": "загрузка данных устройства",
"initiating connection": "установка соеденения",
"never connected to device": "никогда не подключалось к устройству",
"no": "нет",
"yes": "да"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

View File

@ -0,0 +1,160 @@
module.exports = {
"ACCELERATE FOR (steps)": "ACCELERATE FOR (steps)",
"Account Settings": "Account Settings",
"Add": "Add",
"Add Farm Event": "Add Farm Event",
"Age": "Age",
"Agree to Terms of Service": "Agree to Terms of Service",
"ALLOW NEGATIVES": "ALLOW NEGATIVES",
"BACK": "BACK",
"Bot ready": "Bot ready",
"CALIBRATE {{axis}}": "CALIBRATE {{axis}}",
"CALIBRATION": "CALIBRATION",
"calling FarmBot with credentials": "calling FarmBot with credentials",
"Camera": "Camera",
"Choose a species": "Choose a species",
"Confirm Password": "Confirm Password",
"CONTROLLER": "CONTROLLER",
"Copy": "Copy",
"Could not download sync data": "Could not download sync data",
"Create Account": "Create Account",
"Create An Account": "Create An Account",
"Crop Info": "Crop Info",
"Data Label": "Data Label",
"Day {{day}}": "Day {{day}}",
"days old": "days old",
"Delete": "Delete",
"DELETE ACCOUNT": "DELETE ACCOUNT",
"Delete this plant": "Delete this plant",
"Designer": "Designer",
"DEVICE": "DEVICE",
"downloading device credentials": "downloading device credentials",
"Drag and drop into map": "Drag and drop into map",
"DRAG STEP HERE": "DRAG STEP HERE",
"Edit": "Edit",
"EDIT": "EDIT",
"Edit Farm Event": "Edit Farm Event",
"Email": "Email",
"ENABLE ENCODERS": "ENABLE ENCODERS",
"Enter Email": "Enter Email",
"Enter Password": "Enter Password",
"Error establishing socket connection": "Error establishing socket connection",
"Execute Script": "Execute Script",
"EXECUTE SCRIPT": "EXECUTE SCRIPT",
"Execute Sequence": "Execute Sequence",
"EXECUTE SEQUENCE": "EXECUTE SEQUENCE",
"Factory Reset": "Factory Reset",
"Farm Events": "Farm Events",
"FIRMWARE": "FIRMWARE",
"Forgot Password": "Forgot Password",
"GO": "GO",
"I Agree to the Terms of Service": "I Agree to the Terms of Service",
"I agree to the terms of use": "I agree to the terms of use",
"If Statement": "If Statement",
"IF STATEMENT": "IF STATEMENT",
"Import coordinates from": "Import coordinates from",
"initiating connection": "initiating connection",
"INVERT ENDPOINTS": "INVERT ENDPOINTS",
"INVERT MOTORS": "INVERT MOTORS",
"LENGTH (m)": "LENGTH (m)",
"Location": "Location",
"Login": "Login",
"Logout": "Logout",
"Message": "Message",
"Move Absolute": "Move Absolute",
"MOVE ABSOLUTE": "MOVE ABSOLUTE",
"MOVE AMOUNT (mm)": "MOVE AMOUNT (mm)",
"Move Relative": "Move Relative",
"MOVE RELATIVE": "MOVE RELATIVE",
"NAME": "NAME",
"NETWORK": "NETWORK",
"never connected to device": "never connected to device",
"New Password": "New Password",
"no": "no",
"Not Connected to bot": "Not Connected to bot",
"Old Password": "Old Password",
"Operator": "Operator",
"Package Name": "Package Name",
"Parameters": "Parameters",
"Password": "Password",
"Pin {{num}}": "Pin {{num}}",
"Pin Mode": "Pin Mode",
"Pin Number": "Pin Number",
"Plant Info": "Plant Info",
"Plants": "Plants",
"Problem Loading Terms of Service": "Problem Loading Terms of Service",
"Read Pin": "Read Pin",
"READ PIN": "READ PIN",
"Regimen Name": "Regimen Name",
"Regimens": "Regimens",
"Repeats Every": "Repeats Every",
"Request sent": "Request sent",
"Reset": "Reset",
"RESET": "RESET",
"Reset Password": "Reset Password",
"Reset your password": "Reset your password",
"RESTART": "RESTART",
"RESTART FARMBOT": "RESTART FARMBOT",
"Save": "Save",
"SAVE": "SAVE",
"Send Message": "Send Message",
"SEND MESSAGE": "SEND MESSAGE",
"Send Password reset": "Send Password reset",
"Sequence": "Sequence",
"Sequence Editor": "Sequence Editor",
"Sequence or Regimen": "Sequence or Regimen",
"Sequences": "Sequences",
"Server Port": "Server Port",
"Server URL": "Server URL",
"SHUTDOWN": "SHUTDOWN",
"SHUTDOWN FARMBOT": "SHUTDOWN FARMBOT",
"SLOT": "SLOT",
"Socket Connection Established": "Socket Connection Established",
"Speed": "Speed",
"Started": "Started",
"Starts": "Starts",
"STATUS": "STATUS",
"Steps per MM": "Steps per MM",
"Sync Required": "Sync Required",
"Take a Photo": "Take a Photo",
"Take Photo": "Take Photo",
"TAKE PHOTO": "TAKE PHOTO",
"TEST": "TEST",
"Time": "Time",
"Time in milliseconds": "Time in milliseconds",
"TIMEOUT AFTER (seconds)": "TIMEOUT AFTER (seconds)",
"TOOL": "TOOL",
"TOOL NAME": "TOOL NAME",
"TOOLBAY NAME": "TOOLBAY NAME",
"Tried to delete Farm Event": "Tried to delete Farm Event",
"Tried to delete plant": "Tried to delete plant",
"Tried to save Farm Event": "Tried to save Farm Event",
"Tried to save plant": "Tried to save plant",
"Tried to update Farm Event": "Tried to update Farm Event",
"Unable to delete sequence": "Unable to delete sequence",
"Unable to download device credentials": "Unable to download device credentials",
"Until": "Until",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"Value": "Value",
"Variable": "Variable",
"Verify Password": "Verify Password",
"Version": "Version",
"Wait": "Wait",
"WAIT": "WAIT",
"Weed Detector": "Weed Detector",
"Week": "Week",
"Write Pin": "Write Pin",
"WRITE PIN": "WRITE PIN",
"X": "X",
"X (mm)": "X (mm)",
"X AXIS": "X AXIS",
"Y": "Y",
"Y (mm)": "Y (mm)",
"Y AXIS": "Y AXIS",
"yes": "yes",
"Your Name": "Your Name",
"Z": "Z",
"Z (mm)": "Z (mm)",
"Z AXIS": "Z AXIS"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,134 @@
{
"url": "https://api.github.com/repos/FarmBot/farmbot_os/releases/6838828",
"assets_url": "https://api.github.com/repos/FarmBot/farmbot_os/releases/6838828/assets",
"upload_url": "https://uploads.github.com/repos/FarmBot/farmbot_os/releases/6838828/assets{?name,label}",
"html_url": "https://github.com/FarmBot/farmbot_os/releases/tag/v4.0.1",
"id": 6838828,
"tag_name": "v4.0.1",
"target_commitish": "master",
"name": "Version 4.0.1 (Emergent Echinacea)",
"draft": false,
"author": {
"login": "ConnorRigby",
"id": 21150830,
"avatar_url": "https://avatars0.githubusercontent.com/u/21150830?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/ConnorRigby",
"html_url": "https://github.com/ConnorRigby",
"followers_url": "https://api.github.com/users/ConnorRigby/followers",
"following_url": "https://api.github.com/users/ConnorRigby/following{/other_user}",
"gists_url": "https://api.github.com/users/ConnorRigby/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ConnorRigby/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ConnorRigby/subscriptions",
"organizations_url": "https://api.github.com/users/ConnorRigby/orgs",
"repos_url": "https://api.github.com/users/ConnorRigby/repos",
"events_url": "https://api.github.com/users/ConnorRigby/events{/privacy}",
"received_events_url": "https://api.github.com/users/ConnorRigby/received_events",
"type": "User",
"site_admin": false
},
"prerelease": false,
"created_at": "2017-06-26T17:13:27Z",
"published_at": "2017-06-26T17:41:15Z",
"assets": [
{
"url": "https://api.github.com/repos/FarmBot/farmbot_os/releases/assets/4187434",
"id": 4187434,
"name": "farmbot-rpi3-4.0.1.fw",
"label": null,
"uploader": {
"login": "ConnorRigby",
"id": 21150830,
"avatar_url": "https://avatars0.githubusercontent.com/u/21150830?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/ConnorRigby",
"html_url": "https://github.com/ConnorRigby",
"followers_url": "https://api.github.com/users/ConnorRigby/followers",
"following_url": "https://api.github.com/users/ConnorRigby/following{/other_user}",
"gists_url": "https://api.github.com/users/ConnorRigby/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ConnorRigby/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ConnorRigby/subscriptions",
"organizations_url": "https://api.github.com/users/ConnorRigby/orgs",
"repos_url": "https://api.github.com/users/ConnorRigby/repos",
"events_url": "https://api.github.com/users/ConnorRigby/events{/privacy}",
"received_events_url": "https://api.github.com/users/ConnorRigby/received_events",
"type": "User",
"site_admin": false
},
"content_type": "application/octet-stream",
"state": "uploaded",
"size": 54072382,
"download_count": 0,
"created_at": "2017-06-26T17:18:10Z",
"updated_at": "2017-06-26T17:23:06Z",
"browser_download_url": "https://github.com/FarmBot/farmbot_os/releases/download/v4.0.1/farmbot-rpi3-4.0.1.fw"
},
{
"url": "https://api.github.com/repos/FarmBot/farmbot_os/releases/assets/4187435",
"id": 4187435,
"name": "farmbot-rpi3-4.0.1.img",
"label": null,
"uploader": {
"login": "ConnorRigby",
"id": 21150830,
"avatar_url": "https://avatars0.githubusercontent.com/u/21150830?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/ConnorRigby",
"html_url": "https://github.com/ConnorRigby",
"followers_url": "https://api.github.com/users/ConnorRigby/followers",
"following_url": "https://api.github.com/users/ConnorRigby/following{/other_user}",
"gists_url": "https://api.github.com/users/ConnorRigby/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ConnorRigby/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ConnorRigby/subscriptions",
"organizations_url": "https://api.github.com/users/ConnorRigby/orgs",
"repos_url": "https://api.github.com/users/ConnorRigby/repos",
"events_url": "https://api.github.com/users/ConnorRigby/events{/privacy}",
"received_events_url": "https://api.github.com/users/ConnorRigby/received_events",
"type": "User",
"site_admin": false
},
"content_type": "application/x-raw-disk-image",
"state": "uploaded",
"size": 343977472,
"download_count": 0,
"created_at": "2017-06-26T17:18:10Z",
"updated_at": "2017-06-26T17:31:08Z",
"browser_download_url": "https://github.com/FarmBot/farmbot_os/releases/download/v4.0.1/farmbot-rpi3-4.0.1.img"
},
{
"url": "https://api.github.com/repos/FarmBot/farmbot_os/releases/assets/4187433",
"id": 4187433,
"name": "farmbot.rootfs-rpi3-4.0.1.tar.gz",
"label": null,
"uploader": {
"login": "ConnorRigby",
"id": 21150830,
"avatar_url": "https://avatars0.githubusercontent.com/u/21150830?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/ConnorRigby",
"html_url": "https://github.com/ConnorRigby",
"followers_url": "https://api.github.com/users/ConnorRigby/followers",
"following_url": "https://api.github.com/users/ConnorRigby/following{/other_user}",
"gists_url": "https://api.github.com/users/ConnorRigby/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ConnorRigby/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ConnorRigby/subscriptions",
"organizations_url": "https://api.github.com/users/ConnorRigby/orgs",
"repos_url": "https://api.github.com/users/ConnorRigby/repos",
"events_url": "https://api.github.com/users/ConnorRigby/events{/privacy}",
"received_events_url": "https://api.github.com/users/ConnorRigby/received_events",
"type": "User",
"site_admin": false
},
"content_type": "application/gzip",
"state": "uploaded",
"size": 155449054,
"download_count": 0,
"created_at": "2017-06-26T17:18:10Z",
"updated_at": "2017-06-26T17:21:50Z",
"browser_download_url": "https://github.com/FarmBot/farmbot_os/releases/download/v4.0.1/farmbot.rootfs-rpi3-4.0.1.tar.gz"
}
],
"tarball_url": "https://api.github.com/repos/FarmBot/farmbot_os/tarball/v4.0.1",
"zipball_url": "https://api.github.com/repos/FarmBot/farmbot_os/zipball/v4.0.1",
"body": "* fix bug in E-Stopping."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

View File

@ -0,0 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
# Disallow: /

View File

@ -0,0 +1,22 @@
import * as React from "react";
import { t } from "i18next";
// No reusability here. Why not just keep it from taking up sass?
const STYLES = {
"textAlign": "center",
"marginTop": "5rem"
};
export class FourOhFour extends React.Component<{}, {}> {
render() {
return (
<div className="404">
<div className="all-content-wrapper">
<h1 style={STYLES}>
{t("Page Not Found.")}
</h1>
</div>
</div>
);
}
}

View File

@ -0,0 +1,25 @@
import * as React from "react";
import { noop } from "lodash";
import { Everything } from "../interfaces";
import { location } from "./fake_state/location";
import { peripherals } from "./fake_state/peripherals";
import { auth } from "./fake_state/token";
import { bot } from "./fake_state/bot";
import { config } from "./fake_state/config";
import { draggable } from "./fake_state/draggable";
import { resources } from "./fake_state/resources";
/** Factory function for empty state object. */
export function fakeState(dispatcher: Function = noop): Everything {
return {
dispatch: jest.fn(),
router: { push: jest.fn() },
location,
peripherals,
auth,
bot,
config,
draggable,
resources
}
}

View File

@ -0,0 +1,32 @@
import { Everything } from "../../interfaces";
export let bot: Everything["bot"] = {
"stepSize": 100,
"controlPanelState": {
"homing_and_calibration": false,
"motors": false,
"encoders_and_endstops": false,
"danger_zone": false
},
"hardware": {
"mcu_params": {},
"location": [
-1,
-1,
-1
],
"pins": {},
"configuration": {},
"informational_settings": {},
"user_env": {},
"process_info": {
"farmwares": {}
}
},
"x_axis_inverted": false,
"y_axis_inverted": false,
"z_axis_inverted": false,
"dirty": false,
"currentOSVersion": "3.1.6"
};

View File

@ -0,0 +1,6 @@
import { Everything } from "../../interfaces";
export let config: Everything["config"] = {
"host": "localhost",
"port": "3000"
};

View File

@ -0,0 +1,5 @@
import { Everything } from "../../interfaces";
export let draggable: Everything["draggable"] = {
"dataTransfer": {}
};

View File

@ -0,0 +1,55 @@
import { TaggedImage } from "../../resources/tagged_resources";
export let fakeImages: TaggedImage[] = [
{
"kind": "images",
"body": {
"id": 9,
"device_id": 8,
"attachment_processed_at": "2017-06-01T14:16:55.709Z",
"updated_at": "2017-06-01T14:16:55.715Z",
"created_at": "2017-06-01T14:15:50.666Z",
"attachment_url": "imgur.com",
"meta": {
"x": 632,
"y": 347,
"z": 164
}
},
"uuid": "images.9.3"
},
{
"kind": "images",
"body": {
"id": 8,
"device_id": 8,
"attachment_processed_at": "2017-06-01T14:16:45.899Z",
"updated_at": "2017-06-01T14:16:45.903Z",
"created_at": "2017-06-01T14:14:22.747Z",
"attachment_url": "imgur.com",
"meta": {
"x": 632,
"y": 347,
"z": 164
}
},
"uuid": "images.8.4"
},
{
"kind": "images",
"body": {
"id": 7,
"device_id": 8,
"attachment_processed_at": "2017-06-01T14:16:34.839Z",
"updated_at": "2017-06-01T14:16:34.984Z",
"created_at": "2017-06-01T14:14:22.726Z",
"attachment_url": "imgur.com",
"meta": {
"x": 266,
"y": 330,
"z": 53
}
},
"uuid": "images.7.5"
}
]

View File

@ -0,0 +1,11 @@
import { Everything } from "../../interfaces";
export let location: Everything["location"] = {
pathname: "/app/designer",
/** EX: */
search: "?id=twowing-silverbell&p1=SpeciesInfo",
hash: "¯\_(ツ)_/¯",
action: "PUSH",
key: "jhedoi",
query: { query: "string" }
};

View File

@ -0,0 +1,5 @@
import { Everything } from "../../interfaces";
export let peripherals: Everything["peripherals"] = {
"isEditing": true
};

View File

@ -0,0 +1,4 @@
import { Everything } from "../../interfaces";
import { buildResourceIndex } from "../resource_index_builder";
export let resources: Everything["resources"] = buildResourceIndex();

View File

@ -0,0 +1,17 @@
import { Everything } from "../../interfaces";
export let auth: Everything["auth"] = {
"token": {
"unencoded": {
"iat": 1495569084,
"jti": "b38915ca-3d7a-4754-8152-d4306b88504c",
"iss": "//localhost:3000",
"exp": 1499025084,
"mqtt": "10.0.0.6",
"os_update_server": "https://api.github.com/repos/farmbot/farmbot_os/releases/latest",
"fw_update_server": "https://api.github.com/repos/FarmBot/farmbot-arduino-firmware/releases/latest",
"bot": "device_403"
},
"encoded": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbkBhZG1pbi5jb20iLCJpYXQiOjE0OTU1NjkwODQsImp0aSI6ImIzODkxNWNhLTNkN2EtNDc1NC04MTUyLWQ0MzA2Yjg4NTA0YyIsImlzcyI6Ii8vbG9jYWxob3N0OjMwMDAiLCJleHAiOjE0OTkwMjUwODQsIm1xdHQiOiIxMC4wLjAuNiIsIm9zX3VwZGF0ZV9zZXJ2ZXIiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2Zhcm1ib3QvZmFybWJvdF9vcy9yZWxlYXNlcy9sYXRlc3QiLCJmd191cGRhdGVfc2VydmVyIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9GYXJtQm90L2Zhcm1ib3QtYXJkdWluby1maXJtd2FyZS9yZWxlYXNlcy9sYXRlc3QiLCJib3QiOiJkZXZpY2VfNDAzIn0.Gie_-X5F_CrnmrF8AGxnXcfOHS1sK3eFqLectr3Wa-TnIZbIFMr3bVrRT53GPPb7C4HKIdMwfgGYxpaGSOD77qa0qnxw1FraXTnJgbIJXKipBVN9UQ4PqcYgjAVdZ678A-XqXV6SGE624zdr7S7mQ6uj7qpa2LMH4P37R3BIB26G7E8xDcVOGqL5Oiwr9DPajBX3zdhXSbH3k4PyxqvPOLYso-R7kjfpOnfFCMfMZLW8TQtg-yj82zs93RP2DHOOx-jxek69tmgNyP3FJaoWHwHW7bXOEv09p3dhNVTCSVNKD9LZczLpuXV7U4oSmL6KLkbzsM6G0P9rrbJ9ASYaOw"
}
};

View File

@ -0,0 +1,30 @@
import * as moment from "moment";
import {
FarmEventWithExecutable
} from "../farm_designer/farm_events/calendar/interfaces";
export const TIME = {
MONDAY: moment("2017-06-19 01:30:00 -0500"),
TUESDAY: moment("2017-06-20 02:00:00 -0500"),
WEDNESDAY: moment("2017-06-21 03:45:00 -0500"),
THURSDAY: moment("2017-06-22 14:00:00 -0500"),
FRIDAY: moment("2017-06-23 00:05:37 -0500"),
SATURDAY: moment("2017-06-24 23:00:00 -0500")
};
export let fake_fe = (): FarmEventWithExecutable => {
return {
id: 1,
start_time: "---",
repeat: 5,
time_unit: "daily",
executable_id: 23,
executable_type: "Sequence",
executable: {
color: "red",
name: "faker",
kind: "sequence",
args: { version: 0 }
}
}
};

View File

@ -0,0 +1,26 @@
// https://stackoverflow.com
// /questions/32911630/how-do-i-deal-with-localstorage-in-jest-tests
// https://github.com/facebook/jest/issues/2098
//browserMocks.js
var localStorageMock = (function () {
var store = {};
return {
getItem: function (key) {
return store[key] || null;
},
setItem: function (key, value) {
store[key] = value.toString();
},
clear: function () {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});

View File

@ -0,0 +1,275 @@
import { resourceReducer } from "../resources/reducer";
import { TaggedResource } from "../resources/tagged_resources";
import * as _ from "lodash";
import { createStore } from "redux";
let FAKE_RESOURCES: TaggedResource[] = [
{
"kind": "device",
"body": {
"id": 415,
"name": "wispy-firefly-846",
"webcam_url": undefined
},
"uuid": "device.415.0"
},
{
"kind": "farm_events",
"body": {
"id": 21,
"start_time": "2017-05-22T05:00:00.000Z",
"end_time": "2017-05-30T05:00:00.000Z",
"repeat": 1,
"time_unit": "daily",
"executable_id": 23,
"executable_type": "Sequence",
"calendar": [
"2017-05-25T05:00:00.000Z",
"2017-05-26T05:00:00.000Z",
"2017-05-27T05:00:00.000Z",
"2017-05-28T05:00:00.000Z",
"2017-05-29T05:00:00.000Z"
]
},
"uuid": "farm_events.21.1"
},
{
"kind": "farm_events",
"body": {
"id": 22,
"start_time": "2017-05-22T05:00:00.000Z",
"end_time": "2017-05-29T05:00:00.000Z",
"repeat": 2,
"time_unit": "daily",
"executable_id": 24,
"executable_type": "Sequence",
"calendar": [
"2017-05-26T05:00:00.000Z",
"2017-05-28T05:00:00.000Z"
]
},
"uuid": "farm_events.22.2"
},
{
"kind": "images",
"body": {
"id": 415,
"device_id": 415,
"attachment_processed_at": undefined,
"updated_at": "2017-05-24T20:41:19.766Z",
"created_at": "2017-05-24T20:41:19.766Z",
"attachment_url": "https://placehold.it/640%3Ftext=Processing...",
"meta": {
"x": 928,
"y": 428,
"z": 144
}
},
"uuid": "images.415.3"
},
{
"kind": "images",
"body": {
"id": 414,
"device_id": 415,
"attachment_processed_at": undefined,
"updated_at": "2017-05-24T20:41:19.691Z",
"created_at": "2017-05-24T20:41:19.691Z",
"attachment_url": "http://placehold.it/640%3Ftext=Processing...",
"meta": {
"x": 853,
"y": 429,
"z": 165
}
},
"uuid": "images.414.4"
},
{
"kind": "peripherals",
"body": {
"id": 11,
"pin": 13,
"mode": 0,
"label": "LED"
},
"uuid": "peripherals.11.5"
},
{
"kind": "points",
"body": {
"id": 1392,
"created_at": "2017-05-24T20:41:19.804Z",
"updated_at": "2017-05-24T20:41:19.804Z",
"device_id": 415,
"meta": {
},
"name": "fenestrate-flower-3632",
"pointer_type": "Plant",
"radius": 46,
"x": 347,
"y": 385,
"z": 0,
"openfarm_slug": "radish"
},
"uuid": "points.1392.6"
},
{
"kind": "points",
"body": {
"id": 1393,
"created_at": "2017-05-24T20:41:19.822Z",
"updated_at": "2017-05-24T20:41:19.822Z",
"device_id": 415,
"meta": {
},
"name": "alate-fire-7363",
"pointer_type": "Plant",
"radius": 36,
"x": 727,
"y": 376,
"z": 0,
"openfarm_slug": "garlic"
},
"uuid": "points.1393.7"
},
{
"kind": "points",
"body": {
"id": 1394,
"created_at": "2017-05-24T20:41:19.855Z",
"updated_at": "2017-05-24T20:41:19.855Z",
"device_id": 415,
"meta": {
"color": undefined,
"created_by": "plant-detection"
},
"name": "untitled",
"pointer_type": "GenericPointer",
"radius": 6,
"x": 1245,
"y": 637,
"z": 5
},
"uuid": "points.1394.8"
},
{
"kind": "points",
"body": {
"id": 1395,
"created_at": "2017-05-24T20:41:19.889Z",
"updated_at": "2017-05-24T20:41:19.889Z",
"device_id": 415,
"meta": {
"color": "gray",
"created_by": "plant-detection"
},
"name": "untitled",
"pointer_type": "GenericPointer",
"radius": 10,
"x": 490,
"y": 421,
"z": 5
},
"uuid": "points.1395.9"
},
{
"kind": "points",
"body": {
"id": 1396,
"created_at": "2017-05-24T20:41:20.112Z",
"updated_at": "2017-05-24T20:41:20.112Z",
"device_id": 415,
"meta": {
},
"name": "Slot One.",
"pointer_type": "ToolSlot",
"radius": 25,
"x": 10,
"y": 10,
"z": 10,
"tool_id": 14
},
"uuid": "points.1396.10"
},
{
"kind": "regimens",
"body": {
"id": 11,
"name": "Test Regimen 456",
"color": "gray",
"device_id": 415,
"regimen_items": [
{
"id": 33,
"regimen_id": 11,
"sequence_id": 23,
"time_offset": 345900000
}
]
},
"uuid": "regimens.11.46"
},
{
"kind": "sequences",
"body": {
"id": 23,
"name": "Goto 0, 0, 0",
"color": "gray",
"body": [
{
"kind": "move_absolute",
"args": {
"location": {
"kind": "coordinate",
"args": {
"x": 0,
"y": 0,
"z": 0
}
},
"offset": {
"kind": "coordinate",
"args": {
"x": 0,
"y": 0,
"z": 0
}
},
"speed": 800
}
}
],
"args": {
"is_outdated": false,
"version": 4
},
"kind": "sequence"
},
"uuid": "sequences.23.47"
},
{
"kind": "tools",
"body": {
"id": 14,
"name": "Trench Digging Tool",
"status": "active"
},
"uuid": "tools.14.49"
}
];
export
function buildResourceIndex(resources: TaggedResource[] = FAKE_RESOURCES) {
const KIND: keyof TaggedResource = "kind"; // Safety first, kids.
let store = createStore(resourceReducer);
_(resources)
.groupBy(KIND)
.pairs()
.map((x: [(TaggedResource["kind"]), TaggedResource[]]) => x)
.map(y => ({ type: "RESOURCE_READY", payload: { name: y[0], data: y[1] } }))
.map(store.dispatch);
return store.getState();
}

View File

@ -0,0 +1,7 @@
import * as React from "react";
export class Wrapper extends React.Component<any, any> {
render() {
return <div> {this.props.children} </div>;
}
}

View File

@ -0,0 +1,11 @@
import * as React from "react";
import { Spinner } from "../spinner";
import { render } from "enzyme";
describe("spinner", () => {
it("renders a circle", () => {
let spinner = render(<Spinner radius={5} strokeWidth={5} />);
let circles = spinner.find("circle");
expect(circles.length).toEqual(1);
});
});

View File

@ -0,0 +1,10 @@
import * as React from "react";
import { mount } from "enzyme";
import { FourOhFour } from "../404";
describe("<FourOhFour/>", function () {
it("renders helpful text", function () {
let dom = mount(<FourOhFour />);
expect(dom.html()).toContain("Page Not Found");
});
});

View File

@ -0,0 +1,12 @@
import { history, push } from "../history";
describe("push()", () => {
it("calls history with a URL", () => {
const URL = "/wow.html";
let oldFn = history.push;
history.push = jest.fn();
push(URL);
expect(history.push).toHaveBeenCalledWith(URL);
history.push = oldFn;
});
});

View File

@ -0,0 +1,136 @@
import {
prettyPrintApiErrors,
defensiveClone,
getParam,
betterCompact,
safeStringFetch,
oneOf,
semverCompare,
SemverResult
} from "../util";
describe("util", () => {
describe("safeStringFetch", () => {
let data = {
"null": null,
"undefined": undefined,
"number": 0,
"string": "hello",
"boolean": false,
"other": () => { "not allowed!" }
};
it("fetches null", () => {
expect(safeStringFetch(data, "null")).toEqual("");
});
it("fetches undefined", () => {
expect(safeStringFetch(data, "undefined")).toEqual("");
});
it("fetches number", () => {
expect(safeStringFetch(data, "number")).toEqual("0");
});
it("fetches string", () => {
expect(safeStringFetch(data, "string")).toEqual("hello");
});
it("fetches boolean", () => {
expect(safeStringFetch(data, "boolean")).toEqual("false");
});
it("handles others with exception", () => {
expect(() => safeStringFetch(data, "other")).toThrow();
});
});
describe("betterCompact", () => {
it("removes falsy values", () => {
let before = [{}, {}, undefined];
let after = betterCompact(before);
expect(after.length).toBe(2);
expect(after).not.toContain(undefined);
});
});
describe("defensiveClone", () => {
it("deep clones any serializable object", () => {
let origin = { a: "b", c: 2, d: [{ e: { f: "g" } }] };
let child = defensiveClone(origin);
origin.a = "--";
origin.c = 0;
origin.d[0].e.f = "--";
expect(child).not.toBe(origin);
expect(child.a).toEqual("b");
expect(child.c).toEqual(2);
expect(child.d[0].e.f).toEqual("g");
});
});
describe("getParam", () => {
it("gets params", () => {
Object.defineProperty(window.location, "search", {
writable: true,
value: "?foo=bar&baz=wow"
});
expect(getParam("foo")).toEqual("bar");
expect(getParam("baz")).toEqual("wow");
expect(getParam("blah")).toEqual("");
});
});
describe("prettyPrintApiErrors", () => {
it("handles properly formatted API error messages", () => {
let result = prettyPrintApiErrors({
response: {
data: {
email: "can't be blank"
}
}
});
expect(result).toEqual("Email: can't be blank.");
});
});
describe("oneOf()", () => {
it("determines matches", () => {
expect(oneOf(["foo"], "foobar")).toBeTruthy();
expect(oneOf(["foo", "baz"], "foo bar baz")).toBeTruthy();
});
it("determines non-matches", () => {
expect(oneOf(["foo"], "QMMADSDASDASD")).toBeFalsy();
expect(oneOf(["foo", "baz"], "nothing to see here.")).toBeFalsy();
})
});
describe("semver compare", () => {
it("knows when RIGHT_IS_GREATER", () => {
expect(semverCompare("3.1.6", "4.0.0"))
.toBe(SemverResult.RIGHT_IS_GREATER);
expect(semverCompare("2.1.6", "4.1.0"))
.toBe(SemverResult.RIGHT_IS_GREATER);
expect(semverCompare("4.1.6", "5.1.9"))
.toBe(SemverResult.RIGHT_IS_GREATER);
expect(semverCompare("1.1.9", "2.0.2"))
.toBe(SemverResult.RIGHT_IS_GREATER);
});
it("knows when LEFT_IS_GREATER", () => {
expect(semverCompare("4.0.0", "3.1.6"))
.toBe(SemverResult.LEFT_IS_GREATER);
expect(semverCompare("4.1.0", "2.1.6"))
.toBe(SemverResult.LEFT_IS_GREATER);
expect(semverCompare("5.1.9", "4.1.6"))
.toBe(SemverResult.LEFT_IS_GREATER);
expect(semverCompare("2.0.2", "1.1.9"))
.toBe(SemverResult.LEFT_IS_GREATER);
});
})
});

View File

@ -0,0 +1,21 @@
import * as React from "react";
import { mount } from "enzyme";
import { ChangePassword } from "../components";
import { ChangePwPropTypes } from "../interfaces";
describe("<ChangePassword/>", function () {
it("saves new user password", function () {
let props: ChangePwPropTypes = {
password: "wow",
new_password: "wow",
new_password_confirmation: "wow",
set: jest.fn(),
save: jest.fn()
};
let dom = mount(<ChangePassword {...props} />);
expect(props.save).not.toHaveBeenCalled();
dom.find("button").simulate("click");
expect(props.save).toHaveBeenCalled();
expect(dom.html()).toContain("password");
});
});

View File

@ -0,0 +1,19 @@
import * as React from "react";
import { mount } from "enzyme";
import { Settings } from "../components";
import { SettingsPropTypes } from "../interfaces";
describe("<Settings/>", function () {
it("saves user settings", function () {
let props: SettingsPropTypes = {
name: "new_name",
email: "new_email",
set: jest.fn(),
save: jest.fn()
};
let dom = mount(<Settings {...props} />);
expect(props.save).not.toHaveBeenCalled();
dom.find("button").simulate("click");
expect(props.save).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,28 @@
import * as axios from "axios";
import { Thunk } from "../redux/interfaces";
import { API } from "../api";
import { DeletionRequest } from "./interfaces";
import { toastErrors } from "../util";
import { Session } from "../session";
export function deleteUser(payload: DeletionRequest): Thunk {
return (dispatch, getState) => {
let state = getState().auth;
if (state) {
// https://github.com/mzabriskie/axios/issues/312
axios<{}>({
method: "delete",
url: API.current.usersPath,
data: payload,
params: { force: true }
})
.then(resp => {
alert("We're sorry to see you go. :(");
Session.clear(true);
})
.catch(toastErrors);
} else {
throw new Error("Impossible");
}
};
}

View File

@ -0,0 +1,57 @@
import * as React from "react";
import { t } from "i18next";
import { BlurableInput, Widget, WidgetHeader, WidgetBody } from "../../ui";
import { ChangePwPropTypes } from "../interfaces";
export class ChangePassword extends React.Component<ChangePwPropTypes, {}> {
render() {
let { set, save, password, new_password } = this.props;
let npc = this.props.new_password_confirmation;
let npcString = "new_password_confirmation";
return <Widget>
<WidgetHeader title="Change Password">
<button
onClick={save}
className="green fb-button"
type="button"
>
{t("SAVE")}
</button>
</WidgetHeader>
<WidgetBody>
<form>
<label>
{t("Old Password")}
</label>
<BlurableInput
allowEmpty={true}
onCommit={set}
name="password"
value={password || ""}
type="password"
/>
<label>
{t("New Password")}
</label>
<BlurableInput
allowEmpty={true}
onCommit={set}
name="new_password"
value={new_password || ""}
type="password"
/>
<label>
{t("New Password")}
</label>
<BlurableInput
allowEmpty={true}
onCommit={set}
name={npcString}
value={npc || ""}
type="password"
/>
</form>
</WidgetBody>
</Widget>;
}
}

View File

@ -0,0 +1,56 @@
import * as React from "react";
import { t } from "i18next";
import {
BlurableInput,
Widget,
WidgetHeader,
WidgetBody,
Col,
Row
} from "../../ui";
import { DeleteAccountPropTypes } from "../interfaces";
import { Content } from "../../constants";
export class DeleteAccount extends React.Component<DeleteAccountPropTypes, {}> {
render() {
let { set, deletion_confirmation, save } = this.props;
return <Widget>
<WidgetHeader title="Delete Account" />
<WidgetBody>
<div>
{Content.ACCOUNT_DELETE_WARNING}
<br /><br />
{t(`If you are sure you want to delete your account, type in
your password below to continue.`)}
<br /><br />
</div>
<form>
<Row>
<Col xs={12}>
<label>
{t("Enter Password")}
</label>
</Col>
<Col xs={8}>
<BlurableInput
onCommit={set}
name="deletion_confirmation"
allowEmpty={true}
value={deletion_confirmation || ""}
type="password" />
</Col>
<Col xs={4}>
<button
onClick={save}
className="red fb-button"
type="button"
>
{t("Delete Account")}
</button>
</Col>
</Row>
</form>
</WidgetBody>
</Widget>;
}
}

View File

@ -0,0 +1,3 @@
export * from "./change_password";
export * from "./delete_account";
export * from "./settings";

View File

@ -0,0 +1,43 @@
import * as React from "react";
import { t } from "i18next";
import { BlurableInput, Widget, WidgetHeader, WidgetBody } from "../../ui";
import { SettingsPropTypes } from "../interfaces";
export class Settings extends React.Component<SettingsPropTypes, {}> {
render() {
let { name, email, set, save } = this.props;
return <Widget>
<WidgetHeader title="Account Settings">
<button
className="green fb-button"
type="button"
onClick={save}
>
{t("SAVE")}
</button>
</WidgetHeader>
<WidgetBody>
<form>
<label>
{t("Your Name")}
</label>
<BlurableInput
onCommit={set}
name="name"
value={name || ""}
type="text"
/>
<label>
{t("Email")}
</label>
<BlurableInput
onCommit={set}
name="email"
value={email || ""}
type="email"
/>
</form>
</WidgetBody>
</Widget>;
}
}

View File

@ -0,0 +1,67 @@
import * as React from "react";
import { connect } from "react-redux";
import { Settings, DeleteAccount, ChangePassword } from "./components";
import { State, Props } from "./interfaces";
import { Page, Row, Col } from "../ui";
import { mapStateToProps } from "./state_to_props";
@connect(mapStateToProps)
export class Account extends React.Component<Props, State> {
constructor(props: Props) {
super();
this.state = {};
}
componentDidMount() {
if (this.props.user) {
let { name, email } = this.props.user.body;
this.setState({ name, email });
}
}
set = (event: React.FormEvent<HTMLInputElement>) => {
let { name, value } = event.currentTarget;
this.setState({ [name]: value });
}
savePassword = () => {
this.props.saveUser(this.props.dispatch, this.state);
this.setState({
password: "",
new_password: "",
new_password_confirmation: ""
});
}
render() {
return <Page className="account">
<Col xs={12} sm={6} smOffset={3}>
<Row>
<Settings name={this.state.name || ""}
email={this.state.email || ""}
set={this.set}
save={() => this.props.saveUser(this.props.dispatch, this.state)} />
</Row>
<Row>
<ChangePassword
password={this.state.password || ""}
new_password={this.state.new_password || ""}
new_password_confirmation=
{this.state.new_password_confirmation || ""}
set={this.set}
save={this.savePassword} />
</Row>
<Row>
<DeleteAccount
deletion_confirmation=
{this.state.deletion_confirmation || ""}
set={this.set}
save={() => this
.props
.enactDeletion(this.props.dispatch, this.state.deletion_confirmation)} />
</Row>
</Col>
</Page>;
}
}

View File

@ -0,0 +1,47 @@
import { AuthState, User } from "../auth/interfaces";
import { TaggedUser } from "../resources/tagged_resources";
export interface Props {
user: TaggedUser;
dispatch: Function;
saveUser(dispatch: Function, update: Partial<User>): void;
enactDeletion(dispatch: Function, deletion_confirmation: string | undefined): void;
}
/** JSON form that gets POSTed to the API when user updates their info. */
export interface UserInfo {
name: string;
email: string;
password: string;
new_password: string;
new_password_confirmation: string;
/** User must enter password confirmation to delete their account. */
deletion_confirmation: string;
}
export type State = Partial<UserInfo>;
export interface DeletionRequest {
password: string;
}
export interface DeleteAccountPropTypes {
deletion_confirmation: string | undefined;
set: React.EventHandler<React.FormEvent<HTMLInputElement>>;
save: React.EventHandler<React.MouseEvent<HTMLButtonElement>>;
}
export interface ChangePwPropTypes {
password: string | undefined;
new_password: string | undefined;
new_password_confirmation: string | undefined;
set: React.EventHandler<React.FormEvent<HTMLInputElement>>;
save: React.EventHandler<React.MouseEvent<HTMLButtonElement>>;
}
export interface SettingsPropTypes {
name: string;
email: string;
set: React.EventHandler<React.FormEvent<HTMLInputElement>>;
save: React.EventHandler<React.MouseEvent<HTMLButtonElement>>;
}

View File

@ -0,0 +1,23 @@
import { Everything } from "../interfaces";
import { deleteUser } from "./actions";
import { Props } from "./interfaces";
import { getUserAccountSettings } from "../resources/selectors";
import { User } from "../auth/interfaces";
import { edit, save } from "../api/crud";
export function mapStateToProps(props: Everything): Props {
let user = getUserAccountSettings(props.resources.index);
return {
user,
saveUser(dispatch: Function, update: Partial<User>) {
dispatch(edit(user, update));
dispatch(save(user.uuid))
},
enactDeletion(dispatch: Function, password: string | undefined) {
dispatch(deleteUser({ password: password || "NEVER SET" }));
},
dispatch: () => { throw new Error("NEVER SHOULD HAPPEN"); }
};
}

View File

@ -0,0 +1,127 @@
type ProtocolString = "http:" | "https:";
let current: API | undefined;
/** Record of all the relevant stuff in a string URL, except without all the
* stringly typed nonsense. */
interface UrlInfo {
protocol: string;
hostname: string;
port: string;
pathname: string;
search: string;
hash: string;
host: string;
}
/** Store all API endpoints in one place for the sake of DRYness.
* API.current is probably the instance you want to use. */
export class API {
/** Guesses the most appropriate API port based on a number of environment
* factors such as hostname and protocol (HTTP vs. HTTPS). */
static inferPort(): string {
// ATTEMPT 1: Most devs running a webpack server on localhost
// run the API on port 3000.
if (location.port === "8080") { return "3000"; }
// ATTEMPT 2: If they provide an explicit port (as in ://localhost:3000)
// use that port.
if (location.port) { return location.port; }
// ATTEMPT 3: If that doesn't work, check for HTTPS:// and use the
// default of 443.
if (API.parseURL(location.origin).protocol === "https:") {
return "443";
}
// All others just use port 80.
return "80";
}
static fetchBrowserLocation() {
return `//${window.location.hostname}:${API.inferPort()}`;
}
static fetchHostName() {
// Figured we could centralize this in case we change the method.
return window.location.hostname;
}
static parseURL(url: string): UrlInfo {
// Such an amazing hack!
var info = document.createElement("a");
info.href = url;
return info;
}
static setBaseUrl(base: string) {
current = new API(base);
}
/** The base URL can't be known until the user is logged in.
* API.current will give URLs is the base URL is known and throw an
* exception otherwise.
*/
static get current(): API {
if (current) {
return current;
} else {
throw new Error(`
Tried to access API before URL was resolved.
Call API.setBaseUrl() before using API.current .`);
}
};
/** "https:" or "http:". NO "//"! */
private readonly protocol: ProtocolString;
/** "localhost", "yahoo.com" */
private readonly hostname: string;
/** "80", "443" or "" */
private readonly port: string;
/** "/pathname/x/whatever" or "/foo/" */
private readonly pathname: string;
/** "?foo=bar" */
private readonly search: string;
/** "#hashfragment" */
private readonly hash: string;
/** "example.com:3000" */
private readonly host: string;
constructor(input: string) {
let url = API.parseURL(input);
this.protocol = url.protocol as ProtocolString;
this.hostname = url.hostname;
this.port = url.port;
this.pathname = url.pathname;
this.search = url.search;
this.hash = url.hash;
this.host = url.host;
}
/** http://localhost:3000 */
get baseUrl() { return `${this.protocol}//${this.host}`; };
/** /api/tokens/ */
get tokensPath() { return `${this.baseUrl}/api/tokens/`; };
/** /api/password_resets/ */
get passwordResetPath() { return `${this.baseUrl}/api/password_resets/`; };
/** /api/device/ */
get devicePath() { return `${this.baseUrl}/api/device/`; };
/** /api/users/ */
get usersPath() { return `${this.baseUrl}/api/users/`; };
/** /api/peripherals/ */
get peripheralsPath() { return `${this.baseUrl}/api/peripherals/`; };
/** /api/farm_events/ */
get farmEventsPath() { return `${this.baseUrl}/api/farm_events/`; };
/** /api/regimens/ */
get regimensPath() { return `${this.baseUrl}/api/regimens/`; };
/** /api/sequences/ */
get sequencesPath() { return `${this.baseUrl}/api/sequences/`; };
/** /api/tools/ */
get toolsPath() { return `${this.baseUrl}/api/tools/`; };
/** /api/images/ */
get imagesPath() { return `${this.baseUrl}/api/images/`; };
/** /api/points/ */
get pointsPath() { return `${this.baseUrl}/api/points/`; };
/** /api/points/search */
get pointSearchPath() { return `${this.pointsPath}/search/`; };
/** /api/logs */
get logsPath() { return `${this.baseUrl}/api/logs/`; };
}

View File

@ -0,0 +1,192 @@
import {
TaggedResource,
ResourceName,
isTaggedResource,
TaggedSequence,
} from "../resources/tagged_resources";
import { GetState, ReduxAction } from "../redux/interfaces";
import { API } from "./index";
import * as Axios from "axios";
import { updateOK, updateNO, destroyOK, destroyNO } from "../resources/actions";
import { UnsafeError } from "../interfaces";
import { findByUuid } from "../resources/reducer";
import { generateUuid } from "../resources/util";
import { defensiveClone } from "../util";
import { EditResourceParams } from "./interfaces";
import { ResourceIndex } from "../resources/interfaces";
import { SequenceBodyItem } from "farmbot/dist";
export function edit(tr: TaggedResource, update: Partial<typeof tr.body>):
ReduxAction<EditResourceParams> {
return {
type: "EDIT_RESOURCE",
payload: { uuid: tr.uuid, update: update }
};
}
/** Rather than update (patch) a TaggedResource, this method will overwrite
* everything within the `.body` property. */
export function overwrite(tr: TaggedResource, update: typeof tr.body):
ReduxAction<EditResourceParams> {
return {
type: "OVERWRITE_RESOURCE",
payload: { uuid: tr.uuid, update: update }
};
}
interface EditStepProps {
step: Readonly<SequenceBodyItem>;
sequence: Readonly<TaggedSequence>;
index: number;
/** Callback provides a fresh, defensively cloned copy of the
* original step. Perform modifications to the resource within this
* callback */
executor(stepCopy: SequenceBodyItem): void;
}
/** Editing sequence steps is a tedious process. Use this function in place
* of `edit()` or `overwrite`. */
export function editStep({ step, sequence, index, executor }: EditStepProps) {
// https://en.wikipedia.org/wiki/NeXTSTEP
let nextStep = defensiveClone(step);
let nextSeq = defensiveClone(sequence);
// Let the developer safely perform mutations here:
executor(nextStep);
nextSeq.body.body = nextSeq.body.body || [];
nextSeq.body.body[index] = nextStep;
return overwrite(sequence, nextSeq.body);
}
/** Initialize (but don't save) an indexed / tagged resource. */
export function init(resource: TaggedResource): ReduxAction<TaggedResource> {
resource.body.id = 0;
resource.dirty = true;
/** Technically, this happens in the reducer, but I like to be extra safe. */
resource.uuid = generateUuid(resource.body.id, resource.kind);
return { type: "INIT_RESOURCE", payload: resource }
}
export function initSave(resource: TaggedResource) {
return function (dispatch: Function, getState: GetState) {
let action = init(resource);
if (resource.body.id === 0) { delete resource.body.id; }
dispatch(action);
let nextState = getState().resources.index;
let tr = findByUuid(nextState, action.payload.uuid);
return dispatch(save(tr.uuid));
}
}
export function save(uuid: string) {
return function (dispatch: Function, getState: GetState) {
let resource = findByUuid(getState().resources.index, uuid);
dispatch({ type: "SAVE_RESOURCE_START", payload: resource });
return dispatch(update(uuid));
}
}
function update(uuid: string) {
return function (dispatch: Function, getState: GetState) {
return updateViaAjax(getState().resources.index, uuid, dispatch);
}
}
export function destroy(uuid: string) {
return function (dispatch: Function, getState: GetState) {
let resource = findByUuid(getState().resources.index, uuid);
let maybeProceed = confirmationChecker(resource);
return maybeProceed(() => {
if (resource.body.id) {
return Axios
.delete<typeof resource.body>(urlFor(resource.kind) + resource.body.id)
.then(function (resp) {
dispatch(destroyOK(resource));
})
.catch(function (err: UnsafeError) {
dispatch(destroyNO({ err, uuid }));
return Promise.reject(err);
});
} else {
dispatch(destroyOK(resource))
return Promise.resolve("");
}
}) || Promise.reject("User pressed cancel");
}
}
export function saveAll(input: TaggedResource[],
callback: () => void = _.noop,
errBack: (err: UnsafeError) => void = _.noop) {
return function (dispatch: Function, getState: GetState) {
/** Perf issues maybe? RC - Mar 2017 */
let p = input.filter(x => x.dirty).map(tts => dispatch(save(tts.uuid)));
Promise.all(p).then(callback, errBack);
}
}
export function urlFor(tag: ResourceName) {
const OPTIONS: Partial<Record<ResourceName, string>> = {
sequences: API.current.sequencesPath,
tools: API.current.toolsPath,
farm_events: API.current.farmEventsPath,
regimens: API.current.regimensPath,
peripherals: API.current.peripheralsPath,
points: API.current.pointsPath,
users: API.current.usersPath,
device: API.current.devicePath,
images: API.current.imagesPath,
logs: API.current.logsPath
}
let url = OPTIONS[tag];
if (url) {
return url;
} else {
throw new Error(`No resource/URL handler for ${tag} yet.
Consider adding one to crud.ts`);
}
}
/** Shared functionality in create() and update(). */
function updateViaAjax(index: ResourceIndex,
uuid: string,
dispatch: Function) {
let resource = findByUuid(index, uuid);
let { body, kind } = resource;
let verb: "post" | "put";
let url = urlFor(kind);
if (body.id) {
verb = "put";
url += body.id;
} else {
verb = "post";
}
return Axios[verb]<typeof resource.body>(url, body)
.then(function (resp) {
let r1 = defensiveClone(resource);
let r2 = { body: defensiveClone(resp.data) };
let newTR = _.assign({}, r1, r2);
if (isTaggedResource(newTR)) {
dispatch(updateOK(newTR));
} else {
throw new Error("Just saved a malformed TR.");
}
})
.catch(function (err: UnsafeError) {
dispatch(updateNO({ err, uuid }));
return Promise.reject(err);
});
}
let MUST_CONFIRM_LIST: ResourceName[] = ["farm_events", "points"];
let confirmationChecker = (resource: TaggedResource) =>
<T>(proceed: () => T): T | undefined => {
if (MUST_CONFIRM_LIST.includes(resource.kind)) {
if (confirm("Are you sure you want to delete this item?")) {
return proceed();
} else {
return undefined;
}
}
return proceed();
}

View File

@ -0,0 +1 @@
export * from "./api";

View File

@ -0,0 +1,4 @@
export interface EditResourceParams {
uuid: string;
update: object;
}

View File

@ -0,0 +1,89 @@
import * as React from "react";
import { connect } from "react-redux";
import * as _ from "lodash";
import { init, error } from "farmbot-toastr";
import { NavBar } from "./nav";
import { Everything, Log } from "./interfaces";
import { Spinner } from "./spinner";
import { BotState } from "./devices/interfaces";
import { ResourceName, TaggedUser } from "./resources/tagged_resources";
import { selectAllLogs, maybeFetchUser } from "./resources/selectors";
/** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */
let fastClick = require("fastclick");
fastClick.attach(document.body);
/** For the logger module */
init();
/**
* If the sync object takes more than 10s to load, the user will be granted
* access into the app, but still warned.
*/
const TIMEOUT_MESSAGE = `App could not be fully loaded, we recommend you try 
refreshing the page.`;
interface AppProps {
dispatch: Function;
loaded: ResourceName[];
logs: Log[];
user: TaggedUser | undefined;
bot: BotState;
}
function mapStateToProps(props: Everything): AppProps {
  return {
dispatch: props.dispatch,
user: maybeFetchUser(props.resources.index),
bot: props.bot,
logs: _(selectAllLogs(props.resources.index))
.map(x => x.body)
.sortBy("created_at")
.reverse()
.value(),
loaded: props.resources.loaded
};
}
/**
* Relational resources that *must* load before app starts.
* App will crash at load time if they are not pre-loaded.
*/
const MUST_LOAD: ResourceName[] = [
"sequences",
"regimens",
"farm_events",
"points"
];
@connect(mapStateToProps)
export default class App extends React.Component<AppProps, {}> {
get isLoaded() {
return (MUST_LOAD.length ===
_.intersection(this.props.loaded, MUST_LOAD).length);
}
componentDidMount() {
setTimeout(() => {
if (!this.isLoaded) {
this.props.dispatch({ type: "SYNC_TIMEOUT_EXCEEDED" });
error(TIMEOUT_MESSAGE, "Warning");
}
}, 10000);
}
render() {
let syncLoaded = this.isLoaded;
return <div className="app">
<NavBar
user={this.props.user}
bot={this.props.bot}
dispatch={this.props.dispatch}
logs={this.props.logs}
/>
{!syncLoaded && <Spinner radius={33} strokeWidth={6} />}
{syncLoaded && this.props.children}
</div>;
}
}

View File

@ -0,0 +1,136 @@
import * as Axios from "axios";
import { t } from "i18next";
import { error, success } from "farmbot-toastr";
import { connectDevice, fetchReleases } from "../devices/actions";
import { push } from "../history";
import { AuthState } from "./interfaces";
import { ReduxAction, Thunk } from "../redux/interfaces";
import * as Sync from "../sync/actions";
import { API } from "../api";
import { toastErrors } from "../util";
import { Session } from "../session";
import { UnsafeError } from "../interfaces";
import {
responseFulfilled,
responseRejected,
requestFulfilled
} from "../interceptors";
import { Actions } from "../constants";
export function didLogin(authState: AuthState, dispatch: Function) {
API.setBaseUrl(authState.token.unencoded.iss);
dispatch(fetchReleases(authState.token.unencoded.os_update_server));
dispatch(loginOk(authState));
Sync.fetchSyncData(dispatch);
dispatch(connectDevice(authState.token.encoded));
};
// We need to handle OK logins for numerous use cases (Ex: login & registration)
function onLogin(dispatch: Function) {
return (response: Axios.AxiosXHR<AuthState>) => {
let { data } = response;
Session.put(data);
didLogin(data, dispatch);
push("/app/controls");
};
};
export function login(username: string, password: string, url: string): Thunk {
return dispatch => {
return requestToken(username, password, url).then(
onLogin(dispatch),
(err) => dispatch(loginErr())
);
};
}
function loginErr() {
error(t("Login failed."));
return { type: "LOGIN_ERR" };
}
/** Very important. Once called, all outbound HTTP requests will
* have a JSON Web Token attached to their "Authorization" header,
* thereby granting access to the API. */
export function loginOk(auth: AuthState): ReduxAction<AuthState> {
Axios.interceptors.response.use(responseFulfilled, responseRejected);
Axios.interceptors.request.use(requestFulfilled(auth));
return {
type: Actions.LOGIN_OK,
payload: auth
};
}
/** Sign up for the FarmBot service over AJAX. */
export function register(name: string,
email: string,
password: string,
confirmation: string,
url: string): Thunk {
return dispatch => {
let p = requestRegistration(name,
email,
password,
confirmation,
url);
return p.then(onLogin(dispatch),
onRegistrationErr(dispatch));
};
}
/** Handle user registration errors. */
export function onRegistrationErr(dispatch: Function) {
return (err: UnsafeError) => {
toastErrors(err);
dispatch({
type: "REGISTRATION_ERROR",
payload: err
});
};
}
/** Build a JSON object in preparation for an HTTP POST
* to registration endpoint */
function requestRegistration(name: string,
email: string,
password: string,
confirmation: string,
url: string) {
let form = {
user: {
email: email,
password: password,
password_confirmation: confirmation,
name: name
}
};
return Axios.post<AuthState>(API.current.usersPath, form);
}
/** Fetch API token if already registered. */
function requestToken(email: string,
password: string,
url: string) {
let payload = { user: { email: email, password: password } };
// Set the base URL once here.
// It will get set once more when we get the "iss" claim from the JWT.
API.setBaseUrl(url);
return Axios.post<AuthState>(API.current.tokensPath, payload);
}
export function logout() {
// When logging out, we pop up a toast message to confirm logout.
// Sometimes, LOGOUT is dispatched when the user is already logged out.
// In those cases, seeing a logout message may confuse the user.
// To circumvent this, we must check if the user had a token.
// If there was infact a token, we can safely show the message.
if (Session.get()) { success("You have been logged out."); }
Session.clear(true);
// Technically this is unreachable code:
return {
type: "LOGOUT",
payload: {}
};
}

View File

@ -0,0 +1,38 @@
export interface Token {
unencoded: UnencodedToken;
encoded: string;
}
export interface AuthState {
token: Token;
}
export interface UnencodedToken {
// /** SUBJECT - The user's email. STOP USING THIS! */
// sub: string;
/** ISSUED AT */
iat: number;
/** JSON TOKEN IDENTIFIER - a serial number for the token. */
jti: string;
/** ISSUER - Where token came from (API URL). */
iss: string;
/** EXPIRATION DATE */
exp: number;
/** MQTT server address */
mqtt: string;
/** BOT UNIQUE IDENTIFIER */
bot: string;
/** Where to download RPi software */
os_update_server: string;
/** Where to download firmware. */
fw_update_server: string;
}
export interface User {
id: number;
device_id: number;
name: string;
email: string;
created_at: string;
updated_at: string;
}

View File

@ -0,0 +1,8 @@
import { AuthState } from "./interfaces";
import { generateReducer } from "../redux/generate_reducer";
import { Actions } from "../constants";
export let authReducer = generateReducer<AuthState | undefined>(undefined)
.add<AuthState>(Actions.LOGIN_OK, (s, { payload }) => {
return payload;
});

View File

@ -0,0 +1,17 @@
jest.unmock("../../auth/actions");
const actions = require("../../auth/actions");
let didLogin = jest.fn();
jest.mock("../../session", () => ({ Session: { get: () => false } }));
actions.didLogin = didLogin;
import { ready } from "../actions";
const STUB_STATE = { auth: "FOO BAR BAZ" };
describe("Actions", () => {
it("fetches configs and calls didLogin()", () => {
let dispatch = jest.fn();
let getState = jest.fn(() => STUB_STATE);
let thunk = ready();
thunk(dispatch, getState);
expect(didLogin.mock.calls.length).toBe(1);
});
});

View File

@ -0,0 +1,11 @@
import { didLogin } from "../auth/actions";
import { Thunk } from "../redux/interfaces";
import { Session } from "../session";
/** Lets Redux know that the app is ready to bootstrap. */
export function ready(): Thunk {
return (dispatch, getState) => {
let state = Session.get() || getState().auth;
if (state) { didLogin(state, dispatch); };
};
}

View File

@ -0,0 +1,11 @@
/** Payload of CHANGE_API_HOST */
export interface ChangeApiHost { host: string; };
/** Payload of CHANGE_API_PORT */
export interface ChangeApiPort { port: string; };
/** This is a subset of attributes found on window.location. */
export interface ConfigState {
host: string;
port: string;
}

View File

@ -0,0 +1,22 @@
import { generateReducer } from "../redux/generate_reducer";
import { ChangeApiHost, ChangeApiPort, ConfigState } from "./interfaces";
import { API } from "../api";
import { Actions } from "../constants";
let initialState: ConfigState = {
host: location.hostname,
// It gets annoying to manually change the port # in dev mode.
// I automatically point to port 3000 on local.
port: API.inferPort()
};
export let configReducer = generateReducer<ConfigState>(initialState)
.add<ChangeApiPort>(Actions.CHANGE_API_PORT, (s, { payload }) => {
s.port = payload.port.replace(/\D/g, "");
return s;
})
.add<ChangeApiHost>(Actions.CHANGE_API_HOST, (s, { payload }) => {
s.host = payload.host;
return s;
});

View File

@ -0,0 +1,321 @@
/**
* Seems like a better idea to keep content and tooltips centralized. If we have
* the ability to keep the app safer from possible accidental breakages by
* avoiding going into components for copy changes, why not right? ¯\_()_/¯
*/
export namespace ToolTips {
// Controls
export const MOVE =
`Use these manual control buttons to move FarmBot in realtime. Press the
arrows for relative movements or type in new coordinates and press GO for an
absolute movement. Tip: Press the Home button when you are done so FarmBot
is ready to get back to work.`
export const WEBCAM_SAVE =
`Press the edit button to update and save your webcam URL.`
export const PERIPHERALS =
`Use these toggle switches to control FarmBot's peripherals in realtime. To
edit and create new peripherals, press the EDIT button. Make sure to turn
things off when you're done!`
// Device
export const OS_SETTINGS =
`View and change device settings.`
export const HW_SETTINGS =
`Change settings of your FarmBot hardware with the fields below. Caution:
Changing these settings to extreme values can cause hardware malfunction.
Make sure to test any new settings before letting your FarmBot use them
unsupervised. Tip: Recalibrate FarmBot after changing settings and test a
few sequences to verify that everything works as expected. Note: Currently
not all settings can be changed.`
// Hardware Settings: Homing and Calibration
export const HOMING =
`(Alpha) If encoders or end-stops are enabled, home axis (find zero).`
export const CALIBRATION =
`(Alpha) If encoders or end-stops are enabled, home axis and determine
maximum.`
export const SET_ZERO_POSITION =
`Set the current location as zero.`
export const FIND_HOME_ON_BOOT =
`If encoders or end-stops are enabled, find the home position when the
device powers on.`
export const STOP_AT_HOME =
`Stop at the home location of the axis.`
export const STOP_AT_MAX =
`Don't allow movement past the maximum value provided in AXIS LENGTH.`
export const NEGATIVE_COORDINATES_ONLY =
`Restrict travel to negative coordinate locations. Overridden by disabling
STOP AT HOME.`
export const LENGTH =
`Set the length of each axis to provide software limits. Used only if
STOP AT MAX is enabled.`
export const TIMEOUT_AFTER =
`Amount of time to wait for a command to execute before stopping.`
// Hardware Settings: Motors
export const MAX_MOVEMENT_RETRIES =
`Number of times to retry a movement before stopping.`
export const MAX_SPEED =
`Maximum travel speed after acceleration in motor steps per second.`
export const MIN_SPEED =
`Minimum movement speed. Also used for homing, calibration, and movements
across home.`
export const ACCELERATE_FOR =
`Number of steps used for acceleration and deceleration.`
export const STEPS_PER_MM =
`The number of motor steps required to move the axis one millimeter.`
export const ALWAYS_POWER_MOTORS =
`Keep power applied to motors. Prevents slipping from gravity in certain
situations.`
export const INVERT_MOTORS =
`Invert direction of motor during calibration.`
export const ENABLE_X2_MOTOR =
`Enable use of a second x-axis motor. Connects to E0 on RAMPS.`
// Hardware Settings: Encoders and Endstops
export const ENABLE_ENCODERS =
`(Alpha) Enable use of rotary encoders during calibration and homing.`
export const ENCODER_POSITIONING =
`[EXPERIMENTAL] Use encoders for positioning.`
export const INVERT_ENCODERS =
`(Alpha) Reverse the direction of encoder position reading.`
export const MAX_MISSED_STEPS =
`(Alpha) Number of steps missed (determined by encoder) before motor is
considered to have stalled.`
export const ENCODER_MISSED_STEP_DECAY =
`(Alpha) Reduction to missed step total for every good step.`
export const ENCODER_SCALING =
`(Alpha) encoder scaling factor = 100 * (motor resolution * microsteps) /
(encoder resolution)`
export const ENABLE_ENDSTOPS =
`Enable use of electronic end-stops during calibration and homing.`
export const INVERT_ENDPOINTS =
`Swap axis end-stops during calibration.`
// Farmware
export const FARMWARE =
`Manage Farmware (plugins).`
export const PHOTOS =
`Take and view photos with your FarmBot's camera.`
export const WEED_DETECTOR =
`Detect weeds using FarmBot's camera and display them on the Farm Designer
map.`
export const CAMERA_CALIBRATION =
`Calibrate FarmBot's camera for use in the weed detection software.`
// Sequences
export const SEQUENCE_COMMANDS =
`These are the most basic commands FarmBot can execute. Drag and drop them
to create sequences for watering, planting seeds, measuring soil properties,
and more.`
export const SEQUENCE_EDITOR =
`Drag and drop commands here to create sequences for watering, planting
seeds, measuring soil properties, and more. Press the Test button to
immediately try your sequence with FarmBot. You can also edit, copy, and
delete existing sequences; assign a color; and give your commands custom
names.`
export const SEQUENCE_LIST =
`Here is the list of all of your sequences. Click one to edit.`
export const MOVE_ABSOLUTE =
`The Move Absolute step instructs FarmBot to move to the specified
coordinate regardless of the current position. For example, if FarmBot is
currently at X=1000, Y=1000 and it receives a Move Absolute where X=0 and
Y=3000, then FarmBot will move to X=0, Y=3000. If FarmBot must move in
multiple directions, it will move diagonally. If you require straight
movements along one axis at a time, use multiple Move Absolute steps.
Offsets allow you to more easily instruct FarmBot to move to a location,
but offset from it by the specified amount. For example moving to just
above where a peripheral is located. Using offsets lets FarmBot do the
math for you.`
export const MOVE_RELATIVE =
`The Move Relative step instructs FarmBot to move the specified distance
from its current location. For example, if FarmBot is currently at X=1000,
Y=1000 and it receives a Move Relative where X=0 and Y=3000, then FarmBot
will move to X=1000, Y=4000. If FarmBot must move in multiple directions,
it will move diagonally. If you require straight movements along one axis
at a time, use multiple Move Relative steps. Move Relative steps should be
preceded by a Move Absolute step to ensure you are starting from a known
location.`
export const WRITE_PIN =
`The Write Pin step instructs FarmBot to set the specified pin on the
Arduino to the specified mode and value. A Pin Mode of 0 is for on/off
control, while a Pin Mode of 1 is for PWM (pulse width modulation) (0-255).`
export const READ_PIN =
`The Read Pin step instructs FarmBot to read the current value of the
specified pin. A Pin Mode of 0 is for digital (on/off), while a Pin Mode
of 1 is for analog (0-1023 for 0-5V).`
export const WAIT =
`The Wait step instructs FarmBot to wait for the specified amount of time.
Use it in combination with the Pin Write step to water for a length of
time.`
export const SEND_MESSAGE =
`The Send Message step instructs FarmBot to send a custom message to the
logs (and toast message and/or email, if selected). This can help you with
debugging your sequences.`
export const FIND_HOME =
`The Find Home step instructs the device to perform a homing command to
find and set zero for the chosen axis or axes.`
export const IF =
`Execute a sequence if a condition is satisfied. If the condition is not
satisfied, chose to do nothing or execute a different sequence.`
export const EXECUTE_SCRIPT =
`The Run Farmware step runs a Farmware package. The weed detection script
is the only script supported at the moment, but user definable script
support is coming soon!`
export const TAKE_PHOTO =
`Snaps a photo using the device camera. Select the camera type on the
Device page.`
// Regimens
export const BULK_SCHEDULER =
`Add sequences to your regimen by selecting a sequence from the drop down,
specifying a time, choosing which days it should run on, and then clicking
the + button. For example: a Seeding sequence might be scheduled for Day 1,
while a Watering sequence would be scheduled to run every other day.`
export const REGIMEN_EDITOR =
`Regimens allow FarmBot to take care of a plant throughout its entire life.
A regimen consists of many sequences that are scheduled to run based on the
age of the plant. Regimens are applied to plants from the farm designer
(coming soon) and can be re-used on many plants growing at the same or
different times. Multiple regimens can be applied to any one plant.`
export const REGIMEN_LIST =
`This is a list of all of your regimens. Click one to begin editing it.`
// Tools
export const TOOL_LIST =
`This is a list of all your FarmBot Tools. Click the Edit button to add,
edit, or delete tools.`
export const TOOLBAY_LIST =
`Toolbays are where you store your FarmBot Tools. Each Toolbay has Slots
that you can put your Tools in, which should be reflective of your real
FarmBot hardware configuration.`
}
export namespace Content {
// Account
export const ACCOUNT_DELETE_WARNING =
`WARNING! Deleting your account will permanently delete all of your
Sequences , Regimens, Events, and Farm Designer data.Upon deleting your
account, FarmBot will cease to function and become inaccessible until it is
paired with another web app account. To do this, you will need to reboot
your FarmBot so that is goes back into configuration mode for pairing with
another user account. When this happens, all of the data on your FarmBot
will be overwritten with the new account's data. If the account is brand
new, then FarmBot will become a blank slate.`
// Controls
export const FACTORY_RESET_WARNING =
`Factory resetting your FarmBot will destroy all data on the device,
revoking your FarmBot's abilily to connect to your web app account and your
home wifi. Upon factory resetting, your device will restart into
Configurator mode. Factory resetting your FarmBot will not affect any data
or settings from your web app account, allowing you to do a complete restore
to your device once it is back online and paired with your web app account.`
}
export namespace Actions {
// Resources
export const DESTROY_RESOURCE_OK = `DESTROY_RESOURCE_OK`
export const INIT_RESOURCE = `INIT_RESOURCE`
export const SAVE_SPECIAL_RESOURCE = `SAVE_SPECIAL_RESOURCE`
export const SAVE_RESOURCE_OK = `SAVE_RESOURCE_OK`
export const UPDATE_RESOURCE_OK = `UPDATE_RESOURCE_OK`
export const EDIT_RESOURCE = `EDIT_RESOURCE`
export const OVERWRITE_RESOURCE = `OVERWRITE_RESOURCE`
export const SAVE_RESOURCE_START = `SAVE_RESOURCE_START`
export const RESOURCE_READY = `RESOURCE_READY`
// Auth
export const LOGIN_OK = `LOGIN_OK`
// Config
export const CHANGE_API_PORT = `CHANGE_API_PORT`
export const CHANGE_API_HOST = `CHANGE_API_HOST`
// Devices
export const TOGGLE_CONTROL_PANEL_OPTION = `TOGGLE_CONTROL_PANEL_OPTION`
export const CHANGE_STEP_SIZE = `CHANGE_STEP_SIZE`
export const SETTING_UPDATE_START = `SETTING_UPDATE_START`
export const SETTING_UPDATE_END = `SETTING_UPDATE_END`
export const BOT_CHANGE = `BOT_CHANGE`
export const FETCH_OS_UPDATE_INFO_OK = `FETCH_OS_UPDATE_INFO_OK`
export const FETCH_FW_UPDATE_INFO_OK = `FETCH_FW_UPDATE_INFO_OK`
export const SET_SYNC_STATUS = `SET_SYNC_STATUS`
export const INVERT_JOG_BUTTON = `INVERT_JOG_BUTTON`
// Draggable
export const PUT_DATA_XFER = `PUT_DATA_XFER`
export const DROP_DATA_XFER = `DROP_DATA_XFER`
// Designer
export const SEARCH_QUERY_CHANGE = `SEARCH_QUERY_CHANGE`
export const SELECT_PLANT = `SELECT_PLANT`
export const TOGGLE_HOVERED_PLANT = `TOGGLE_HOVERED_PLANT`
export const UPDATE_BOT_ORIGIN_QUADRANT = `UPDATE_BOT_ORIGIN_QUADRANT`
export const UPDATE_MAP_ZOOM_LEVEL = `UPDATE_MAP_ZOOM_LEVEL`
export const OF_SEARCH_RESULTS_OK = `OF_SEARCH_RESULTS_OK`
// Regimens
export const PUSH_WEEK = `PUSH_WEEK`
export const POP_WEEK = `POP_WEEK`
export const TOGGLE_DAY = `TOGGLE_DAY`
export const SELECT_REGIMEN = `SELECT_REGIMEN`
export const SET_SEQUENCE = `SET_SEQUENCE`
export const SET_TIME_OFFSET = `SET_TIME_OFFSET`
// Sequences
export const SELECT_SEQUENCE = `SELECT_SEQUENCE`
// Farmware
export const SELECT_IMAGE = `SELECT_IMAGE`
}

View File

@ -0,0 +1,67 @@
import * as React from "react";
import { isNaN } from "lodash";
import { AxisInputBoxProps, AxisInputBoxState } from "./interfaces";
export class AxisInputBox
extends React.Component<AxisInputBoxProps, AxisInputBoxState> {
constructor() {
super();
this.state = { value: undefined };
}
whatToDisplay() {
if (this.state.value === undefined) {
return this.props.value;
} else {
return this.state.value;
}
}
style() {
let border = "1px solid red";
return (this.state.value === undefined) ? {} : { border };
}
componentWillReceiveProps(nextProps: AxisInputBoxProps) {
if (this.props.value !== nextProps.value) {
this.reset();
}
}
blur = (e: React.FormEvent<HTMLInputElement>) => {
switch (this.state.value) {
case undefined:
return;
case "":
return this.reset();
default:
let num = parseFloat(this.state.value);
if (isNaN(num)) {
return this.reset();
} else {
return this.props.onChange(this.props.axis, num);
}
}
}
reset() {
this.setState({ value: undefined });
this.props.onChange(this.props.axis, undefined);
}
change = (e: React.FormEvent<HTMLInputElement>) => {
this.setState({ value: e.currentTarget.value });
}
render() {
return <div className="col-xs-3">
<label>{this.props.label}</label>
<input className="move-input"
type="text"
style={this.style()}
onBlur={this.blur}
onChange={this.change}
value={this.whatToDisplay()} />
</div>;
}
}

View File

@ -0,0 +1,66 @@
import * as React from "react";
import { AxisInputBox } from "./axis_input_box";
import { t } from "i18next";
import { Row } from "../ui";
import {
AxisInputBoxGroupProps,
AxisInputBoxGroupState,
Vector
} from "./interfaces";
export class AxisInputBoxGroup extends React.Component<AxisInputBoxGroupProps,
Partial<AxisInputBoxGroupState>> {
constructor() {
super();
this.state = {};
}
change = (axis: keyof Vector, val: number) => {
this.setState({ [axis]: val });
}
get vector() {
let { x, y, z } = this.state;
let [x2, y2, z2] = this.props.bot.hardware.location;
return {
x: _.isNumber(x) ? x : x2,
y: _.isNumber(y) ? y : y2,
z: _.isNumber(z) ? z : z2
};
}
clicked = () => {
this.props.onCommit(this.vector);
this.setState({ x: undefined, y: undefined, z: undefined });
}
render() {
let [x, y, z] = this.props.bot.hardware.location;
return <Row>
<AxisInputBox
axis="x"
label="X AXIS"
value={x}
onChange={this.change} />
<AxisInputBox
axis="y"
label="Y AXIS"
value={y}
onChange={this.change} />
<AxisInputBox
axis="z"
label="Z AXIS"
value={z}
onChange={this.change} />
<div className="col-xs-3">
<button
onClick={this.clicked}
className="full-width green go fb-button"
>
{t("GO")}
</button>
</div>
</Row>;
}
}

View File

@ -0,0 +1,118 @@
import * as React from "react";
import { connect } from "react-redux";
import { t } from "i18next";
import { changeStepSize, moveAbs } from "../devices/actions";
import { Peripherals } from "./peripherals";
import { EStopButton } from "../devices/components/e_stop_btn";
import { JogButtons } from "./jog_buttons";
import { AxisInputBoxGroup } from "./axis_input_box_group";
import { Row, Page, Col, Widget, WidgetBody, WidgetHeader } from "../ui";
import { mapStateToProps } from "./state_to_props";
import { StepSizeSelector } from "./step_size_selector";
import { MustBeOnline } from "../devices/must_be_online";
import { ToolTips } from "../constants";
import { WebcamPanel } from "./webcam_panel";
import { Props } from "./interfaces";
import { Xyz } from "../devices/interfaces";
import { Popover, Position } from "@blueprintjs/core";
@connect(mapStateToProps)
export class Controls extends React.Component<Props, {}> {
toggle = (name: Xyz) => () =>
this.props.dispatch({ type: "INVERT_JOG_BUTTON", payload: name });
render() {
let { sync_status } = this.props.bot.hardware.informational_settings;
let { x_axis_inverted, y_axis_inverted, z_axis_inverted } = this.props.bot;
let xBtnColor = x_axis_inverted ? "green" : "red";
let yBtnColor = y_axis_inverted ? "green" : "red";
let zBtnColor = z_axis_inverted ? "green" : "red";
return <Page className="controls">
<Row>
<Col xs={12} sm={6} md={4} mdOffset={1}>
<Widget>
<WidgetHeader
title="Move"
helpText={ToolTips.MOVE}>
<Popover position={Position.BOTTOM}>
<i className="fa fa-gear" />
<div>
<label>
{t("Invert Jog Buttons")}
</label>
<fieldset>
<label>
{t("X Axis")}
</label>
<button
className={"fb-button fb-toggle-button " + xBtnColor}
onClick={this.toggle("x")}
/>
</fieldset>
<fieldset>
<label>
{t("Y Axis")}
</label>
<button
className={"fb-button fb-toggle-button " + yBtnColor}
onClick={this.toggle("y")}
/>
</fieldset>
<fieldset>
<label>
{t("Z Axis")}
</label>
<button
className={"fb-button fb-toggle-button " + zBtnColor}
onClick={this.toggle("z")}
/>
</fieldset>
</div>
</Popover>
<EStopButton
bot={this.props.bot}
user={this.props.user}
/>
</WidgetHeader>
<WidgetBody>
<MustBeOnline
fallback="Bot is offline."
lockOpen={process.env.NODE_ENV !== "production"}
status={sync_status}>
<label className="text-center">
{t("MOVE AMOUNT (mm)")}
</label>
<StepSizeSelector
choices={[1, 10, 100, 1000, 10000]}
selector={num => this.props.dispatch(changeStepSize(num))}
selected={this.props.bot.stepSize}
/>
<JogButtons
bot={this.props.bot}
x_axis_inverted={x_axis_inverted}
y_axis_inverted={y_axis_inverted}
z_axis_inverted={z_axis_inverted}
/>
<AxisInputBoxGroup
bot={this.props.bot}
onCommit={input => moveAbs(input)}
/>
</MustBeOnline>
</WidgetBody>
</Widget>
<Peripherals
bot={this.props.bot}
peripherals={this.props.peripherals}
dispatch={this.props.dispatch}
resources={this.props.resources}
/>
</Col>
<Col xs={12} sm={6}>
<WebcamPanel {...this.props} />
</Col>
</Row>
</Page>;
}
};

View File

@ -0,0 +1,28 @@
import * as React from "react";
import { Farmbot } from "farmbot";
import { moveRelative } from "../devices/actions";
import { DirectionButtonProps, Payl } from "./interfaces";
export class DirectionButton extends React.Component<DirectionButtonProps, {}> {
sendCommand = () => {
let { direction, isInverted } = this.props;
let isNegative = (direction === "up") || (direction === "right");
let inverter = isInverted ? -1 : 1;
let multiplier = isNegative ? -1 : 1;
let distance = (this.props.steps || 250) * multiplier * inverter;
let payload: Payl = { speed: Farmbot.defaults.speed, x: 0, y: 0, z: 0 };
payload[this.props.axis] = distance;
moveRelative(payload);
}
render() {
let { direction, axis } = this.props;
let klass = `fb-button fa fa-2x arrow-button radius fa-arrow-${direction}`;
let title = `move ${axis} axis`;
return <button
onClick={this.sendCommand}
className={klass}
title={title}
/>
}
}

View File

@ -0,0 +1,78 @@
import { BotState, Xyz } from "../devices/interfaces";
import { Vector3 } from "farmbot/dist";
import { TaggedPeripheral, TaggedDevice } from "../resources/tagged_resources";
import { RestResources } from "../resources/interfaces";
import { TaggedUser } from "../resources/tagged_resources";
export interface Props {
dispatch: Function;
bot: BotState;
account: TaggedDevice;
user: TaggedUser | undefined;
peripherals: TaggedPeripheral[];
resources: RestResources;
}
export interface WebcamPanelState {
isEditing: boolean;
}
export interface DirectionButtonProps {
axis: Xyz;
direction: "up" | "down" | "left" | "right";
isInverted: boolean;
steps: number;
}
export interface Payl {
speed: number;
x: number;
y: number;
z: number;
}
export type Vector = Vector3;
export interface AxisInputBoxGroupProps {
onCommit: (v: Vector) => void;
bot: BotState;
}
export interface AxisInputBoxGroupState {
x?: number | undefined;
y?: number | undefined;
z?: number | undefined;
}
export interface AxisInputBoxProps {
axis: Xyz;
label: string;
value: number | undefined;
onChange: (key: string, val: number | undefined) => void;
}
export interface AxisInputBoxState {
value: string | undefined;
}
export interface StepSizeSelectorProps {
choices: number[];
selected: number;
selector: (num: number) => void;
}
export interface JogMovementControlsProps {
x_axis_inverted: boolean;
y_axis_inverted: boolean;
z_axis_inverted: boolean;
bot: BotState;
}
export interface ToggleButtonProps {
/** Function that is executed when the toggle button is clicked */
toggleAction: () => void;
toggleval: number | string | undefined;
disabled?: boolean | undefined;
}

View File

@ -0,0 +1,81 @@
import * as React from "react";
import { DirectionButton } from "./direction_button";
import { homeAll } from "../devices/actions";
import { JogMovementControlsProps } from "./interfaces";
export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
render() {
return <table className="jog-table" style={{ border: 0 }}>
<tbody>
<tr>
<td />
<td />
<td />
<td>
<DirectionButton
axis="y"
direction="up"
isInverted={this.props.y_axis_inverted}
steps={this.props.bot.stepSize || 1000}
/>
</td>
<td />
<td />
<td>
<DirectionButton
axis="z"
direction="up"
isInverted={this.props.z_axis_inverted}
steps={this.props.bot.stepSize || 1000}
/>
</td>
</tr>
<tr>
<td>
<button
className="i fa fa-home arrow-button fb-button"
onClick={() => homeAll(100)}
/>
</td>
<td />
<td>
<DirectionButton
axis="x"
direction="left"
isInverted={this.props.x_axis_inverted}
steps={this.props.bot.stepSize || 1000}
/>
</td>
<td>
<DirectionButton
axis="y"
direction="down"
isInverted={this.props.y_axis_inverted}
steps={this.props.bot.stepSize || 1000}
/>
</td>
<td>
<DirectionButton
axis="x"
direction="right"
isInverted={this.props.x_axis_inverted}
steps={this.props.bot.stepSize || 1000}
/>
</td>
<td />
<td>
<DirectionButton
axis="z"
direction="down"
isInverted={this.props.z_axis_inverted}
steps={this.props.bot.stepSize || 1000}
/>
</td>
</tr>
<tr>
<td />
</tr>
</tbody>
</table>;
}
}

View File

@ -0,0 +1,52 @@
import * as React from "react";
import { render } from "enzyme";
import { PeripheralList } from "../peripheral_list";
import { TaggedPeripheral } from "../../../resources/tagged_resources";
import { Pins } from "farmbot/dist";
describe("<PeripheralList/>", function () {
const peripherals: TaggedPeripheral[] = [
{
uuid: "peripherals.2.2",
kind: "peripherals",
body: {
id: 2,
pin: 13,
label: "GPIO 13 - LED"
}
},
{
uuid: "peripherals.1.1",
kind: "peripherals",
body: {
id: 1,
pin: 2,
label: "GPIO 2"
}
},
]
const pins: Pins = {
13: {
mode: 0,
value: 1
},
2: {
mode: 0,
value: 1
}
}
it("renders a list of peripherals, in sorted order", function () {
pending("Stopping here for now. Still needs finishing.");
let node = <PeripheralList dispatch={() => { }}
peripherals={peripherals}
pins={pins} />
let labels = render(node).find("label");
let first = labels.first();
expect(first.text()).toBeTruthy();
expect(first.text()).toContain("GPIO 2")
let last = labels.last();
expect(last.text()).toBeTruthy();
expect(last.text()).toContain("GPIO 2")
});
});

View File

@ -0,0 +1,106 @@
import * as React from "react";
import { t } from "i18next";
import { error } from "farmbot-toastr";
import { PeripheralList } from "./peripheral_list";
import { PeripheralForm } from "./peripheral_form";
import { Widget, WidgetBody, WidgetHeader, SaveBtn } from "../../ui";
import { PeripheralsProps } from "../../devices/interfaces";
import { PeripheralState } from "./interfaces";
import { TaggedPeripheral } from "../../resources/tagged_resources";
import { saveAll, init } from "../../api/crud";
import { ToolTips } from "../../constants";
export class Peripherals extends React.Component<PeripheralsProps, PeripheralState> {
constructor() {
super();
this.state = { isEditing: false };
}
toggle = () => {
this.setState({ isEditing: !this.state.isEditing });
}
maybeSave = () => {
let { peripherals } = this.props;
let pinNums = peripherals.map(x => x.body.pin);
let positivePins = pinNums.filter(x => x && x > 0);
let smallPins = pinNums.filter(x => x && x < 1000);
// I hate adding client side validation, but this is a wonky endpoint - RC.
let allAreUniq = _.uniq(pinNums).length === pinNums.length;
let allArePositive = positivePins.length === pinNums.length;
let allAreSmall = smallPins.length === pinNums.length;
if (allAreUniq && allArePositive) {
if (allAreSmall) {
this.props.dispatch(saveAll(this.props.peripherals, this.toggle));
} else {
error("Pin numbers must be less than 1000.");
}
} else {
error("Pin numbers are required and must be positive and unique.");
}
}
showPins = () => {
let { peripherals, dispatch, bot } = this.props;
let pins = bot.hardware.pins;
if (this.state.isEditing) {
return <PeripheralForm peripherals={peripherals}
dispatch={dispatch} />
} else {
return <PeripheralList peripherals={peripherals}
dispatch={dispatch}
pins={pins} />
}
}
emptyPeripheral = (): TaggedPeripheral => {
return {
uuid: "WILL_BE_CHANGED_BY_REDUCER",
kind: "peripherals",
body: { pin: 0, label: "New Peripheral" }
}
}
render() {
let { dispatch, peripherals } = this.props;
let { isEditing } = this.state;
let isSaving = peripherals && peripherals
.filter(x => x.saving).length !== 0;
let isDirty = peripherals && peripherals
.filter(x => x.dirty).length !== 0;
let isSaved = !isSaving && !isDirty;
return <Widget className="peripherals-widget">
<WidgetHeader title={"Peripherals"} helpText={ToolTips.PERIPHERALS}>
<button
className="fb-button gray"
onClick={this.toggle}
hidden={!isSaved}>
{!isEditing && t("Edit")}
{isEditing && t("Back")}
</button>
<SaveBtn
hidden={!isEditing}
isDirty={isDirty}
isSaving={isSaving}
isSaved={isSaved}
onClick={this.maybeSave}
/>
<button
hidden={!isEditing}
className="fb-button green"
type="button"
onClick={() => { dispatch(init(this.emptyPeripheral())) }}>
<i className="fa fa-plus" />
</button>
</WidgetHeader>
<WidgetBody>
{this.showPins()}
</WidgetBody>
</Widget>;
};
}

View File

@ -0,0 +1,23 @@
import { TaggedPeripheral } from "../../resources/tagged_resources";
import { Pins } from "farmbot/dist";
export interface PeripheralState {
isEditing: boolean;
}
export interface Peripheral {
id?: number;
pin: number | undefined;
label: string;
}
export interface PeripheralFormProps {
dispatch: Function;
peripherals: TaggedPeripheral[];
}
export interface PeripheralListProps {
dispatch: Function;
peripherals: TaggedPeripheral[];
pins: Pins;
}

View File

@ -0,0 +1,44 @@
import * as React from "react";
import { Row, Col } from "../../ui/index";
import { destroy, edit } from "../../api/crud";
import { PeripheralFormProps } from "./interfaces";
import { sortResourcesById } from "../../util";
export function PeripheralForm(props: PeripheralFormProps) {
let { dispatch, peripherals } = props;
return <div>
{sortResourcesById(peripherals).map(p => {
return <Row key={p.uuid}>
<Col xs={6}>
<input type="text"
placeholder="Label"
value={p.body.label}
onChange={(e) => {
let { value } = e.currentTarget;
dispatch(edit(p, { label: value }));
}}
/>
</Col>
<Col xs={4}>
<input type="number"
value={(p.body.pin || "").toString()}
placeholder="Pin #"
onChange={(e) => {
let { value } = e.currentTarget;
let update: Partial<typeof p.body> = { pin: parseInt(value, 10) };
dispatch(edit(p, update));
}} />
</Col>
<Col xs={2}>
<button
className="red fb-button"
onClick={() => { dispatch(destroy(p.uuid)); }}
>
<i className="fa fa-minus" />
</button>
</Col>
</Row>
})}
</div>
};

View File

@ -0,0 +1,28 @@
import * as React from "react";
import { ToggleButton } from "../toggle_button";
import { pinToggle } from "../../devices/actions";
import { Row, Col } from "../../ui";
import { PeripheralListProps } from "./interfaces";
import { sortResourcesById } from "../../util";
export function PeripheralList(props: PeripheralListProps) {
let { pins } = props;
return <div>
{sortResourcesById(props.peripherals).map(p => {
let value = (pins[p.body.pin || -1] || { value: undefined }).value;
return <Row key={p.uuid}>
<Col xs={4}>
<label>{p.body.label}</label>
</Col>
<Col xs={4}>
<p>{p.body.pin}</p>
</Col>
<Col xs={4}>
<ToggleButton
toggleval={value}
toggleAction={() => p.body.pin && pinToggle(p.body.pin)} />
</Col>
</Row>
})}
</div>
};

View File

@ -0,0 +1,27 @@
import * as React from "react";
import { FallbackImg } from "../ui/fallback_img";
import { PLACEHOLDER_FARMBOT } from "../images/index";
import { t } from "i18next";
export const showUrl = (url: string, dirty: boolean) => {
if (dirty) {
return <p>{t("Press save to view.")}</p>;
} else {
if (url.includes(PLACEHOLDER_FARMBOT)) {
return <div className="webcam-stream-unavailable">
<FallbackImg className="webcam-stream"
src={url}
fallback={PLACEHOLDER_FARMBOT} />
<text>
{t("Camera stream not available.")}
<br />
{t("Press ")}<b>{t("EDIT")}</b>{t(" to add a stream.")}
</text>
</div>;
} else {
return <FallbackImg className="webcam-stream"
src={url}
fallback={PLACEHOLDER_FARMBOT} />;
};
};
};

View File

@ -0,0 +1,22 @@
import { Everything } from "../interfaces";
import {
selectAllPeripherals,
getDeviceAccountSettings
} from "../resources/selectors";
import { Props } from "./interfaces";
import { maybeFetchUser } from "../resources/selectors";
export function mapStateToProps(props: Everything): Props {
let peripherals = _.uniq(selectAllPeripherals(props.resources.index));
let resources = props.resources;
return {
account: getDeviceAccountSettings(resources.index),
dispatch: props.dispatch,
bot: props.bot,
user: maybeFetchUser(props.resources.index),
resources,
peripherals
};
}

View File

@ -0,0 +1,35 @@
import * as React from "react";
import { Component } from "react";
import { StepSizeSelectorProps } from "./interfaces";
export class StepSizeSelector extends Component<StepSizeSelectorProps, {}> {
cssForIndex(num: number) {
let choices = this.props.choices;
let css = "move-amount no-radius fb-button ";
if (num === _.first(choices)) {
css += "leftmost ";
}
if (num === _.last(choices)) {
css += "rightmost ";
}
if (num === this.props.selected) {
css += "move-amount-selected ";
}
return css;
}
render() {
return <div className="move-amount-wrapper">
{
this.props.choices.map(
(item: number, inx: number) => <button
className={this.cssForIndex(item)}
onClick={() => this.props.selector(item)}
key={inx}>
{item}
</button>
)
}
</div>;
}
}

View File

@ -0,0 +1,50 @@
import * as React from "react";
import * as i18next from "i18next";
import { ToggleButtonProps } from "./interfaces";
export class ToggleButton extends React.Component<ToggleButtonProps, {}> {
caption() {
let captions: { [s: string]: string | undefined } = {
"0": i18next.t("no"),
"false": i18next.t("no"),
"off": i18next.t("no"),
"1": i18next.t("yes"),
"true": i18next.t("yes"),
"on": i18next.t("yes"),
"undefined": "🚫",
"-1": "🚫"
};
let togval = String(this.props.toggleval);
return captions[togval] || "---";
}
css() {
let css = "fb-toggle-button fb-button"
if (this.props.disabled) { return css + " gray"; }
let redCSS = css + " red";
let greenCSS = css + " green";
let yellowCSS = css + " yellow";
let cssClasses: { [s: string]: string | undefined } = {
"0": redCSS,
"false": redCSS,
"off": redCSS,
"1": greenCSS,
"true": greenCSS,
"on": greenCSS,
"undefined": yellowCSS
};
return cssClasses[String(this.props.toggleval)] || yellowCSS;
}
render() {
let cb = () => !this.props.disabled && this.props.toggleAction();
return <button
disabled={!!this.props.disabled}
className={this.css()}
onClick={cb}>
{this.caption()}
</button>;
}
}

View File

@ -0,0 +1,84 @@
import * as React from "react";
import { t } from "i18next";
import { Widget, WidgetHeader } from "../ui";
import { WebcamPanelState, Props } from "./interfaces";
import { PLACEHOLDER_FARMBOT } from "../images/index";
import { showUrl } from "./show_url";
import { ToolTips } from "../constants";
import { overwrite, edit, save } from "../api/crud";
import { API } from "../api/api";
import { DeviceAccountSettings } from "../devices/interfaces";
import { createOK } from "../resources/actions";
import * as axios from "axios";
export class WebcamPanel
extends React.Component<Props, Partial<WebcamPanelState>> {
state: WebcamPanelState = { isEditing: false };
toggle = () => { this.setState({ isEditing: !this.state.isEditing }); }
save = () => {
this.props.dispatch(save(this.props.account.uuid));
this.toggle();
};
edit = (update: Partial<DeviceAccountSettings>) => {
this.props.dispatch(edit(this.props.account, update));
};
clearURL = () => {
axios
.get<DeviceAccountSettings>(API.current.devicePath)
.then(resp => {
// TODO: We're starting to hit use cases where we need edit/undo.
// Revisit this one when undo/redo is implemented.
this.props.dispatch(overwrite(this.props.account, resp.data));
this.props.dispatch(createOK(this.props.account));
});
}
render() {
let url = this.props.account.body.webcam_url || PLACEHOLDER_FARMBOT;
let dirty = !!this.props.bot.dirty;
let { isEditing } = this.state;
return <Widget>
<WidgetHeader title="Camera" helpText={ToolTips.WEBCAM_SAVE}>
{isEditing ?
<button
className="fb-button green"
onClick={this.save}
>
{t("Save")}{this.props.account.dirty ? "*" : ""}
</button>
:
<button
className="fb-button gray"
onClick={this.toggle}
>
{t("Edit")}
</button>
}
</WidgetHeader>
{isEditing &&
<div>
<label>{t("Set Webcam URL:")}</label>
<button
className="fb-button clear-webcam-url-btn"
onClick={this.clearURL}
>
<i className="fa fa-times"></i>
</button>
<input
type="text"
onChange={e => this.edit({ webcam_url: e.currentTarget.value })}
placeholder="https://"
value={this.props.account.body.webcam_url}
className="webcam-url-input" />
</div>
}
{showUrl(url, dirty)}
</Widget>
}
}

View File

@ -0,0 +1,36 @@
// Node Modules
@import "../../node_modules/@blueprintjs/core/dist/blueprint.css";
// Partials
@import "animations";
@import "colors";
@import "fonts";
// Global
@import "global";
// Components
@import "blocks";
@import "blueprint";
@import "buttons";
@import "devices";
@import "events";
@import "farm_designer";
@import "farm_designer_mobile";
@import "farm_designer_panels";
@import "front_page";
@import "inputs";
@import "labels";
@import "navbar";
@import "regimen_editor_widget";
@import "regimens";
@import "sequences";
@import "spinner";
@import "status_ticker";
@import "steps";
@import "tables";
@import "toastr";
@import "tooltips";
@import "tools";
@import "weed_detector";
@import "widget_move";
@import "widget_tool_control";
@import "widgets";
@import "image_flipper";

View File

@ -0,0 +1,8 @@
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,28 @@
// Styles for the sequence and regimen BLOCKS
.block-wrapper {
height: 3.5rem;
margin-bottom: 1.5rem;
button {
transition: all 0.2s ease-out;
&:hover {
transition: all 0.2s ease-out;
.block-control {
transition: all 0.2s ease-out;
opacity: 1;
}
}
}
}
.block-control {
transition: all 0.2s ease-out;
opacity: 0;
float: right;
font-size: 1.4rem !important;
padding-left: 1rem;
}
.block-header {
height: 3.5rem;
font-size: 1.2rem !important;
}

View File

@ -0,0 +1,91 @@
// Padding for the popups.
.pt-popover-content {
padding: 1rem;
}
// Arrow is slightly off by default in the popup menu.
.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow {
left: -1rem;
}
// As of right now, all the menus appear from floated icons in the widgets.
.widget-header {
.pt-popover-target {
float: right;
}
}
.device-widget {
.pt-popover-target {
width: 100%;
}
.pt-icon-standard {
float: right;
margin-top: 0.7rem;
}
.pt-button {
text-align: left;
width: 100%;
span {
font-weight: normal;
text-transform: none;
font-size: 1.2rem;
}
}
}
.pt-overlay-content.pt-tether-abutted.pt-tether-abutted-top.pt-tether-element.pt-tether-element-attached-left.pt-tether-element-attached-top.pt-tether-enabled.pt-tether-target-attached-bottom.pt-tether-target-attached-left.pt-transition-container {
top: 2rem !important;
}
.pt-menu {
max-height: 20rem !important;
overflow-y: scroll;
}
.pt-input-group {
.pt-icon {
top: 0.6rem;
}
}
.pt-button:not(.pt-minimal) {
font-weight: normal;
text-transform: none;
background: $white;
border-radius: 0;
font-size: 1.2rem !important;
color: $black !important;
height: 27px !important;
width: 100% !important;
border: 0 !important;
box-shadow: 0px 0px 10px #ddd !important;
background: $white !important;
&:active {
box-shadow: none !important;
transform: translateY(0px) !important;
}
&:hover {
background: $white !important;
}
:first-child {
white-space: nowrap;
width: 80%;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
}
.pt-popover-target {
position: relative;
display: block;
* {
text-align: left;
}
}
.pt-icon-standard.pt-align-right {
float: right !important;
margin-top: 0.6rem !important;
}

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