Compare commits

..

79 Commits

Author SHA1 Message Date
Francesco Stasi
51da3c0668 Version 2.0.0-rc3 2021-12-22 16:44:17 +01:00
Francesco Stasi
c00d3d33dd Merge remote-tracking branch 'origin/i18n/translations-update' 2021-12-22 16:43:22 +01:00
Francesco Stasi
cfa9b8aea6 bump serial plotter to 0.0.17 2021-12-22 11:32:44 +01:00
per1234
6106e9ff1a Use major version ref of carlosperate/download-file-action
The `carlosperate/download-file-action` action is used in the GitHub Actions workflows as a convenient way to download
external resources.

A major version ref has been added to that repository. It will always point to the latest release of the "1" major
version series. This means it is no longer necessary to do a full pin of the action version in use as before.

Use of the major version ref will cause the workflow to use a stable version of the action, while also benefiting from
ongoing development to the action up until such time as a new major release of an action is made. At that time we would
need to evaluate whether any changes to the workflow are required by the breaking change that triggered the major
release before manually updating the major ref (e.g., uses: `carlosperate/download-file-action@v2`). I think this
approach strikes the right balance between stability and maintainability for these workflows.
2021-12-21 01:19:29 -08:00
Francesco Stasi
b1d9f65a0d bump serial plotter version (#698) 2021-12-20 15:49:16 +01:00
Francesco Stasi
f4008100e1 Correctly transform uint8array to string (#696)
* correctly transform uint8array to string

* export function
2021-12-20 14:56:38 +01:00
Francesco Stasi
11a6959a24 serial monitor lines not to wrap (#697) 2021-12-20 14:56:26 +01:00
github-actions[bot]
3c6e11832b Updated translation files 2021-12-20 02:19:55 +00:00
Alberto Iannaccone
c064673ce1 Close serial port connection before flashing firmware (#688) 2021-12-15 09:31:12 +00:00
Silvano Cerza
cc5764e536 Update README.md
Co-authored-by: per1234 <accounts@perglass.com>
2021-12-14 17:47:31 +01:00
Silvano Cerza
9131f2d09e Update README.md with translations project link 2021-12-14 17:47:31 +01:00
Alberto Iannaccone
0b6fc0b973 Version 2.0.0-rc2 2021-12-13 11:04:35 +01:00
Alberto Iannaccone
c91fe2d775 bump arduino-language-server to 0.5.0 (#679) 2021-12-13 09:55:50 +00:00
github-actions[bot]
bbded57ae4 Updated translation files (#638)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-13 09:20:03 +01:00
Francesco Stasi
a8ae0bb4e0 workaround: stop discoveries before install/uninstall boards/libs (#674) 2021-12-10 17:03:24 +01:00
Francesco Stasi
49d12d99ff IDE to run CLI with auto assigned port (#673)
* get daemon port from CLI stdout

* config-service to use CLI daemon port

* updating LS

* fixed tests

* fix upload blocked when selectedBoard.port is undefined

* bump arduino-cli to 0.20.2

Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-12-09 15:08:26 +01:00
Francesco Stasi
767b09d2f1 Fix upload and serial (#661)
* get serial connection status from BE

* handle serial connect in the BE

* allow breakpoints on vscode (windows)

* Timeout on config change to prevent serial busy

* serial-service tests
2021-12-07 17:38:43 +01:00
Alberto Iannaccone
88397931c5 Automatically install 'Arduino_BuiltIn' library at first startup (#663) 2021-12-06 15:56:17 +00:00
Silvano Cerza
5ddab1ded7 Remove gRPC error code from error notifications 2021-12-06 09:58:17 +01:00
Francesco Stasi
f0d9894a16 Fix notification icons (#642) 2021-11-30 17:24:29 +01:00
Alberto Iannaccone
59e4c57ecd Update version to 2.0.0-rc1 2021-11-30 12:21:59 +01:00
Francesco Stasi
dd76f9180c Update Theia, CLI and LS (#610)
* Update Theia to 1.19.0

* update CLI to 0.20.0-rc3

* Add language selector to settings

* updated language server and vscode-arduino-tools

* update Language Server flags

* get cli port from config

* force native menu on windows

* pinned Language Server to rc2

* fix search icon

* update CLI version
2021-11-29 15:54:13 +01:00
Alberto Iannaccone
6e34a27b7e move language server preference to advanced 2021-11-29 15:03:03 +01:00
Silvano Cerza
a090dfe99c Add dialog to insert user fields for board that require them to upload (#550)
* Rebuild gRPC protocol interfaces

* Implement methods to get user fields for board/port combination

* Implement dialog to input board user fields

* Add configure and upload step when uploading to board requiring user fields

* Disable Sketch > Configure and Upload menu if board doesn't support user fields

* Fix serial upload not working with all boards

* Update i18n source file

* fix user fields UI

* regenerate cli protocol

* fix localisation

* check if user fields are empty

Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-11-25 18:22:51 +01:00
Silvano Cerza
74bfdc4c56 Rework listing of discovered ports (#614)
* Removed Protocol type

* Reworked function that groups ports by protocol

* Remove useless protocol check in Port sameAs function

* Reworked port selection menu ordering

Now ports are shown in this order:
1. Serial with recognized boards
2. Serial with unrecognized boards
3. Network with recognized boards
4. Network with unrecognized boards
5. Other protocols with recognized boards
6. Other protocols with unrecognized boards

* Fix ports shown multiple times in menu

* Reworked board selection dropdown ordering

Ordering is now:
1. Serial with recognized boards
2. Serial with guessed boards
3. Serial with incomplete boards
4. Network with recognized boards
5. Other protocols with recognized boards

* Localize some strings

* Fix bug selecting board in boards selector dropdown

* Reworked board selection dialog ordering

* Fix Tools > Port menu not refreshing

* Move Select other board button to bottom of Board selector dropdown and change its style

* Updated arduino-cli to 0.20.0 and generated protocol files
2021-11-24 15:15:40 +01:00
Alberto Iannaccone
20f7712129 Serial Plotter implementation (#597)
* spawn new window where to instantiate serial plotter app

* initialize serial monito web app

* connect serial plotter app with websocket

* use npm serial-plotter package

* refactor monitor connection and fix some connection issues

* fix clearConsole + refactor monitor connection

* add serial unit tests

* refactoring and cleaning code
2021-11-23 17:18:20 +00:00
Francesco Stasi
9863dc2f90 Fix editor tabs order (#612) 2021-11-23 12:16:56 +01:00
Francesco Stasi
13734a642c Disable Editor breadcrumbs by default (#611) 2021-11-23 12:14:45 +01:00
Silvano Cerza
7ac7ae9063 Fix i18n:generate command not including tsx files 2021-11-17 18:17:03 +01:00
Federico Bond
437caeb348 Open Save as... dialog when saving sketches for the first time (#579)
* Properly recognize temporary sketches in macOS

Without this fix, sketches report their URI path as /private/var/xxx
whereas `os.tmpdir()` returns /var/xxx. The second path can be turned
into the first by resolving symlinks, which gives a canonical path to
compare against.

* Open Save as... dialog when saving sketches for the first time
2021-11-10 15:46:24 +00:00
Silvano Cerza
3b04d8df26 Remove gRPC errors codes from compile/upload console output (#564) 2021-11-05 10:08:06 +01:00
Silvano Cerza
99d65531c4 Update translation source file 2021-11-05 09:49:05 +01:00
Silvano Cerza
4f4ccb8c66 Add step to install dependencies in i18n workflows 2021-11-05 09:49:05 +01:00
Silvano Cerza
7bc83eba1d Update theia/cli version 2021-11-05 09:49:05 +01:00
Silvano Cerza
72750f0be3 Update .github/workflows/check-i18n-task.yml
Co-authored-by: per1234 <accounts@perglass.com>
2021-11-05 09:49:05 +01:00
Silvano Cerza
8cbf7f419c Apply suggestions from code review
Co-authored-by: per1234 <accounts@perglass.com>
2021-11-05 09:49:05 +01:00
Silvano Cerza
ea2aeec69b Add workflows to push and pull translations from Transifex and check source file is updated when necessary 2021-11-05 09:49:05 +01:00
Silvano Cerza
b83702fde3 Add commands to generate translation file and check they're updated 2021-11-05 09:49:05 +01:00
Silvano Cerza
5be3e9de2d Add script to push translations source to transifex 2021-11-05 09:49:05 +01:00
Silvano Cerza
e8bc7d7179 Add script to download translations from transifex 2021-11-05 09:49:05 +01:00
Mark Sujew
acbb164c3c Fix cortex-debug related debugging issue (#578)
* Fix cortex-debug related debugging issue

* Update arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts

Co-authored-by: Francesco Stasi <francescomaria.stasi@gmail.com>
2021-10-27 12:56:17 +02:00
Silvano Cerza
99099b06aa Fix duplicated id children warnings 2021-10-20 11:28:23 +02:00
Silvano Cerza
5c958bc6c7 Fix Tools > Board and Tools > Port labels (#558) 2021-10-18 11:35:26 +02:00
Mark Sujew
11b75bd610 Translating Arduino-IDE using Theia's nls API (#545) 2021-10-18 09:59:33 +02:00
Francesco Stasi
61262c23ac fix: reset charCount on serial monitor reset 2021-10-15 00:11:26 +02:00
per1234
7503739a9f Sync labels in write mode on schedule trigger
In order to facilitate the testing and review of proposed changes to the repository label infrastructure, the
"Sync Labels" template workflow does a dry run when triggered under conditions that indicate it would not be appropriate
to make real changes to the repository's labels. The changes that would have resulted are printed to the log, but not
actually made.

One of the criteria used to determine "dry run" mode usage is whether the event occurred on the repository's default
branch. A trigger on a development branch or for a pull request should not result in a change to the labels.
It turns out that GitHub does not define a `github.event.repository.default_branch` context item when a workflow is
triggered by a `schedule` event. This resulted in the workflow always running in "dry run" mode on a `schedule` trigger.
Since `schedule` and `repository_dispatch` triggers are only permitted for the default branch, there is no need to check
whether the event's ref matches the default branch and it is safe to always run in write mode on these events.
2021-10-13 01:57:33 -07:00
per1234
060ab5bccb Correct context key name in "Sync Labels" workflow
Incorrect context key name resulted in impossible to satisfy conditional, meaning the dry run determination code was
solely dependent on the check for whether the workflow was triggered from the default branch name.
2021-10-13 01:57:33 -07:00
Steve Anderson
1c42b8cefc Footer min-height for library and board manager (#392)
Increase the `min-height` from 26px to 30px to prevent the list items from changing height when mousing over them
2021-10-07 16:39:26 +01:00
Francesco Stasi
825f0b0f2a Updated to 2.0.0-beta.12 2021-10-07 09:38:19 +02:00
Francesco Stasi
846c22cb03 Theia 18 hotfixes (#528)
* Restore monaco suggestion highlights

* remove duplicated tabs on startup

* fix rename and delete sketch

* remove '.only(...)' in tests

Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-10-06 16:50:02 +01:00
Francesco Stasi
fc0f67493b [ATL-1599] [ATL-1416] Upgrade Theia to 1.18.0 (#489)
Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-10-06 13:55:55 +02:00
Francesco Stasi
54a67fc67c Improve Serial Monitor Performances (#524)
Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-10-06 09:21:06 +02:00
Alberto Iannaccone
7f8b227c39 [ATL-1531] Integrate arduino-cli 0.19.1 (#506)
* integrate cli 0.19.0

* Update CLI version used to fix crash on lib/core install/uninstall

* Update CLI version

* Update CLI version

* update cli version

Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
2021-09-30 09:02:09 +01:00
Silvano Cerza
ba177be41d [skip changelog] Add missing athena script 2021-09-27 18:14:06 +02:00
Silvano Cerza
0eb2d25570 [skip changelog] Update workflow and script to fetch Arduino CDN download data 2021-09-27 18:07:32 +02:00
Alberto Iannaccone
e9db1c0482 implement unit tests for boards-auto-installer (#513)
Co-authored-by: Francesco Stasi <f.stasi@me.com>
2021-09-27 10:09:11 +01:00
per1234
79b075c961 Add CI workflow to synchronize with shared repository labels
On every push that changes relevant files, and periodically, configure the repository's issue and pull request labels
according to the universal, shared, and local label configuration files.
2021-09-24 10:01:57 -07:00
rsora
a46f36acd1 [skip changelog] Add stats workflow to gather downloads data 2021-09-24 18:11:06 +02:00
Alberto Iannaccone
bfb90a8b4f at first ide startup invoke installation of arduino:avr (#497) 2021-09-02 11:50:26 +01:00
Alberto Iannaccone
658c19f55b [ATL-1571] Fix editor quick suggestions preference (#494)
* Fix editor quick suggestions preference

* little settings refactoring
2021-09-02 11:50:04 +01:00
Alberto Iannaccone
3f8a07654d add refresh icon to fontawesome (#493) 2021-09-02 11:49:44 +01:00
Alberto Iannaccone
a8ec7c2640 Change menu item "Export compiled Binary" to "Export Compiled Binary" (#492) 2021-09-02 11:49:16 +01:00
Alberto Iannaccone
a7a1f95ced Adjust "Edit" menu to remove "Copy for Forum"/"Copy for GitHub" redundancy (#491) 2021-09-02 11:48:42 +01:00
Yash
835e9913ae Fix README broken link (#467)
I believe this file name " path src/node/monitor-service-impl.ts " was moved into another folder named monitor, making the correct path for this file here "src/node/monitor/monitor-service-impl.ts"
2021-08-31 15:55:47 +02:00
Francesco Stasi
d3d6ba8176 [ATL-1556] Sort board families in Tool menu (#486)
* [ATL-1556] Sort board families in Tool menu
2021-08-26 15:25:37 +02:00
Francesco Stasi
0f82e91380 [ATL-1570] Install core notification not to appear on board unplug (#485) 2021-08-26 15:09:56 +02:00
Francesco Stasi
7d5381bbde Updated to 2.0.0-beta.11 2021-08-25 10:43:10 +02:00
Francesco Stasi
302fb7b6af [ATL-1533] Firmware&Certificate Uploader (#469)
Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-08-25 10:36:51 +02:00
Alberto Iannaccone
6233e1fa98 [ATL-493] Support platforms installed in directories.user 2021-08-23 10:47:36 +02:00
per1234
2cb9889fe4 Add source URL comment to "Check Certificates" workflow
This will make it easier for the maintainers to sync fixes and improvements in either direction between the upstream
"template" workflow and its installation in this repository.
2021-08-18 03:00:24 -07:00
per1234
bed6e0b741 Use major version ref of Slack notification action
At the time the workflow was written the authors of the `rtCamp/action-slack-notify` GitHub Actions action did not
provide a major version ref. This meant that it was necessary to pin the action to a specific version.

Since then, a few new releases have been made, meaning an outdated version of the action was in use as a consequence of
the pinning.

The action now offers a `v2` major ref. Use of this ref will cause the workflow to benefit from ongoing development to
the action up until such time as a new major release is made, at which time we would need to evaluate whether any changes
to the workflow are required by the breaking change that triggered the major release before updating the major ref
(e.g., `uses: rtCamp/action-slack-notify@v3`).
2021-08-18 03:00:24 -07:00
per1234
302f0109dd Use standardized repository secret name for Slack webhook
The "Check Certificates" workflow is configured to send a notification via Slack if a problem is found with a certificate.
TThis is currently posted to the `team_tooling` channel, but that is not necessarily always going to be the case, and for
every deployment of the workflow. So a less specific secret name is more universally applicable to serve all applications
of this "template" workflow.
2021-08-18 03:00:24 -07:00
per1234
735d3733e2 Make trivial formatting changes to "Check Certificates" workflow
No functional change, and neither is necessarily superior, but this is the formatting style either defined in the
"template", or by the repository's Prettier formatting configuration preferences, so it must be brought into compliance
here as well.
2021-08-18 03:00:24 -07:00
per1234
4b36852f57 Use the matrix identifier to name the "Check Certificates" workflow jobs
When no name is provided for a matrix job, the workflow job is named according to the contents of
`jobs[].<job_id>.strategy.matrix[]`. That can result in some fairly cryptic job names when the matrix contains a complex
data structure as is the case here. We already have a string to uniquely identify each certificate to humans, which is
exactly what the `jobs[].<job_id>.name` property does for jobs, so it will be an improvement to name the jobs according
to that identifier.
2021-08-18 03:00:24 -07:00
per1234
b84b6c921d Make trivial adjustments to comments in "Check Certificates" workflow
No functional difference, and neither is necessarily superior, but this is how it is in the "template", and so it must be
here as well.
2021-08-18 03:00:24 -07:00
per1234
289f07f187 Run "Check Certificates" workflow on modification
This will facilitate testing and review of modifications to the workflow.

Because the workflow requires access to repository secrets, and so will fail whenever triggered by an event from a fork,
a conditional is added to make it only run when the modifications are made within the `arduino/arduino-ide`
repository.
2021-08-18 03:00:24 -07:00
per1234
b9c777a5c3 Add API trigger to "Check Certificates" workflow
The `repository_dispatch` event allows triggering workflows via the GitHub API. This might be useful for triggering an
immediate check in multiple relevant repositories after an external change, or some automated process. Although we don't
have any specific need for this event at the moment, the event has no impact on the workflow, so there is no reason
against having it. It is the sort of thing that can end up being useful if it is already in consistently in place, but
not worth setting up on demand, since the effort to set it up is greater than the effort to trigger all the workflows
manually.
2021-08-18 03:00:24 -07:00
per1234
92af4bef26 Use standardized name for certificate check workflow
This is the naming convention established in the standardized "template" workflow.
2021-08-18 03:00:24 -07:00
Jim Marinis
167f059163 Update BUILDING.md
Corrected typographical error where "on" was used rather than "one".
2021-08-06 05:16:05 -07:00
240 changed files with 22695 additions and 8147 deletions

View File

@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'type: bug'
labels: 'type: imperfection'
assignees: ''
---

View File

@@ -0,0 +1,24 @@
# Used by the "Sync Labels" workflow
# See: https://github.com/Financial-Times/github-label-sync#label-config-file
- name: "topic: accessibility"
color: "00ffff"
description: Enabling the use of the software by everyone
- name: "topic: CLI"
color: "00ffff"
description: Related to Arduino CLI
- name: "topic: debugger"
color: "00ffff"
description: Related to the integrated debugger
- name: "topic: language server"
color: "00ffff"
description: Related to the Arduino Language Server
- name: "topic: serial monitor"
color: "00ffff"
description: Related to the Serial Monitor
- name: "topic: theia"
color: "00ffff"
description: Related to the Theia IDE framework
- name: "topic: theme"
color: "00ffff"
description: Related to GUI theming

131
.github/tools/fetch_athena_stats.py vendored Normal file
View File

@@ -0,0 +1,131 @@
import boto3
import semver
import os
import logging
import uuid
import time
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
log = logging.getLogger()
logging.getLogger("boto3").setLevel(logging.CRITICAL)
logging.getLogger("botocore").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
def execute(client, statement, dest_s3_output_location):
log.info("execute query: {} dumping in {}".format(statement, dest_s3_output_location))
result = client.start_query_execution(
QueryString=statement,
ClientRequestToken=str(uuid.uuid4()),
ResultConfiguration={
"OutputLocation": dest_s3_output_location,
},
)
execution_id = result["QueryExecutionId"]
log.info("wait for query {} completion".format(execution_id))
wait_for_query_execution_completion(client, execution_id)
log.info("operation successful")
return execution_id
def wait_for_query_execution_completion(client, query_execution_id):
query_ended = False
while not query_ended:
query_execution = client.get_query_execution(QueryExecutionId=query_execution_id)
state = query_execution["QueryExecution"]["Status"]["State"]
if state == "SUCCEEDED":
query_ended = True
elif state in ["FAILED", "CANCELLED"]:
raise BaseException(
"query failed or canceled: {}".format(query_execution["QueryExecution"]["Status"]["StateChangeReason"])
)
else:
time.sleep(1)
def valid(key):
split = key.split("_")
if len(split) < 1:
return False
try:
semver.parse(split[0])
except ValueError:
return False
return True
def get_results(client, execution_id):
results_paginator = client.get_paginator("get_query_results")
results_iter = results_paginator.paginate(QueryExecutionId=execution_id, PaginationConfig={"PageSize": 1000})
res = {}
for results_page in results_iter:
for row in results_page["ResultSet"]["Rows"][1:]:
# Loop through the JSON objects
key = row["Data"][0]["VarCharValue"]
if valid(key):
res[key] = row["Data"][1]["VarCharValue"]
return res
def convert_data(data):
result = []
for key, value in data.items():
# 0.18.0_macOS_64bit.tar.gz
split_key = key.split("_")
if len(split_key) != 3:
continue
(version, os_version, arch) = split_key
arch_split = arch.split(".")
if len(arch_split) < 1:
continue
arch = arch_split[0]
if len(arch) > 10:
# This can't be an architecture really.
# It's an ugly solution but works for now so deal with it.
continue
repo = os.environ["GITHUB_REPOSITORY"].split("/")[1]
result.append(
{
"type": "gauge",
"name": "arduino.downloads.total",
"value": value,
"host": os.environ["GITHUB_REPOSITORY"],
"tags": [
f"version:{version}",
f"os:{os_version}",
f"arch:{arch}",
"cdn:downloads.arduino.cc",
f"project:{repo}",
],
}
)
return result
if __name__ == "__main__":
DEST_S3_OUTPUT = os.environ["AWS_ATHENA_OUTPUT_LOCATION"]
AWS_ATHENA_SOURCE_TABLE = os.environ["AWS_ATHENA_SOURCE_TABLE"]
session = boto3.session.Session(region_name="us-east-1")
athena_client = session.client("athena")
# Load all partitions before querying downloads
execute(athena_client, f"MSCK REPAIR TABLE {AWS_ATHENA_SOURCE_TABLE};", DEST_S3_OUTPUT)
query = f"""SELECT replace(json_extract_scalar(url_decode(url_decode(querystring)),
'$.data.url'), 'https://downloads.arduino.cc/arduino-ide/arduino-ide_', '')
AS flavor, count(json_extract(url_decode(url_decode(querystring)),'$')) AS gauge
FROM {AWS_ATHENA_SOURCE_TABLE}
WHERE json_extract_scalar(url_decode(url_decode(querystring)),'$.data.url')
LIKE 'https://downloads.arduino.cc/arduino-ide/arduino-ide_%'
AND json_extract_scalar(url_decode(url_decode(querystring)),'$.data.url')
NOT LIKE '%latest%' -- exclude latest redirect
group by 1 ;"""
exec_id = execute(athena_client, query, DEST_S3_OUTPUT)
results = get_results(athena_client, exec_id)
result_json = convert_data(results)
print(f"::set-output name=result::{result_json}")

57
.github/workflows/arduino-stats.yaml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: arduino-stats
on:
schedule:
# run every day at 07:00 AM, 03:00 PM and 11:00 PM
- cron: "0 7,15,23 * * *"
workflow_dispatch:
repository_dispatch:
jobs:
push-stats:
# This workflow is only of value to the arduino/arduino-ide repository and
# would always fail in forks
if: github.repository == 'arduino/arduino-ide'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Fetch downloads count form Arduino CDN using AWS Athena
id: fetch
env:
AWS_ACCESS_KEY_ID: ${{ secrets.STATS_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.STATS_AWS_SECRET_ACCESS_KEY }}
AWS_ATHENA_SOURCE_TABLE: ${{ secrets.STATS_AWS_ATHENA_SOURCE_TABLE }}
AWS_ATHENA_OUTPUT_LOCATION: ${{ secrets.STATS_AWS_ATHENA_OUTPUT_LOCATION }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
pip install boto3 semver
python .github/tools/fetch_athena_stats.py
- name: Send metrics
uses: masci/datadog@v1
with:
api-key: ${{ secrets.DD_API_KEY }}
# Metrics input expects YAML but JSON will work just right.
metrics: ${{steps.fetch.outputs.result}}
- name: Report failure
if: failure()
uses: masci/datadog@v1
with:
api-key: ${{ secrets.DD_API_KEY }}
events: |
- title: "Arduino IDE stats failing"
text: "Stats collection failed"
alert_type: "error"
host: ${{ github.repository }}
tags:
- "project:arduino-ide"
- "cdn:downloads.arduino.cc"
- "workflow:${{ github.workflow }}"

View File

@@ -70,6 +70,7 @@ jobs:
elif [ "${{ runner.OS }}" = "Windows" ]; then
export CSC_LINK="${{ runner.temp }}/signing_certificate.pfx"
npm config set msvs_version 2017 --global
echo "${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PFX }}" | base64 --decode > "$CSC_LINK"
export CSC_KEY_PASSWORD="${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}"

View File

@@ -1,35 +1,41 @@
name: Check for issues with signing certificates
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-certificates.md
name: Check Certificates
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
on:
push:
paths:
- '.github/workflows/check-certificates.ya?ml'
pull_request:
paths:
- '.github/workflows/check-certificates.ya?ml'
schedule:
# run every 10 hours
- cron: "0 */10 * * *"
# workflow_dispatch event allows the workflow to be triggered manually.
# This could be used to run an immediate check after updating certificate secrets.
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch
# Run every 10 hours.
- cron: '0 */10 * * *'
workflow_dispatch:
repository_dispatch:
env:
# Begin notifications when there are less than this many days remaining before expiration
# Begin notifications when there are less than this many days remaining before expiration.
EXPIRATION_WARNING_PERIOD: 30
jobs:
check-certificates:
name: ${{ matrix.certificate.identifier }}
# Only run when the workflow will have access to the certificate secrets.
if: >
(github.event_name != 'pull_request' && github.repository == 'arduino/arduino-ide') ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'arduino/arduino-ide')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
certificate:
- identifier: macOS signing certificate # Text used to identify the certificate in notifications
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # The name of the secret that contains the certificate
password-secret: KEYCHAIN_PASSWORD # The name of the secret that contains the certificate password
# Additional certificate definitions can be added to this list.
- identifier: macOS signing certificate # Text used to identify certificate in notifications.
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # Name of the secret that contains the certificate.
password-secret: KEYCHAIN_PASSWORD # Name of the secret that contains the certificate password.
- identifier: Windows signing certificate
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
@@ -37,7 +43,7 @@ jobs:
steps:
- name: Set certificate path environment variable
run: |
# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
# See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
echo "CERTIFICATE_PATH=${{ runner.temp }}/certificate.p12" >> "$GITHUB_ENV"
- name: Decode certificate
@@ -59,18 +65,17 @@ jobs:
exit 1
)
# See: https://github.com/rtCamp/action-slack-notify
- name: Slack notification of certificate verification failure
if: failure()
uses: rtCamp/action-slack-notify@v2.1.0
env:
SLACK_WEBHOOK: ${{ secrets.TEAM_TOOLING_CHANNEL_SLACK_WEBHOOK }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_MESSAGE: |
:warning::warning::warning::warning:
WARNING: ${{ github.repository }} ${{ matrix.certificate.identifier }} verification failed!!!
:warning::warning::warning::warning:
SLACK_COLOR: danger
MSG_MINIMAL: true
uses: rtCamp/action-slack-notify@v2
- name: Get days remaining before certificate expiration date
env:
@@ -99,7 +104,7 @@ jobs:
DAYS_BEFORE_EXPIRATION="$((($(date --utc --date="$EXPIRATION_DATE" +%s) - $(date --utc +%s)) / 60 / 60 / 24))"
# Display the expiration information in the log
# Display the expiration information in the log.
echo "Certificate expiration date: $EXPIRATION_DATE"
echo "Days remaining before expiration: $DAYS_BEFORE_EXPIRATION"
@@ -114,14 +119,14 @@ jobs:
fi
- name: Slack notification of pending certificate expiration
# Don't send spurious expiration notification if verification fails
# Don't send spurious expiration notification if verification fails.
if: failure() && steps.check-expiration.outcome == 'failure'
uses: rtCamp/action-slack-notify@v2.1.0
env:
SLACK_WEBHOOK: ${{ secrets.TEAM_TOOLING_CHANNEL_SLACK_WEBHOOK }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_MESSAGE: |
:warning::warning::warning::warning:
WARNING: ${{ github.repository }} ${{ matrix.certificate.identifier }} will expire in ${{ steps.get-days-before-expiration.outputs.days }} days!!!
:warning::warning::warning::warning:
SLACK_COLOR: danger
MSG_MINIMAL: true
uses: rtCamp/action-slack-notify@v2

38
.github/workflows/check-i18n-task.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Check Internationalization
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
on:
push:
paths:
- '.github/workflows/check-i18n-task.ya?ml'
- '**/package.json'
- '**.ts'
- 'i18n/**'
pull_request:
paths:
- '.github/workflows/check-i18n-task.ya?ml'
- '**/package.json'
- '**.ts'
- 'i18n/**'
workflow_dispatch:
repository_dispatch:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Node.js 12.x
uses: actions/setup-node@v2
with:
node-version: '12.14.1'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: yarn
- name: Check for errors
run: yarn i18n:check

96
.github/workflows/github-stats.yaml vendored Normal file
View File

@@ -0,0 +1,96 @@
name: github-stats
on:
schedule:
# run every 30 minutes
- cron: "*/30 * * * *"
workflow_dispatch:
repository_dispatch:
jobs:
push-stats:
# This workflow is only of value to the arduino/arduino-ide repository and
# would always fail in forks
if: github.repository == 'arduino/arduino-ide'
runs-on: ubuntu-latest
steps:
- name: Fetch downloads count
id: fetch
uses: actions/github-script@v4
with:
github-token: ${{github.token}}
script: |
let metrics = []
// Get a list of releases
const opts = github.repos.listReleases.endpoint.merge({
...context.repo
})
const releases = await github.paginate(opts)
// Get download stats for every release
for (const rel of releases) {
// Names for assets are like `arduino-ide_2.0.0-beta.12_Linux_64bit.zip`,
// we'll use this later to split the asset file name more easily
const baseName = `arduino-ide_${rel.name}_`
// Get a list of assets for this release
const opts = github.repos.listReleaseAssets.endpoint.merge({
...context.repo,
release_id: rel.id
})
const assets = await github.paginate(opts)
for (const asset of assets) {
// Ignore files that are not arduino-ide packages
if (!asset.name.startsWith(baseName)) {
continue
}
// Strip the base and remove file extension to get `Linux_32bit`
systemArch = asset.name.replace(baseName, "").split(".")[0].split("_")
// Add a metric object to the list of gathered metrics
metrics.push({
"type": "gauge",
"name": "arduino.downloads.total",
"value": asset.download_count,
"host": "${{ github.repository }}",
"tags": [
`version:${rel.name}`,
`os:${systemArch[0]}`,
`arch:${systemArch[1]}`,
"cdn:github.com",
"project:arduino-ide"
]
})
}
}
// The action will put whatever we return from this function in
// `outputs.result`, JSON encoded. So we just return the array
// of objects and GitHub will do the rest.
return metrics
- name: Send metrics
uses: masci/datadog@v1
with:
api-key: ${{ secrets.DD_API_KEY }}
# Metrics input expects YAML but JSON will work just right.
metrics: ${{steps.fetch.outputs.result}}
- name: Report failure
if: failure()
uses: masci/datadog@v1
with:
api-key: ${{ secrets.DD_API_KEY }}
events: |
- title: "Arduino IDE stats failing"
text: "Stats collection failed"
alert_type: "error"
host: ${{ github.repository }}
tags:
- "project:arduino-ide"
- "cdn:github.com"
- "workflow:${{ github.workflow }}"

30
.github/workflows/i18n-nightly-push.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: i18n-nightly-push
on:
schedule:
# run every day at 1AM
- cron: '0 1 * * *'
jobs:
push-to-transifex:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node.js 12.x
uses: actions/setup-node@v2
with:
node-version: '12.14.1'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: yarn
- name: Run i18n:push script
run: yarn run i18n:push
env:
TRANSIFEX_ORGANIZATION: ${{ secrets.TRANSIFEX_ORGANIZATION }}
TRANSIFEX_PROJECT: ${{ secrets.TRANSIFEX_PROJECT }}
TRANSIFEX_RESOURCE: ${{ secrets.TRANSIFEX_RESOURCE }}
TRANSIFEX_API_KEY: ${{ secrets.TRANSIFEX_API_KEY }}

38
.github/workflows/i18n-weekly-pull.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: i18n-weekly-pull
on:
schedule:
# run every monday at 2AM
- cron: '0 2 * * 1'
jobs:
pull-from-transifex:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node.js 12.x
uses: actions/setup-node@v2
with:
node-version: '12.14.1'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: yarn
- name: Run i18n:pull script
run: yarn run i18n:pull
env:
TRANSIFEX_ORGANIZATION: ${{ secrets.TRANSIFEX_ORGANIZATION }}
TRANSIFEX_PROJECT: ${{ secrets.TRANSIFEX_PROJECT }}
TRANSIFEX_RESOURCE: ${{ secrets.TRANSIFEX_RESOURCE }}
TRANSIFEX_API_KEY: ${{ secrets.TRANSIFEX_API_KEY }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
commit-message: Updated translation files
title: Update translation files
branch: i18n/translations-update
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

139
.github/workflows/sync-labels.yml vendored Normal file
View File

@@ -0,0 +1,139 @@
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md
name: Sync Labels
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
on:
push:
paths:
- ".github/workflows/sync-labels.ya?ml"
- ".github/label-configuration-files/*.ya?ml"
pull_request:
paths:
- ".github/workflows/sync-labels.ya?ml"
- ".github/label-configuration-files/*.ya?ml"
schedule:
# Run daily at 8 AM UTC to sync with changes to shared label configurations.
- cron: "0 8 * * *"
workflow_dispatch:
repository_dispatch:
env:
CONFIGURATIONS_FOLDER: .github/label-configuration-files
CONFIGURATIONS_ARTIFACT: label-configuration-files
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Download JSON schema for labels configuration file
id: download-schema
uses: carlosperate/download-file-action@v1
with:
file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json
location: ${{ runner.temp }}/label-configuration-schema
- name: Install JSON schema validator
run: |
sudo npm install \
--global \
ajv-cli \
ajv-formats
- name: Validate local labels configuration
run: |
# See: https://github.com/ajv-validator/ajv-cli#readme
ajv validate \
--all-errors \
-c ajv-formats \
-s "${{ steps.download-schema.outputs.file-path }}" \
-d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}"
download:
needs: check
runs-on: ubuntu-latest
strategy:
matrix:
filename:
# Filenames of the shared configurations to apply to the repository in addition to the local configuration.
# https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels
- universal.yml
- tooling.yml
steps:
- name: Download
uses: carlosperate/download-file-action@v1
with:
file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }}
- name: Pass configuration files to next job via workflow artifact
uses: actions/upload-artifact@v2
with:
path: |
*.yaml
*.yml
if-no-files-found: error
name: ${{ env.CONFIGURATIONS_ARTIFACT }}
sync:
needs: download
runs-on: ubuntu-latest
steps:
- name: Set environment variables
run: |
# See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV"
- name: Determine whether to dry run
id: dry-run
if: >
github.event_name == 'pull_request' ||
(
(
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch'
) &&
github.ref != format('refs/heads/{0}', github.event.repository.default_branch)
)
run: |
# Use of this flag in the github-label-sync command will cause it to only check the validity of the
# configuration.
echo "::set-output name=flag::--dry-run"
- name: Checkout repository
uses: actions/checkout@v2
- name: Download configuration files artifact
uses: actions/download-artifact@v2
with:
name: ${{ env.CONFIGURATIONS_ARTIFACT }}
path: ${{ env.CONFIGURATIONS_FOLDER }}
- name: Remove unneeded artifact
uses: geekyeggo/delete-artifact@v1
with:
name: ${{ env.CONFIGURATIONS_ARTIFACT }}
- name: Merge label configuration files
run: |
# Merge all configuration files
shopt -s extglob
cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}"
- name: Install github-label-sync
run: sudo npm install --global github-label-sync
- name: Sync labels
env:
GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# See: https://github.com/Financial-Times/github-label-sync
github-label-sync \
--labels "${{ env.MERGED_CONFIGURATION_PATH }}" \
${{ steps.dry-run.outputs.flag }} \
${{ github.repository }}

3
.gitignore vendored
View File

@@ -7,7 +7,8 @@ build/
Examples/
!electron/build/
src-gen/
*webpack.config.js
!webpack.config.js
gen-webpack.config.js
.DS_Store
# switching from `electron` to `browser` in dev mode.
.browser_modules

12
.vscode/launch.json vendored
View File

@@ -8,10 +8,6 @@
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
"env": {
"NODE_ENV": "development",
"NODE_PRESERVE_SYMLINKS": "1"
}
},
"cwd": "${workspaceFolder}/electron-app",
"protocol": "inspector",
@@ -23,7 +19,8 @@
"--app-project-path=${workspaceRoot}/electron-app",
"--remote-debugging-port=9222",
"--no-app-auto-install",
"--plugins=local-dir:../plugins"
"--plugins=local-dir:../plugins",
"--hosted-plugin-inspect=9339"
],
"env": {
"NODE_ENV": "development"
@@ -33,7 +30,8 @@
"${workspaceRoot}/electron-app/src-gen/backend/*.js",
"${workspaceRoot}/electron-app/src-gen/frontend/*.js",
"${workspaceRoot}/electron-app/lib/**/*.js",
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js",
"${workspaceRoot}/node_modules/@theia/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
@@ -79,6 +77,8 @@
"args": [
"--require",
"reflect-metadata/Reflect",
"--require",
"ignore-styles",
"--no-timeouts",
"--colors",
"**/${fileBasenameNoExtension}.js"

View File

@@ -14,7 +14,7 @@ The _Electron main_ process is responsible for:
- managing the application lifecycle via listeners, and
- creating and managing the web pages for the app.
In Electron, the process that runs the main entry JavaScript file is called the main process. The _Electron main_ process can display a GUI by creating web pages. An Electron app always has exactly on main process.
In Electron, the process that runs the main entry JavaScript file is called the main process. The _Electron main_ process can display a GUI by creating web pages. An Electron app always has exactly one main process.
By default, whenever the _Electron main_ process creates a web page, it will instantiate a new `BrowserWindow` instance. Since Electron uses Chromium for displaying web pages, Chromium's multi-process architecture is also used. Each web page in Electron runs in its own process, which is called the renderer process. Each `BrowserWindow` instance runs the web page in its own renderer process. When a `BrowserWindow` instance is destroyed, the corresponding renderer process is also terminated. The main process manages all web pages and their corresponding renderer processes. Each renderer process is isolated and only cares about the web page running in it.<sup>[[1]]</sup>

View File

@@ -15,29 +15,30 @@ The Arduino IDE 2.x is a major rewrite, sharing no code with the IDE 1.x. It is
## Download
You can download the latest version from the [software download page on the Arduino website](https://www.arduino.cc/en/software#experimental-software).
### Nightly builds
These builds are generated every day at 03:00 GMT from the `main` branch and
should be considered unstable:
Platform | 32 bit | 64 bit |
--------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
Linux | | [Nightly Linux 64 bit] |
Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
Windows | | [Nightly Windows 64 bit installer]<br />[Nightly Windows 64 bit MSI]<br />[Nightly Windows 64 bit ZIP] |
macOS | | [Nightly macOS 64 bit] |
| Platform | 32 bit | 64 bit |
| --------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
| Linux | | [Nightly Linux 64 bit] |
| Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
| Windows | | [Nightly Windows 64 bit installer]<br />[Nightly Windows 64 bit MSI]<br />[Nightly Windows 64 bit ZIP] |
| macOS | | [Nightly macOS 64 bit] |
[🚧 Work in progress...]: https://github.com/arduino/arduino-ide/issues/107
[Nightly Linux 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip
[Nightly Windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.exe
[Nightly Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.msi
[Nightly Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.zip
[Nightly macOS 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_macOS_64bit.dmg
[🚧 work in progress...]: https://github.com/arduino/arduino-ide/issues/107
[nightly linux 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip
[nightly windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.exe
[nightly windows 64 bit msi]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.msi
[nightly windows 64 bit zip]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.zip
[nightly macos 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_macOS_64bit.dmg
> These links return an HTTP `302: Found` response, redirecting to latest
generated builds by replacing `latest` with the latest available build
date, using the format YYYYMMDD (i.e for 2019/Aug/06 `latest` is
replaced with `20190806`)
> generated builds by replacing `latest` with the latest available build
> date, using the format YYYYMMDD (i.e for 2019/Aug/06 `latest` is
> replaced with `20190806`)
## Support
@@ -47,8 +48,8 @@ If you need assistance, see the [Help Center](https://support.arduino.cc/hc/en-u
If you want to report an issue, you can submit it to the [issue tracker](https://github.com/arduino/arduino-ide/issues) of this repository. A few rules apply:
* Before posting, please check if the same problem has been already reported by someone else to avoid duplicates.
* Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. Make sure you're using an original Arduino board.
- Before posting, please check if the same problem has been already reported by someone else to avoid duplicates.
- Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. Make sure you're using an original Arduino board.
### Security
@@ -64,10 +65,13 @@ Contributions are very welcome! You can browse the list of open issues to see wh
This repository contains the main code, but two more repositories are included during the build process:
* [vscode-arduino-tools](https://github.com/arduino/vscode-arduino-tools): provides support for the language server and the debugger
* [arduino-language-server](https://github.com/arduino/arduino-language-server): provides the language server that parses Arduino code
- [vscode-arduino-tools](https://github.com/arduino/vscode-arduino-tools): provides support for the language server and the debugger
- [arduino-language-server](https://github.com/arduino/arduino-language-server): provides the language server that parses Arduino code
See the [BUILDING.md](BUILDING.md) for a technical overview of the application and instructions for building the code.
You can help with the translation of the Arduino IDE to your language here: [Arduino IDE on Transifex](https://www.transifex.com/arduino-1/ide2/dashboard/).
## Donations
This open source code was written by the Arduino team and is maintained on a daily basis with the help of the community. We invest a considerable amount of time in development, testing and optimization. Please consider [donating](https://www.arduino.cc/en/donate/) or [sponsoring](https://github.com/sponsors/arduino) to support our work, as well as [buying original Arduino boards](https://store.arduino.cc/) which is the best way to make sure our effort can continue in the long term.

View File

@@ -30,17 +30,20 @@ The Core Service is responsible for building your sketches and uploading them to
- compiling a sketch for a selected board type
- uploading a sketch to a connected board
#### Monitor Service
#### Serial Service
The Monitor Service allows getting information back from sketches running on your Arduino boards.
The Serial Service allows getting information back from sketches running on your Arduino boards.
- [src/common/protocol/monitor-service.ts](./src/common/protocol/monitor-service.ts) implements the common classes and interfaces
- [src/node/monitor-service-impl.ts](./src/node/monitor-service-impl.ts) implements the service backend:
- [src/common/protocol/serial-service.ts](./src/common/protocol/serial-service.ts) implements the common classes and interfaces
- [src/node/serial/serial-service-impl.ts](./src/node/serial/serial-service-impl.ts) implements the service backend:
- connecting to / disconnecting from a board
- receiving and sending data
- [src/browser/monitor/monitor-widget.tsx](./src/browser/monitor/monitor-widget.tsx) implements the serial monitor front-end:
- [src/browser/serial/serial-connection-manager.ts](./src/browser/serial/serial-connection-manager.ts) handles the serial connection in the frontend
- [src/browser/serial/monitor/monitor-widget.tsx](./src/browser/serial/monitor/monitor-widget.tsx) implements the serial monitor front-end:
- viewing the output from a connected board
- entering data to send to the board
- [src/browser/serial/plotter/plotter-frontend-contribution.ts](./src/browser/serial/plotter/plotter-frontend-contribution.ts) implements the serial plotter front-end:
- opening a new window running the [Serial Plotter Web App](https://github.com/arduino/arduino-serial-plotter-webapp)
#### Config Service
@@ -58,3 +61,13 @@ The Config Service knows about your system, like for example the default sketch
#### Rebuild gRPC protocol interfaces
- Some CLI updates can bring changes to the gRPC interfaces, as the API might change. gRPC interfaces can be updated running the command
`yarn --cwd arduino-ide-extension generate-protocol`
### Customize Icons
ArduinoIde uses a customized version of FontAwesome.
In order to update/replace icons follow the following steps:
- import the file `arduino-icons.json` in [Icomoon](https://icomoon.io/app/#/projects)
- load it
- edit the icons as needed
- !! download the **new** `arduino-icons.json` file and put it in this repo
- Click on "Generate Font" in Icomoon, then download
- place the updated fonts in the `src/style/fonts` directory

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,14 @@
{
"name": "arduino-ide-extension",
"version": "2.0.0-beta.10",
"version": "2.0.0-rc3",
"description": "An extension for Theia building the Arduino IDE",
"license": "AGPL-3.0-or-later",
"scripts": {
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn clean && yarn download-examples && yarn build",
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn copy-serial-plotter && yarn clean && yarn download-examples && yarn build && yarn test",
"clean": "rimraf lib",
"download-cli": "node ./scripts/download-cli.js",
"download-fwuploader": "node ./scripts/download-fwuploader.js",
"copy-serial-plotter": "npx ncp ../node_modules/arduino-serial-plotter-webapp ./build/arduino-serial-plotter-webapp",
"download-ls": "node ./scripts/download-ls.js",
"download-examples": "node ./scripts/download-examples.js",
"generate-protocol": "node ./scripts/generate-protocol.js",
@@ -18,22 +19,24 @@
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
},
"dependencies": {
"@grpc/grpc-js": "^1.1.1",
"@theia/application-package": "next",
"@theia/core": "next",
"@theia/editor": "next",
"@theia/filesystem": "next",
"@theia/git": "next",
"@theia/keymaps": "next",
"@theia/markers": "next",
"@theia/monaco": "next",
"@theia/navigator": "next",
"@theia/outline-view": "next",
"@theia/preferences": "next",
"@theia/output": "next",
"@theia/search-in-workspace": "next",
"@theia/terminal": "next",
"@theia/workspace": "next",
"@grpc/grpc-js": "^1.3.7",
"@theia/application-package": "1.19.0",
"@theia/core": "1.19.0",
"@theia/editor": "1.19.0",
"@theia/editor-preview": "1.19.0",
"@theia/filesystem": "1.19.0",
"@theia/git": "1.19.0",
"@theia/keymaps": "1.19.0",
"@theia/markers": "1.19.0",
"@theia/monaco": "1.19.0",
"@theia/navigator": "1.19.0",
"@theia/outline-view": "1.19.0",
"@theia/output": "1.19.0",
"@theia/preferences": "1.19.0",
"@theia/search-in-workspace": "1.19.0",
"@theia/terminal": "1.19.0",
"@theia/workspace": "1.19.0",
"@tippyjs/react": "^4.2.5",
"@types/atob": "^2.1.2",
"@types/auth0-js": "^9.14.0",
"@types/btoa": "^1.2.3",
@@ -49,10 +52,10 @@
"@types/ps-tree": "^1.1.0",
"@types/react-select": "^3.0.0",
"@types/react-tabs": "^2.3.2",
"@types/sinon": "^7.5.2",
"@types/temp": "^0.8.34",
"@types/which": "^1.3.1",
"ajv": "^6.5.3",
"arduino-serial-plotter-webapp": "0.0.17",
"async-mutex": "^0.3.0",
"atob": "^2.1.2",
"auth0-js": "^9.14.0",
@@ -63,6 +66,7 @@
"fuzzy": "^0.1.3",
"glob": "^7.1.6",
"google-protobuf": "^3.11.4",
"grpc": "^1.24.11",
"hash.js": "^1.1.7",
"is-valid-path": "^0.1.1",
"js-yaml": "^3.13.1",
@@ -74,20 +78,26 @@
"open": "^8.0.6",
"p-queue": "^5.0.0",
"ps-tree": "^1.2.0",
"query-string": "^7.0.1",
"react-disable": "^0.1.0",
"react-select": "^3.0.4",
"react-tabs": "^3.1.2",
"react-window": "^1.8.6",
"semver": "^7.3.2",
"string-natural-compare": "^2.0.3",
"temp": "^0.9.1",
"tree-kill": "^1.2.1",
"upath": "^1.1.2",
"url": "^0.11.0",
"which": "^1.3.1"
},
"devDependencies": {
"@types/chai": "^4.2.7",
"@types/chai-string": "^1.4.2",
"@types/mocha": "^5.2.7",
"@types/react-window": "^1.8.5",
"@types/sinon": "^10.0.6",
"@types/sinon-chai": "^3.2.6",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"decompress": "^4.2.0",
@@ -96,10 +106,13 @@
"download": "^7.1.0",
"grpc_tools_node_protoc_ts": "^4.1.0",
"mocha": "^7.0.0",
"mockdate": "^3.0.5",
"moment": "^2.24.0",
"protoc": "^1.0.4",
"shelljs": "^0.8.3",
"sinon": "^9.0.1",
"sinon": "^12.0.1",
"sinon-chai": "^3.7.0",
"typemoq": "^2.1.0",
"uuid": "^3.2.1",
"yargs": "^11.1.0"
},
@@ -108,7 +121,8 @@
},
"mocha": {
"require": [
"reflect-metadata/Reflect"
"reflect-metadata/Reflect",
"ignore-styles"
],
"reporter": "spec",
"colors": true,
@@ -137,10 +151,10 @@
],
"arduino": {
"cli": {
"version": "0.18.3"
"version": "0.20.2"
},
"fwuploader": {
"version": "1.0.2"
"version": "2.0.0"
}
}
}

View File

@@ -4,69 +4,78 @@
// - https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${VERSION}_${SUFFIX}
(() => {
const DEFAULT_ALS_VERSION = '0.5.0';
const DEFAULT_CLANGD_VERSION = 'snapshot_20210124';
const DEFAULT_ALS_VERSION = 'nightly';
const DEFAULT_CLANGD_VERSION = 'snapshot_20210124';
const path = require('path');
const shell = require('shelljs');
const downloader = require('./downloader');
const path = require('path');
const shell = require('shelljs');
const downloader = require('./downloader');
const yargs = require('yargs')
.option('ls-version', {
alias: 'lv',
default: DEFAULT_ALS_VERSION,
describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_ALS_VERSION}.`,
})
.option('clangd-version', {
alias: 'cv',
default: DEFAULT_CLANGD_VERSION,
choices: ['snapshot_20210124'],
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`,
})
.option('force-download', {
alias: 'fd',
default: false,
describe: `If set, this script force downloads the 'arduino-language-server' even if it already exists on the file system.`,
})
.version(false)
.parse();
const yargs = require('yargs')
.option('ls-version', {
alias: 'lv',
default: DEFAULT_ALS_VERSION,
choices: ['nightly'],
describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_ALS_VERSION}.`
})
.option('clangd-version', {
alias: 'cv',
default: DEFAULT_CLANGD_VERSION,
choices: ['snapshot_20210124'],
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`
})
.option('force-download', {
alias: 'fd',
default: false,
describe: `If set, this script force downloads the 'arduino-language-server' even if it already exists on the file system.`
})
.version(false).parse();
const alsVersion = yargs['ls-version'];
const clangdVersion = yargs['clangd-version'];
const force = yargs['force-download'];
const { platform, arch } = process;
const alsVersion = yargs['ls-version'];
const clangdVersion = yargs['clangd-version']
const force = yargs['force-download'];
const { platform, arch } = process;
const build = path.join(__dirname, '..', 'build');
const lsExecutablePath = path.join(
build,
`arduino-language-server${platform === 'win32' ? '.exe' : ''}`
);
const build = path.join(__dirname, '..', 'build');
const lsExecutablePath = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`);
let clangdExecutablePath, lsSuffix, clangdPrefix;
switch (platform) {
case 'darwin':
clangdExecutablePath = path.join(build, 'bin', 'clangd');
lsSuffix = 'macOS_64bit.tar.gz';
clangdPrefix = 'mac';
break;
case 'linux':
clangdExecutablePath = path.join(build, 'bin', 'clangd');
lsSuffix = 'Linux_64bit.tar.gz';
clangdPrefix = 'linux';
break;
case 'win32':
clangdExecutablePath = path.join(build, 'bin', 'clangd.exe');
lsSuffix = 'Windows_64bit.zip';
clangdPrefix = 'windows';
break;
}
if (!lsSuffix) {
shell.echo(
`The arduino-language-server is not available for ${platform} ${arch}.`
);
shell.exit(1);
}
let clangdExecutablePath, lsSuffix, clangdPrefix;
switch (platform) {
case 'darwin':
clangdExecutablePath = path.join(build, 'bin', 'clangd')
lsSuffix = 'macOS_amd64.zip';
clangdPrefix = 'mac';
break;
case 'linux':
clangdExecutablePath = path.join(build, 'bin', 'clangd')
lsSuffix = 'Linux_amd64.zip';
clangdPrefix = 'linux'
break;
case 'win32':
clangdExecutablePath = path.join(build, 'bin', 'clangd.exe')
lsSuffix = 'Windows_amd64.zip';
clangdPrefix = 'windows';
break;
}
if (!lsSuffix) {
shell.echo(`The arduino-language-server is not available for ${platform} ${arch}.`);
shell.exit(1);
}
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${lsSuffix}`;
downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force);
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`;
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, { strip: 1 }); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder.
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${
alsVersion === 'nightly'
? 'nightly/arduino-language-server'
: 'arduino-language-server_' + alsVersion
}_${lsSuffix}`;
downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force);
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`;
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, {
strip: 1,
}); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder.
})();

View File

@@ -1,20 +1,30 @@
import { inject, injectable, postConstruct } from 'inversify';
import * as React from 'react';
import { remote } from 'electron';
import {
BoardsService,
Port,
SketchesService,
ExecutableService,
Sketch,
LibraryService,
} from '../common/protocol';
import { Mutex } from 'async-mutex';
import {
MAIN_MENU_BAR,
MenuContribution,
MenuModelRegistry,
SelectionService,
ILogger,
DisposableCollection,
} from '@theia/core';
import {
ContextMenuRenderer,
FrontendApplication,
FrontendApplicationContribution,
OpenerService,
LocalStorageService,
StatusBar,
StatusBarAlignment,
} from '@theia/core/lib/browser';
import { nls } from '@theia/core/lib/common';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
@@ -33,7 +43,6 @@ import {
EditorManager,
EditorOpenerOptions,
} from '@theia/editor/lib/browser';
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
@@ -42,43 +51,27 @@ import { OutputContribution } from '@theia/output/lib/browser/output-contributio
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
import { inject, injectable, postConstruct } from 'inversify';
import * as React from 'react';
import { remote } from 'electron';
import { MainMenuManager } from '../common/main-menu-manager';
import {
BoardsService,
CoreService,
Port,
SketchesService,
ExecutableService,
Sketch,
} from '../common/protocol';
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { FileChangeType } from '@theia/filesystem/lib/browser';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { ConfigService } from '../common/protocol/config-service';
import { FileSystemExt } from '../common/protocol/filesystem-ext';
import { ArduinoCommands } from './arduino-commands';
import { BoardsConfig } from './boards/boards-config';
import { BoardsConfigDialog } from './boards/boards-config-dialog';
import { BoardsDataStore } from './boards/boards-data-store';
import { BoardsServiceProvider } from './boards/boards-service-provider';
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
import { EditorMode } from './editor-mode';
import { ArduinoMenus } from './menu/arduino-menus';
import { MonitorConnection } from './monitor/monitor-connection';
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
import { WorkspaceService } from './theia/workspace/workspace-service';
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { ResponseService } from '../common/protocol/response-service';
import { ArduinoPreferences } from './arduino-preferences';
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
import { SaveAsSketch } from './contributions/save-as-sketch';
import { FileChangeType } from '@theia/filesystem/lib/browser';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution';
const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages';
@injectable()
export class ArduinoFrontendContribution
implements
@@ -97,24 +90,15 @@ export class ArduinoFrontendContribution
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(LibraryService)
protected readonly libraryService: LibraryService;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(SelectionService)
protected readonly selectionService: SelectionService;
@inject(EditorManager)
protected readonly editorManager: EditorManager;
@inject(ContextMenuRenderer)
protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(FileDialogService)
protected readonly fileDialogService: FileDialogService;
@inject(FileService)
protected readonly fileService: FileService;
@@ -124,21 +108,12 @@ export class ArduinoFrontendContribution
@inject(BoardsConfigDialog)
protected readonly boardsConfigDialog: BoardsConfigDialog;
@inject(MenuModelRegistry)
protected readonly menuRegistry: MenuModelRegistry;
@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry;
@inject(StatusBar)
protected readonly statusBar: StatusBar;
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;
@inject(MonitorConnection)
protected readonly monitorConnection: MonitorConnection;
@inject(FileNavigatorContribution)
protected readonly fileNavigatorContributions: FileNavigatorContribution;
@@ -163,42 +138,26 @@ export class ArduinoFrontendContribution
@inject(EditorMode)
protected readonly editorMode: EditorMode;
@inject(ArduinoDaemon)
protected readonly daemon: ArduinoDaemon;
@inject(OpenerService)
protected readonly openerService: OpenerService;
@inject(ConfigService)
protected readonly configService: ConfigService;
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@inject(MainMenuManager)
protected readonly mainMenuManager: MainMenuManager;
@inject(FileSystemExt)
protected readonly fileSystemExt: FileSystemExt;
@inject(HostedPluginSupport)
protected hostedPluginSupport: HostedPluginSupport;
@inject(ExecutableService)
protected executableService: ExecutableService;
@inject(ResponseService)
protected readonly responseService: ResponseService;
@inject(ArduinoPreferences)
protected readonly arduinoPreferences: ArduinoPreferences;
@inject(SketchesServiceClientImpl)
protected readonly sketchServiceClient: SketchesServiceClientImpl;
@inject(FrontendApplicationStateService)
protected readonly appStateService: FrontendApplicationStateService;
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
protected invalidConfigPopup:
| Promise<void | 'No' | 'Yes' | undefined>
| undefined;
@@ -206,10 +165,34 @@ export class ArduinoFrontendContribution
@postConstruct()
protected async init(): Promise<void> {
const isFirstStartup = !(await this.localStorageService.getData(
INIT_LIBS_AND_PACKAGES
));
if (isFirstStartup) {
await this.localStorageService.setData(INIT_LIBS_AND_PACKAGES, true);
const avrPackage = await this.boardsService.getBoardPackage({
id: 'arduino:avr',
});
const builtInLibrary = (
await this.libraryService.search({
query: 'Arduino_BuiltIn',
})
)[0];
!!avrPackage && (await this.boardsService.install({ item: avrPackage }));
!!builtInLibrary &&
(await this.libraryService.install({
item: builtInLibrary,
installDependencies: true,
}));
}
if (!window.navigator.onLine) {
// tslint:disable-next-line:max-line-length
this.messageService.warn(
'You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.'
nls.localize(
'arduino/common/offlineIndicator',
'You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.'
)
);
}
const updateStatusBar = ({
@@ -220,15 +203,22 @@ export class ArduinoFrontendContribution
alignment: StatusBarAlignment.RIGHT,
text: selectedBoard
? `$(microchip) ${selectedBoard.name}`
: '$(close) no board selected',
: `$(close) ${nls.localize(
'arduino/common/noBoardSelected',
'No board selected'
)}`,
className: 'arduino-selected-board',
});
if (selectedBoard) {
this.statusBar.setElement('arduino-selected-port', {
alignment: StatusBarAlignment.RIGHT,
text: selectedPort
? `on ${Port.toString(selectedPort)}`
: '[not connected]',
? nls.localize(
'arduino/common/selectedOn',
'on {0}',
Port.toString(selectedPort)
)
: nls.localize('arduino/common/notConnected', '[not connected]'),
className: 'arduino-selected-port',
});
}
@@ -309,7 +299,7 @@ export class ArduinoFrontendContribution
webContents.setZoomLevel(event.newValue || 0);
}
});
app.shell.leftPanelHandler.removeMenu('settings-menu');
app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
}
onStop(): void {
@@ -365,15 +355,14 @@ export class ArduinoFrontendContribution
);
}
}
const { clangdUri, cliUri, lsUri } = await this.executableService.list();
const [clangdPath, cliPath, lsPath, cliConfigPath] = await Promise.all([
const { clangdUri, lsUri } = await this.executableService.list();
const [clangdPath, lsPath] = await Promise.all([
this.fileService.fsPath(new URI(clangdUri)),
this.fileService.fsPath(new URI(cliUri)),
this.fileService.fsPath(new URI(lsUri)),
this.fileService.fsPath(
new URI(await this.configService.getCliConfigFileUri())
),
]);
const config = await this.configService.getConfiguration();
this.languageServerFqbn = await Promise.race([
new Promise<undefined>((_, reject) =>
setTimeout(
@@ -385,10 +374,10 @@ export class ArduinoFrontendContribution
'arduino.languageserver.start',
{
lsPath,
cliPath,
cliDaemonAddr: `localhost:${config.daemon.port}`, // TODO: verify if this port is coming from the BE
clangdPath,
log: currentSketchPath ? currentSketchPath : log,
cliConfigPath,
cliDaemonInstance: '1',
board: {
fqbn,
name: name ? `"${name}"` : undefined,
@@ -421,7 +410,7 @@ export class ArduinoFrontendContribution
registry.registerItem({
id: 'toggle-serial-monitor',
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
tooltip: 'Serial Monitor',
tooltip: nls.localize('arduino/common/serialMonitor', 'Serial Monitor'),
});
}
@@ -456,12 +445,21 @@ export class ArduinoFrontendContribution
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL));
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW));
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
registry.registerSubmenu(
ArduinoMenus.SKETCH,
nls.localize('arduino/menu/sketch', 'Sketch')
);
registry.registerSubmenu(
ArduinoMenus.TOOLS,
nls.localize('arduino/menu/tools', 'Tools')
);
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
label: 'Optimize for Debugging',
order: '4',
label: nls.localize(
'arduino/debug/optimizeForDebugging',
'Optimize for Debugging'
),
order: '5',
});
}
@@ -472,13 +470,17 @@ export class ArduinoFrontendContribution
for (const uri of [mainFileUri, ...rootFolderFileUris]) {
await this.ensureOpened(uri);
}
await this.ensureOpened(mainFileUri, true);
if (mainFileUri.endsWith('.pde')) {
const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`;
const message = nls.localize(
'arduino/common/oldFormat',
"The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?",
sketch.name
);
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
this.messageService
.info(message, 'Later', 'Yes')
.info(message, nls.localize('arduino/common/later', 'Later'), yes)
.then(async (answer) => {
if (answer === 'Yes') {
if (answer === yes) {
this.commandRegistry.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{

View File

@@ -42,8 +42,8 @@ import { FileNavigatorContribution as TheiaFileNavigatorContribution } from '@th
import { KeymapsFrontendContribution } from './theia/keymaps/keymaps-frontend-contribution';
import { KeymapsFrontendContribution as TheiaKeymapsFrontendContribution } from '@theia/keymaps/lib/browser/keymaps-frontend-contribution';
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
import { EditorContribution as TheiaEditorContribution } from '@theia/editor/lib/browser/editor-contribution';
import { EditorContribution } from './theia/editor/editor-contribution';
import { EditorPreviewContribution as TheiaEditorPreviewContribution } from '@theia/editor-preview/lib/browser/editor-preview-contribution';
import { EditorPreviewContribution } from './theia/editor/editor-contribution';
import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
import { MonacoStatusBarContribution } from './theia/monaco/monaco-status-bar-contribution';
import {
@@ -69,20 +69,20 @@ import { ScmContribution } from './theia/scm/scm-contribution';
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution';
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
import { SerialServiceClientImpl } from './serial/serial-service-client-impl';
import {
MonitorServicePath,
MonitorService,
MonitorServiceClient,
} from '../common/protocol/monitor-service';
SerialServicePath,
SerialService,
SerialServiceClient,
} from '../common/protocol/serial-service';
import {
ConfigService,
ConfigServicePath,
} from '../common/protocol/config-service';
import { MonitorWidget } from './monitor/monitor-widget';
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
import { MonitorConnection } from './monitor/monitor-connection';
import { MonitorModel } from './monitor/monitor-model';
import { MonitorWidget } from './serial/monitor/monitor-widget';
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
import { SerialConnectionManager } from './serial/serial-connection-manager';
import { SerialModel } from './serial/serial-model';
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
import { TabBarDecoratorService } from './theia/core/tab-bar-decorator';
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser';
@@ -154,7 +154,7 @@ import {
} from '../common/protocol/examples-service';
import { BuiltInExamples, LibraryExamples } from './contributions/examples';
import { IncludeLibrary } from './contributions/include-library';
import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/browser/output-channel';
import { OutputChannelManager } from './theia/output/output-channel';
import {
OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl,
@@ -165,8 +165,9 @@ import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/mo
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
import { ResponseServiceImpl } from './response-service-impl';
import {
ResponseServicePath,
ResponseService,
ResponseServiceArduino,
ResponseServicePath,
} from '../common/protocol/response-service';
import { NotificationCenter } from './notification-center';
import {
@@ -189,12 +190,12 @@ import { BoardSelection } from './contributions/board-selection';
import { OpenRecentSketch } from './contributions/open-recent-sketch';
import { Help } from './contributions/help';
import { bindArduinoPreferences } from './arduino-preferences';
import { SettingsService } from './dialogs/settings/settings';
import {
SettingsService,
SettingsDialog,
SettingsWidget,
SettingsDialogProps,
} from './settings';
} from './dialogs/settings/settings-dialog';
import { AddFile } from './contributions/add-file';
import { ArchiveSketch } from './contributions/archive-sketch';
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
@@ -206,12 +207,12 @@ import { DebugConfigurationManager } from './theia/debug/debug-configuration-man
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-widget';
import { SearchInWorkspaceWidget } from './theia/search-in-workspace/search-in-workspace-widget';
import { SearchInWorkspaceFactory as TheiaSearchInWorkspaceFactory } from '@theia/search-in-workspace/lib/browser/search-in-workspace-factory';
import { SearchInWorkspaceFactory } from './theia/search-in-workspace/search-in-workspace-factory';
import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget';
import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget';
import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider';
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
import { DebugEditorModel } from './theia/debug/debug-editor-model';
import { DebugEditorModelFactory } from '@theia/debug/lib/browser/editor/debug-editor-model';
import { StorageWrapper } from './storage-wrapper';
import { NotificationManager } from './theia/messages/notifications-manager';
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
@@ -223,7 +224,7 @@ import { CloudSketchbookWidget } from './widgets/cloud-sketchbook/cloud-sketchbo
import { CloudSketchbookTreeWidget } from './widgets/cloud-sketchbook/cloud-sketchbook-tree-widget';
import { createCloudSketchbookTreeWidget } from './widgets/cloud-sketchbook/cloud-sketchbook-tree-container';
import { CreateApi } from './create/create-api';
import { ShareSketchDialog } from './dialogs.ts/cloud-share-sketch-dialog';
import { ShareSketchDialog } from './dialogs/cloud-share-sketch-dialog';
import { AuthenticationClientService } from './auth/authentication-client-service';
import {
AuthenticationService,
@@ -237,6 +238,30 @@ import { SketchbookWidget } from './widgets/sketchbook/sketchbook-widget';
import { SketchbookTreeWidget } from './widgets/sketchbook/sketchbook-tree-widget';
import { createSketchbookTreeWidget } from './widgets/sketchbook/sketchbook-tree-container';
import { SketchCache } from './widgets/cloud-sketchbook/cloud-sketch-cache';
import { UploadFirmware } from './contributions/upload-firmware';
import {
UploadFirmwareDialog,
UploadFirmwareDialogProps,
UploadFirmwareDialogWidget,
} from './dialogs/firmware-uploader/firmware-uploader-dialog';
import { UploadCertificate } from './contributions/upload-certificate';
import {
ArduinoFirmwareUploader,
ArduinoFirmwareUploaderPath,
} from '../common/protocol/arduino-firmware-uploader';
import {
UploadCertificateDialog,
UploadCertificateDialogProps,
UploadCertificateDialogWidget,
} from './dialogs/certificate-uploader/certificate-uploader-dialog';
import { PlotterFrontendContribution } from './serial/plotter/plotter-frontend-contribution';
import {
UserFieldsDialog,
UserFieldsDialogProps,
UserFieldsDialogWidget,
} from './dialogs/user-fields/user-fields-dialog';
import { nls } from '@theia/core/lib/common';
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -355,7 +380,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
bind(BoardsConfigDialog).toSelf().inSingletonScope();
bind(BoardsConfigDialogProps).toConstantValue({
title: 'Select Board',
title: nls.localize('arduino/common/selectBoard', 'Select Board'),
});
// Core service
@@ -369,8 +394,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
.inSingletonScope();
// Serial monitor
bind(MonitorModel).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(MonitorModel);
bind(SerialModel).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(SerialModel);
bind(MonitorWidget).toSelf();
bindViewContribution(bind, MonitorViewContribution);
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
@@ -378,28 +403,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
id: MonitorWidget.ID,
createWidget: () => context.container.get(MonitorWidget),
}));
// Frontend binding for the serial monitor service
bind(MonitorService)
// Frontend binding for the serial service
bind(SerialService)
.toDynamicValue((context) => {
const connection = context.container.get(WebSocketConnectionProvider);
const client = context.container.get(MonitorServiceClientImpl);
return connection.createProxy(MonitorServicePath, client);
})
.inSingletonScope();
bind(MonitorConnection).toSelf().inSingletonScope();
// Serial monitor service client to receive and delegate notifications from the backend.
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
bind(MonitorServiceClient)
.toDynamicValue((context) => {
const client = context.container.get(MonitorServiceClientImpl);
WebSocketConnectionProvider.createProxy(
context.container,
MonitorServicePath,
client
);
return client;
const client =
context.container.get<SerialServiceClient>(SerialServiceClient);
return connection.createProxy(SerialServicePath, client);
})
.inSingletonScope();
bind(SerialConnectionManager).toSelf().inSingletonScope();
// Serial service client to receive and delegate notifications from the backend.
bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope();
bind(WorkspaceService).toSelf().inSingletonScope();
rebind(TheiaWorkspaceService).toService(WorkspaceService);
@@ -423,7 +439,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(TheiaKeymapsFrontendContribution)
.to(KeymapsFrontendContribution)
.inSingletonScope();
rebind(TheiaEditorContribution).to(EditorContribution).inSingletonScope();
rebind(TheiaEditorPreviewContribution)
.to(EditorPreviewContribution)
.inSingletonScope();
rebind(TheiaMonacoStatusBarContribution)
.to(MonacoStatusBarContribution)
.inSingletonScope();
@@ -476,6 +494,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SearchInWorkspaceWidget).toSelf();
rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget);
// replace search icon
rebind(TheiaSearchInWorkspaceFactory)
.to(SearchInWorkspaceFactory)
.inSingletonScope();
rebind(TheiaSearchInWorkspaceResultTreeWidget).toDynamicValue(
({ container }) => {
const childContainer = createTreeContainer(container);
@@ -522,6 +546,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
)
.inSingletonScope();
bind(ArduinoFirmwareUploader)
.toDynamicValue((context) =>
WebSocketConnectionProvider.createProxy(
context.container,
ArduinoFirmwareUploaderPath
)
)
.inSingletonScope();
// File-system extension
bind(FileSystemExt)
.toDynamicValue((context) =>
@@ -571,12 +604,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, About);
Contribution.configure(bind, Debug);
Contribution.configure(bind, Sketchbook);
Contribution.configure(bind, UploadFirmware);
Contribution.configure(bind, UploadCertificate);
Contribution.configure(bind, BoardSelection);
Contribution.configure(bind, OpenRecentSketch);
Contribution.configure(bind, Help);
Contribution.configure(bind, AddFile);
Contribution.configure(bind, ArchiveSketch);
Contribution.configure(bind, AddZipLibrary);
Contribution.configure(bind, PlotterFrontendContribution);
bind(ResponseServiceImpl)
.toSelf()
@@ -589,7 +625,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
);
return responseService;
});
bind(ResponseService).toService(ResponseServiceImpl);
bind(ResponseServiceArduino).toService(ResponseServiceImpl);
bind(NotificationCenter).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(NotificationCenter);
@@ -639,16 +677,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(DebugConfigurationManager).toSelf().inSingletonScope();
rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager);
// Patch for the debug hover: https://github.com/eclipse-theia/theia/pull/9256/
rebind(DebugEditorModelFactory)
.toDynamicValue(
({ container }) =>
<DebugEditorModelFactory>(
((editor) => DebugEditorModel.createModel(container, editor))
)
)
.inSingletonScope();
// Preferences
bindArduinoPreferences(bind);
@@ -658,7 +686,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SettingsWidget).toSelf().inSingletonScope();
bind(SettingsDialog).toSelf().inSingletonScope();
bind(SettingsDialogProps).toConstantValue({
title: 'Preferences',
title: nls.localize(
'vscode/preferences.contribution/preferences',
'Preferences'
),
});
bind(StorageWrapper).toSelf().inSingletonScope();
@@ -713,4 +744,21 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
id: 'cloud-sketchbook-composite-widget',
createWidget: () => ctx.container.get(CloudSketchbookCompositeWidget),
}));
bind(UploadFirmwareDialogWidget).toSelf().inSingletonScope();
bind(UploadFirmwareDialog).toSelf().inSingletonScope();
bind(UploadFirmwareDialogProps).toConstantValue({
title: 'UploadFirmware',
});
bind(UploadCertificateDialogWidget).toSelf().inSingletonScope();
bind(UploadCertificateDialog).toSelf().inSingletonScope();
bind(UploadCertificateDialogProps).toConstantValue({
title: 'UploadCertificate',
});
bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
bind(UserFieldsDialog).toSelf().inSingletonScope();
bind(UserFieldsDialogProps).toConstantValue({
title: 'UserFields',
});
});

View File

@@ -6,6 +6,7 @@ import {
PreferenceContribution,
PreferenceSchema,
} from '@theia/core/lib/browser/preferences';
import { nls } from '@theia/core/lib/common';
import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol';
export const ArduinoConfigSchema: PreferenceSchema = {
@@ -13,24 +14,34 @@ export const ArduinoConfigSchema: PreferenceSchema = {
properties: {
'arduino.language.log': {
type: 'boolean',
description:
"True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.",
description: nls.localize(
'arduino/preferences/language.log',
"True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default."
),
default: false,
},
'arduino.compile.verbose': {
type: 'boolean',
description: 'True for verbose compile output. False by default',
description: nls.localize(
'arduino/preferences/compile.verbose',
'True for verbose compile output. False by default'
),
default: false,
},
'arduino.compile.warnings': {
enum: [...CompilerWarningLiterals],
description:
"Tells gcc which warning level to use. It's 'None' by default",
description: nls.localize(
'arduino/preferences/compile.warnings',
"Tells gcc which warning level to use. It's 'None' by default"
),
default: 'None',
},
'arduino.upload.verbose': {
type: 'boolean',
description: 'True for verbose upload output. False by default.',
description: nls.localize(
'arduino/preferences/upload.verbose',
'True for verbose upload output. False by default.'
),
default: false,
},
'arduino.upload.verify': {
@@ -39,76 +50,114 @@ export const ArduinoConfigSchema: PreferenceSchema = {
},
'arduino.window.autoScale': {
type: 'boolean',
description:
'True if the user interface automatically scales with the font size.',
description: nls.localize(
'arduino/preferences/window.autoScale',
'True if the user interface automatically scales with the font size.'
),
default: true,
},
'arduino.window.zoomLevel': {
type: 'number',
description:
'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.',
description: nls.localize(
'arduino/preferences/window.zoomLevel',
'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.'
),
default: 0,
},
'arduino.ide.autoUpdate': {
type: 'boolean',
description:
'True to enable automatic update checks. The IDE will check for updates automatically and periodically.',
description: nls.localize(
'arduino/preferences/ide.autoUpdate',
'True to enable automatic update checks. The IDE will check for updates automatically and periodically.'
),
default: true,
},
'arduino.board.certificates': {
type: 'string',
description: nls.localize(
'arduino/preferences/board.certificates',
'List of certificates that can be uploaded to boards'
),
default: '',
},
'arduino.sketchbook.showAllFiles': {
type: 'boolean',
description:
'True to show all sketch files inside the sketch. It is false by default.',
description: nls.localize(
'arduino/preferences/sketchbook.showAllFiles',
'True to show all sketch files inside the sketch. It is false by default.'
),
default: false,
},
'arduino.cloud.enabled': {
type: 'boolean',
description:
'True if the sketch sync functions are enabled. Defaults to true.',
description: nls.localize(
'arduino/preferences/cloud.enabled',
'True if the sketch sync functions are enabled. Defaults to true.'
),
default: true,
},
'arduino.cloud.pull.warn': {
type: 'boolean',
description:
'True if users should be warned before pulling a cloud sketch. Defaults to true.',
description: nls.localize(
'arduino/preferences/cloud.pull.warn',
'True if users should be warned before pulling a cloud sketch. Defaults to true.'
),
default: true,
},
'arduino.cloud.push.warn': {
type: 'boolean',
description:
'True if users should be warned before pushing a cloud sketch. Defaults to true.',
description: nls.localize(
'arduino/preferences/cloud.push.warn',
'True if users should be warned before pushing a cloud sketch. Defaults to true.'
),
default: true,
},
'arduino.cloud.pushpublic.warn': {
type: 'boolean',
description:
'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.',
description: nls.localize(
'arduino/preferences/cloud.pushpublic.warn',
'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.'
),
default: true,
},
'arduino.cloud.sketchSyncEnpoint': {
type: 'string',
description:
'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.',
description: nls.localize(
'arduino/preferences/cloud.sketchSyncEnpoint',
'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.'
),
default: 'https://api2.arduino.cc/create',
},
'arduino.auth.clientID': {
type: 'string',
description: 'The OAuth2 client ID.',
description: nls.localize(
'arduino/preferences/auth.clientID',
'The OAuth2 client ID.'
),
default: 'C34Ya6ex77jTNxyKWj01lCe1vAHIaPIo',
},
'arduino.auth.domain': {
type: 'string',
description: 'The OAuth2 domain.',
description: nls.localize(
'arduino/preferences/auth.domain',
'The OAuth2 domain.'
),
default: 'login.arduino.cc',
},
'arduino.auth.audience': {
type: 'string',
description: 'The 0Auth2 audience.',
description: nls.localize(
'arduino/preferences/auth.audience',
'The OAuth2 audience.'
),
default: 'https://api.arduino.cc',
},
'arduino.auth.registerUri': {
type: 'string',
description: 'The URI used to register a new user.',
description: nls.localize(
'arduino/preferences/auth.registerUri',
'The URI used to register a new user.'
),
default: 'https://auth.arduino.cc/login#/register',
},
},
@@ -123,6 +172,7 @@ export interface ArduinoConfiguration {
'arduino.window.autoScale': boolean;
'arduino.window.zoomLevel': number;
'arduino.ide.autoUpdate': boolean;
'arduino.board.certificates': string;
'arduino.sketchbook.showAllFiles': boolean;
'arduino.cloud.enabled': boolean;
'arduino.cloud.pull.warn': boolean;

View File

@@ -1,15 +1,21 @@
import { Command } from '@theia/core/lib/common/command';
export namespace CloudUserCommands {
export const LOGIN: Command = {
id: 'arduino-cloud--login',
label: 'Sign in',
};
export const LOGIN = Command.toLocalizedCommand(
{
id: 'arduino-cloud--login',
label: 'Sign in',
},
'arduino/cloud/signIn'
);
export const LOGOUT: Command = {
id: 'arduino-cloud--logout',
label: 'Sign Out',
};
export const LOGOUT = Command.toLocalizedCommand(
{
id: 'arduino-cloud--logout',
label: 'Sign Out',
},
'arduino/cloud/signOut'
);
export const OPEN_PROFILE_CONTEXT_MENU: Command = {
id: 'arduino-cloud-sketchbook--open-profile-menu',

View File

@@ -7,10 +7,10 @@ import {
Board,
} from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { BoardsConfig } from './boards-config';
import { Installable } from '../../common/protocol';
import { ResponseServiceImpl } from '../response-service-impl';
import { Installable, ResponseServiceArduino } from '../../common/protocol';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { nls } from '@theia/core/lib/common';
/**
* Listens on `BoardsConfig.Config` changes, if a board is selected which does not
@@ -27,8 +27,8 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;
@inject(ResponseServiceArduino)
protected readonly responseService: ResponseServiceArduino;
@inject(BoardsListWidgetFrontendContribution)
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
@@ -44,9 +44,10 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
}
protected ensureCoreExists(config: BoardsConfig.Config): void {
const { selectedBoard } = config;
const { selectedBoard, selectedPort } = config;
if (
selectedBoard &&
selectedPort &&
!this.notifications.find((board) => Board.sameAs(board, selectedBoard))
) {
this.notifications.push(selectedBoard);
@@ -81,12 +82,23 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
const version = candidate.availableVersions[0]
? `[v ${candidate.availableVersions[0]}]`
: '';
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
const manualInstall = nls.localize(
'arduino/board/installManually',
'Install Manually'
);
// tslint:disable-next-line:max-line-length
this.messageService
.info(
`The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`,
'Install Manually',
'Yes'
nls.localize(
'arduino/board/installNow',
'The "{0} {1}" core has to be installed for the currently selected "{2}" board. Do you want to install it now?',
candidate.name,
version,
selectedBoard.name
),
manualInstall,
yes
)
.then(async (answer) => {
const index = this.notifications.findIndex((board) =>
@@ -95,7 +107,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
if (index !== -1) {
this.notifications.splice(index, 1);
}
if (answer === 'Yes') {
if (answer === yes) {
await Installable.installWithProgress({
installable: this.boardsService,
item: candidate,
@@ -105,7 +117,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
});
return;
}
if (answer) {
if (answer === manualInstall) {
this.boardsManagerFrontendContribution
.openView({ reveal: true })
.then((widget) =>

View File

@@ -1,15 +1,12 @@
import { injectable, inject, postConstruct } from 'inversify';
import { Message } from '@phosphor/messaging';
import {
AbstractDialog,
DialogProps,
Widget,
DialogError,
} from '@theia/core/lib/browser';
import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
import { AbstractDialog } from '../theia/dialogs/dialogs';
import { BoardsConfig } from './boards-config';
import { BoardsService } from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
import { nls } from '@theia/core/lib/common';
@injectable()
export class BoardsConfigDialogProps extends DialogProps {}
@@ -36,8 +33,10 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
this.contentNode.classList.add('select-board-dialog');
this.contentNode.appendChild(this.createDescription());
this.appendCloseButton('CANCEL');
this.appendAcceptButton('OK');
this.appendCloseButton(
nls.localize('vscode/issueMainService/cancel', 'Cancel')
);
this.appendAcceptButton(nls.localize('vscode/issueMainService/ok', 'OK'));
}
@postConstruct()
@@ -67,7 +66,10 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
head.classList.add('head');
const title = document.createElement('div');
title.textContent = 'Select Other Board & Port';
title.textContent = nls.localize(
'arduino/board/configDialogTitle',
'Select Other Board & Port'
);
title.classList.add('title');
head.appendChild(title);
@@ -76,8 +78,14 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
head.appendChild(text);
for (const paragraph of [
'Select both a Board and a Port if you want to upload a sketch.',
'If you only select a Board you will be able just to compile, but not to upload your sketch.',
nls.localize(
'arduino/board/configDialog1',
'Select both a Board and a Port if you want to upload a sketch.'
),
nls.localize(
'arduino/board/configDialog2',
'If you only select a Board you will be able just to compile, but not to upload your sketch.'
),
]) {
const p = document.createElement('div');
p.textContent = paragraph;
@@ -121,7 +129,10 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
protected isValid(value: BoardsConfig.Config): DialogError {
if (!value.selectedBoard) {
if (value.selectedPort) {
return 'Please pick a board connected to the port you have selected.';
return nls.localize(
'arduino/board/pleasePickBoard',
'Please pick a board connected to the port you have selected.'
);
}
return false;
}

View File

@@ -10,7 +10,12 @@ import {
BoardWithPackage,
} from '../../common/protocol/boards-service';
import { NotificationCenter } from '../notification-center';
import { BoardsServiceProvider } from './boards-service-provider';
import {
AvailableBoard,
BoardsServiceProvider,
} from './boards-service-provider';
import { naturalCompare } from '../../common/utils';
import { nls } from '@theia/core/lib/common';
export namespace BoardsConfig {
export interface Config {
@@ -183,11 +188,50 @@ export class BoardsConfig extends React.Component<
.filter(notEmpty);
}
protected get availableBoards(): AvailableBoard[] {
return this.props.boardsServiceProvider.availableBoards;
}
protected queryPorts = async (
availablePorts: MaybePromise<Port[]> = this.availablePorts
) => {
const ports = await availablePorts;
return { knownPorts: ports.sort(Port.compare) };
// Available ports must be sorted in this order:
// 1. Serial with recognized boards
// 2. Serial with guessed boards
// 3. Serial with incomplete boards
// 4. Network with recognized boards
// 5. Other protocols with recognized boards
const ports = (await availablePorts).sort((left: Port, right: Port) => {
if (left.protocol === 'serial' && right.protocol !== 'serial') {
return -1;
} else if (left.protocol !== 'serial' && right.protocol === 'serial') {
return 1;
} else if (left.protocol === 'network' && right.protocol !== 'network') {
return -1;
} else if (left.protocol !== 'network' && right.protocol === 'network') {
return 1;
} else if (left.protocol === right.protocol) {
// We show ports, including those that have guessed
// or unrecognized boards, so we must sort those too.
const leftBoard = this.availableBoards.find((board) =>
Port.sameAs(board.port, left)
);
const rightBoard = this.availableBoards.find((board) =>
Port.sameAs(board.port, right)
);
if (leftBoard && !rightBoard) {
return -1;
} else if (!leftBoard && rightBoard) {
return 1;
} else if (leftBoard?.state! < rightBoard?.state!) {
return -1;
} else if (leftBoard?.state! > rightBoard?.state!) {
return 1;
}
}
return naturalCompare(left.address, right.address);
});
return { knownPorts: ports };
};
protected toggleFilterPorts = () => {
@@ -265,7 +309,7 @@ export class BoardsConfig extends React.Component<
<div className="boards list">
{Array.from(distinctBoards.values()).map((board) => (
<Item<BoardWithPackage>
key={`${board.name}-${board.packageName}`}
key={toKey(board)}
item={board}
label={board.name}
details={board.details}
@@ -280,8 +324,24 @@ export class BoardsConfig extends React.Component<
}
protected renderPorts(): React.ReactNode {
const filter = this.state.showAllPorts ? () => true : Port.isBoardPort;
const ports = this.state.knownPorts.filter(filter);
let ports = [] as Port[];
if (this.state.showAllPorts) {
ports = this.state.knownPorts;
} else {
ports = this.state.knownPorts.filter((port) => {
if (port.protocol === 'serial') {
return true;
}
// All other ports with different protocol are
// only shown if there is a recognized board
// connected
for (const board of this.availableBoards) {
if (board.port?.address === port.address) {
return true;
}
}
});
}
return !ports.length ? (
<div className="loading noselect">No ports discovered</div>
) : (
@@ -302,7 +362,12 @@ export class BoardsConfig extends React.Component<
protected renderPortsFooter(): React.ReactNode {
return (
<div className="noselect">
<label title="Shows all available ports when enabled">
<label
title={nls.localize(
'arduino/board/showAllAvailablePorts',
'Shows all available ports when enabled'
)}
>
<input
type="checkbox"
defaultChecked={this.state.showAllPorts}

View File

@@ -12,6 +12,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { BoardsDataStore } from './boards-data-store';
import { MainMenuManager } from '../../common/main-menu-manager';
import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
@injectable()
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
@@ -115,9 +116,13 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP,
'z02_programmers',
];
const programmerNls = nls.localize(
'arduino/board/programmer',
'Programmer'
);
const label = selectedProgrammer
? `Programmer: "${selectedProgrammer.name}"`
: 'Programmer';
? `${programmerNls}: "${selectedProgrammer.name}"`
: programmerNls;
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
this.toDisposeOnBoardChange.push(
Disposable.create(() =>

View File

@@ -5,11 +5,12 @@ import {
} from '../../common/protocol/boards-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
@injectable()
export class BoardsListWidget extends ListWidget<BoardsPackage> {
static WIDGET_ID = 'boards-list-widget';
static WIDGET_LABEL = 'Boards Manager';
static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager');
constructor(
@inject(BoardsService) protected service: BoardsService,
@@ -19,7 +20,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
super({
id: BoardsListWidget.WIDGET_ID,
label: BoardsListWidget.WIDGET_LABEL,
iconClass: 'fa fa-microchip',
iconClass: 'fa fa-arduino-boards',
searchable: service,
installable: service,
itemLabel: (item: BoardsPackage) => item.name,
@@ -52,7 +53,12 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
}): Promise<void> {
await super.install({ item, progressId, version });
this.messageService.info(
`Successfully installed platform ${item.name}:${version}`,
nls.localize(
'arduino/board/succesfullyInstalledPlatform',
'Successfully installed platform {0}:{1}',
item.name,
version
),
{ timeout: 3000 }
);
}
@@ -66,7 +72,12 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
}): Promise<void> {
await super.uninstall({ item, progressId });
this.messageService.info(
`Successfully uninstalled platform ${item.name}:${item.installedVersion}`,
nls.localize(
'arduino/board/succesfullyUninstalledPlatform',
'Successfully uninstalled platform {0}:{1}',
item.name,
item.installedVersion!
),
{ timeout: 3000 }
);
}

View File

@@ -12,12 +12,14 @@ import {
BoardsPackage,
AttachedBoardsChangeEvent,
BoardWithPackage,
BoardUserField,
} from '../../common/protocol';
import { BoardsConfig } from './boards-config';
import { naturalCompare } from '../../common/utils';
import { NotificationCenter } from '../notification-center';
import { ArduinoCommands } from '../arduino-commands';
import { StorageWrapper } from '../storage-wrapper';
import { nls } from '@theia/core/lib/common';
@injectable()
export class BoardsServiceProvider implements FrontendApplicationContribution {
@@ -41,6 +43,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
protected readonly onAvailableBoardsChangedEmitter = new Emitter<
AvailableBoard[]
>();
protected readonly onAvailablePortsChangedEmitter = new Emitter<Port[]>();
/**
* Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it.
@@ -63,11 +66,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
* This even also fires, when the boards package was not available for the currently selected board,
* and the user installs the board package. Note: installing a board package will set the `fqbn` of the
* currently selected board.\
* This even also emitted when the board package for the currently selected board was uninstalled.
* This event is also emitted when the board package for the currently selected board was uninstalled.
*/
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
readonly onAvailableBoardsChanged =
this.onAvailableBoardsChangedEmitter.event;
readonly onAvailablePortsChanged = this.onAvailablePortsChangedEmitter.event;
onStart(): void {
this.notificationCenter.onAttachedBoardsChanged(
@@ -87,6 +91,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
]).then(([attachedBoards, availablePorts]) => {
this._attachedBoards = attachedBoards;
this._availablePorts = availablePorts;
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
this.reconcileAvailableBoards().then(() => this.tryReconnect());
});
}
@@ -101,6 +106,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
this._attachedBoards = event.newState.boards;
this._availablePorts = event.newState.ports;
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
this.reconcileAvailableBoards().then(() => this.tryReconnect());
}
@@ -134,14 +140,20 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
selectedBoard.packageId === event.item.id &&
!installedBoard
) {
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
this.messageService
.warn(
`Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`,
'Reselect later',
'Yes'
nls.localize(
'arduino/board/couldNotFindPreviouslySelected',
"Could not find previously selected board '{0}' in installed platform '{1}'. Please manually reselect the board you want to use. Do you want to reselect it now?",
selectedBoard.name,
event.item.name
),
nls.localize('arduino/board/reselectLater', 'Reselect later'),
yes
)
.then(async (answer) => {
if (answer === 'Yes') {
if (answer === yes) {
this.commandService.executeCommand(
ArduinoCommands.OPEN_BOARDS_DIALOG.id,
selectedBoard.name
@@ -264,6 +276,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
return boards;
}
async selectedBoardUserFields(): Promise<BoardUserField[]> {
if (!this._boardsConfig.selectedBoard || !this._boardsConfig.selectedPort) {
return [];
}
const fqbn = this._boardsConfig.selectedBoard.fqbn;
if (!fqbn) {
return [];
}
const protocol = this._boardsConfig.selectedPort.protocol;
return await this.boardsService.getBoardUserFields({ fqbn, protocol });
}
/**
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
*/
@@ -277,9 +301,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
if (!config.selectedBoard) {
if (!options.silent) {
this.messageService.warn('No boards selected.', {
timeout: 3000,
});
this.messageService.warn(
nls.localize('arduino/board/noneSelected', 'No boards selected.'),
{
timeout: 3000,
}
);
}
return false;
}
@@ -301,9 +328,16 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const { name } = config.selectedBoard;
if (!config.selectedPort) {
if (!options.silent) {
this.messageService.warn(`No ports selected for board: '${name}'.`, {
timeout: 3000,
});
this.messageService.warn(
nls.localize(
'arduino/board/noPortsSelected',
"No ports selected for board: '{0}'.",
name
),
{
timeout: 3000,
}
);
}
return false;
}
@@ -311,7 +345,11 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
if (!config.selectedBoard.fqbn) {
if (!options.silent) {
this.messageService.warn(
`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`,
nls.localize(
'arduino/board/noFQBN',
'The FQBN is not available for the selected board "{0}". Do you have the corresponding core installed?',
name
),
{ timeout: 3000 }
);
}
@@ -363,7 +401,6 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
protected async reconcileAvailableBoards(): Promise<void> {
const attachedBoards = this._attachedBoards;
const availablePorts = this._availablePorts;
// Unset the port on the user's config, if it is not available anymore.
if (
@@ -381,51 +418,73 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const boardsConfig = this.boardsConfig;
const currentAvailableBoards = this._availableBoards;
const availableBoards: AvailableBoard[] = [];
const availableBoardPorts = availablePorts.filter(Port.isBoardPort);
const attachedSerialBoards = attachedBoards.filter(({ port }) => !!port);
const attachedBoards = this._attachedBoards.filter(({ port }) => !!port);
const availableBoardPorts = availablePorts.filter((port) => {
if (port.protocol === 'serial') {
// We always show all serial ports, even if there
// is no recognized board connected to it
return true;
}
for (const boardPort of availableBoardPorts) {
let state = AvailableBoard.State.incomplete; // Initial pessimism.
let board = attachedSerialBoards.find(({ port }) =>
Port.sameAs(boardPort, port)
);
if (board) {
state = AvailableBoard.State.recognized;
} else {
// If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623
// We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
boardPort
);
if (lastSelectedBoard) {
board = {
...lastSelectedBoard,
port: boardPort,
};
state = AvailableBoard.State.guessed;
// All other ports with different protocol are
// only shown if there is a recognized board
// connected
for (const board of attachedBoards) {
if (board.port?.address === port.address) {
return true;
}
}
if (!board) {
availableBoards.push({
name: 'Unknown',
port: boardPort,
state,
});
} else {
const selected = BoardsConfig.Config.sameAs(boardsConfig, board);
availableBoards.push({
return false;
});
for (const boardPort of availableBoardPorts) {
const board = attachedBoards.find(({ port }) =>
Port.sameAs(boardPort, port)
);
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
boardPort
);
let availableBoard = {} as AvailableBoard;
if (board) {
availableBoard = {
...board,
state,
selected,
state: AvailableBoard.State.recognized,
selected: BoardsConfig.Config.sameAs(boardsConfig, board),
port: boardPort,
});
};
} else if (lastSelectedBoard) {
// If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623
// We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836
availableBoard = {
...lastSelectedBoard,
state: AvailableBoard.State.guessed,
selected: BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard),
port: boardPort,
};
} else {
availableBoard = {
name: nls.localize('arduino/common/unknown', 'Unknown'),
port: boardPort,
state: AvailableBoard.State.incomplete,
};
}
availableBoards.push(availableBoard);
}
if (
boardsConfig.selectedBoard &&
!availableBoards.some(({ selected }) => selected)
) {
// If the selected board has the same port of an unknown board
// that is already in availableBoards we might get a duplicate port.
// So we remove the one already in the array and add the selected one.
const found = availableBoards.findIndex(
(board) => board.port?.address === boardsConfig.selectedPort?.address
);
if (found >= 0) {
availableBoards.splice(found, 1);
}
availableBoards.push({
...boardsConfig.selectedBoard,
port: boardsConfig.selectedPort,
@@ -434,28 +493,24 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
});
}
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.compare);
let hasChanged =
sortedAvailableBoards.length !== currentAvailableBoards.length;
for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) {
availableBoards.sort(AvailableBoard.compare);
let hasChanged = availableBoards.length !== currentAvailableBoards.length;
for (let i = 0; !hasChanged && i < availableBoards.length; i++) {
const [left, right] = [availableBoards[i], currentAvailableBoards[i]];
hasChanged =
AvailableBoard.compare(
sortedAvailableBoards[i],
currentAvailableBoards[i]
) !== 0;
!!AvailableBoard.compare(left, right) ||
left.selected !== right.selected;
}
if (hasChanged) {
this._availableBoards = sortedAvailableBoards;
this._availableBoards = availableBoards;
this.onAvailableBoardsChangedEmitter.fire(this._availableBoards);
}
}
protected async getLastSelectedBoardOnPort(
port: Port | string | undefined
port: Port
): Promise<Board | undefined> {
if (!port) {
return undefined;
}
const key = this.getLastSelectedBoardOnPortKey(port);
return this.getData<Board>(key);
}
@@ -564,35 +619,39 @@ export namespace AvailableBoard {
return !!board.port;
}
// Available boards must be sorted in this order:
// 1. Serial with recognized boards
// 2. Serial with guessed boards
// 3. Serial with incomplete boards
// 4. Network with recognized boards
// 5. Other protocols with recognized boards
export const compare = (left: AvailableBoard, right: AvailableBoard) => {
if (left.selected && !right.selected) {
if (left.port?.protocol === 'serial' && right.port?.protocol !== 'serial') {
return -1;
}
if (right.selected && !left.selected) {
} else if (
left.port?.protocol !== 'serial' &&
right.port?.protocol === 'serial'
) {
return 1;
}
let result = naturalCompare(left.name, right.name);
if (result !== 0) {
return result;
}
if (left.fqbn && right.fqbn) {
result = naturalCompare(left.fqbn, right.fqbn);
if (result !== 0) {
return result;
} else if (
left.port?.protocol === 'network' &&
right.port?.protocol !== 'network'
) {
return -1;
} else if (
left.port?.protocol !== 'network' &&
right.port?.protocol === 'network'
) {
return 1;
} else if (left.port?.protocol === right.port?.protocol) {
// We show all ports, including those that have guessed
// or unrecognized boards, so we must sort those too.
if (left.state < right.state) {
return -1;
} else if (left.state > right.state) {
return 1;
}
}
if (left.port && right.port) {
result = Port.compare(left.port, right.port);
if (result !== 0) {
return result;
}
}
if (!!left.selected && !right.selected) {
return -1;
}
if (!!right.selected && !left.selected) {
return 1;
}
return left.state - right.state;
return naturalCompare(left.port?.address!, right.port?.address!);
};
}

View File

@@ -9,6 +9,7 @@ import {
BoardsServiceProvider,
AvailableBoard,
} from './boards-service-provider';
import { nls } from '@theia/core/lib/common';
export interface BoardsDropDownListCoords {
readonly top: number;
@@ -49,6 +50,10 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
if (coords === 'hidden') {
return '';
}
const footerLabel = nls.localize(
'arduino/board/openBoardsConfig',
'Select other board and port…'
);
return (
<div
className="arduino-boards-dropdown-list"
@@ -57,17 +62,25 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
...coords,
}}
>
{this.renderItem({
label: 'Select Other Board & Port',
onClick: () => this.props.openBoardsConfig(),
})}
{items
.map(({ name, port, selected, onClick }) => ({
label: `${name} at ${Port.toString(port)}`,
label: nls.localize(
'arduino/board/boardListItem',
'{0} at {1}',
name,
Port.toString(port)
),
selected,
onClick,
}))
.map(this.renderItem)}
<div
key={footerLabel}
className="arduino-boards-dropdown-item arduino-board-dropdown-footer"
onClick={() => this.props.openBoardsConfig()}
>
<div>{footerLabel}</div>
</div>
</div>
);
}
@@ -152,7 +165,10 @@ export class BoardsToolBarItem extends React.Component<
const { coords, availableBoards } = this.state;
const boardsConfig = this.props.boardsServiceClient.boardsConfig;
const title = BoardsConfig.Config.toString(boardsConfig, {
default: 'no board selected',
default: nls.localize(
'arduino/common/noBoardSelected',
'No board selected'
),
});
const decorator = (() => {
const selectedBoard = availableBoards.find(({ selected }) => selected);

View File

@@ -12,6 +12,7 @@ import {
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ConfigService } from '../../common/protocol';
import { nls } from '@theia/core/lib/common';
@injectable()
export class About extends Contribution {
@@ -30,7 +31,11 @@ export class About extends Contribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, {
commandId: About.Commands.ABOUT_APP.id,
label: `About ${this.applicationName}`,
label: nls.localize(
'arduino/about/label',
'About {0}',
this.applicationName
),
order: '0',
});
}
@@ -42,16 +47,24 @@ export class About extends Contribution {
status: cliStatus,
} = await this.configService.getVersion();
const buildDate = this.buildDate;
const detail = (showAll: boolean) => `Version: ${remote.app.getVersion()}
Date: ${buildDate ? buildDate : 'dev build'}${
buildDate && showAll ? ` (${this.ago(buildDate)})` : ''
}
CLI Version: ${version}${cliStatus ? ` ${cliStatus}` : ''} [${commit}]
${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
`;
const ok = 'OK';
const copy = 'Copy';
const detail = (showAll: boolean) =>
nls.localize(
'arduino/about/detail',
'Version: {0}\nDate: {1}{2}\nCLI Version: {3}{4} [{5}]\n\n{6}',
remote.app.getVersion(),
buildDate ? buildDate : nls.localize('', 'dev build'),
buildDate && showAll ? ` (${this.ago(buildDate)})` : '',
version,
cliStatus ? ` ${cliStatus}` : '',
commit,
nls.localize(
'arduino/about/copyright',
'Copyright © {0} Arduino SA',
new Date().getFullYear().toString()
)
);
const ok = nls.localize('vscode/issueMainService/ok', 'OK');
const copy = nls.localize('vscode/textInputActions/copy', 'Copy');
const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy];
const { response } = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
@@ -85,26 +98,86 @@ ${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
const other = moment(isoTime);
let result = now.diff(other, 'minute');
if (result < 60) {
return result === 1 ? `${result} minute ago` : `${result} minute ago`;
return result === 1
? nls.localize(
'vscode/date/date.fromNow.minutes.singular.ago',
'{0} minute ago',
result.toString()
)
: nls.localize(
'vscode/date/date.fromNow.minutes.plural.ago',
'{0} minutes ago',
result.toString()
);
}
result = now.diff(other, 'hour');
if (result < 25) {
return result === 1 ? `${result} hour ago` : `${result} hours ago`;
return result === 1
? nls.localize(
'vscode/date/date.fromNow.hours.singular.ago',
'{0} hour ago',
result.toString()
)
: nls.localize(
'vscode/date/date.fromNow.hours.plural.ago',
'{0} hours ago',
result.toString()
);
}
result = now.diff(other, 'day');
if (result < 8) {
return result === 1 ? `${result} day ago` : `${result} days ago`;
return result === 1
? nls.localize(
'vscode/date/date.fromNow.days.singular.ago',
'{0} day ago',
result.toString()
)
: nls.localize(
'vscode/date/date.fromNow.days.plural.ago',
'{0} days ago',
result.toString()
);
}
result = now.diff(other, 'week');
if (result < 5) {
return result === 1 ? `${result} week ago` : `${result} weeks ago`;
return result === 1
? nls.localize(
'vscode/date/date.fromNow.weeks.singular.ago',
'{0} week ago',
result.toString()
)
: nls.localize(
'vscode/date/date.fromNow.weeks.plural.ago',
'{0} weeks ago',
result.toString()
);
}
result = now.diff(other, 'month');
if (result < 13) {
return result === 1 ? `${result} month ago` : `${result} months ago`;
return result === 1
? nls.localize(
'vscode/date/date.fromNow.months.singular.ago',
'{0} month ago',
result.toString()
)
: nls.localize(
'vscode/date/date.fromNow.months.plural.ago',
'{0} months ago',
result.toString()
);
}
result = now.diff(other, 'year');
return result === 1 ? `${result} year ago` : `${result} years ago`;
return result === 1
? nls.localize(
'vscode/date/date.fromNow.years.singular.ago',
'{0} year ago',
result.toString()
)
: nls.localize(
'vscode/date/date.fromNow.years.plural.ago',
'{0} years ago',
result.toString()
);
}
}

View File

@@ -9,6 +9,7 @@ import {
URI,
} from './contribution';
import { FileDialogService } from '@theia/filesystem/lib/browser';
import { nls } from '@theia/core/lib/common';
@injectable()
export class AddFile extends SketchContribution {
@@ -24,7 +25,7 @@ export class AddFile extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
commandId: AddFile.Commands.ADD_FILE.id,
label: 'Add File...',
label: nls.localize('arduino/contributions/addFile', 'Add File') + '...',
order: '2',
});
}
@@ -35,7 +36,7 @@ export class AddFile extends SketchContribution {
return;
}
const toAddUri = await this.fileDialogService.showOpenDialog({
title: 'Add File',
title: nls.localize('arduino/contributions/addFile', 'Add File'),
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
@@ -50,9 +51,16 @@ export class AddFile extends SketchContribution {
if (exists) {
const { response } = await remote.dialog.showMessageBox({
type: 'question',
title: 'Replace',
buttons: ['Cancel', 'OK'],
message: `Replace the existing version of ${filename}?`,
title: nls.localize('arduino/contributions/replaceTitle', 'Replace'),
buttons: [
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
nls.localize('vscode/issueMainService/ok', 'OK'),
],
message: nls.localize(
'arduino/replaceMsg',
'Replace the existing version of {0}?',
filename
),
});
if (response === 0) {
// Cancel
@@ -60,9 +68,15 @@ export class AddFile extends SketchContribution {
}
}
await this.fileService.copy(toAddUri, targetUri, { overwrite: true });
this.messageService.info('One file added to the sketch.', {
timeout: 2000,
});
this.messageService.info(
nls.localize(
'arduino/contributions/fileAdded',
'One file added to the sketch.'
),
{
timeout: 2000,
}
);
}
}

View File

@@ -4,22 +4,26 @@ import URI from '@theia/core/lib/common/uri';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ResponseServiceImpl } from '../response-service-impl';
import { Installable, LibraryService } from '../../common/protocol';
import {
Installable,
LibraryService,
ResponseServiceArduino,
} from '../../common/protocol';
import {
SketchContribution,
Command,
CommandRegistry,
MenuModelRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class AddZipLibrary extends SketchContribution {
@inject(EnvVariablesServer)
protected readonly envVariableServer: EnvVariablesServer;
@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;
@inject(ResponseServiceArduino)
protected readonly responseService: ResponseServiceArduino;
@inject(LibraryService)
protected readonly libraryService: LibraryService;
@@ -41,7 +45,7 @@ export class AddZipLibrary extends SketchContribution {
});
registry.registerMenuAction([...includeLibMenuPath, '1_install'], {
commandId: AddZipLibrary.Commands.ADD_ZIP_LIBRARY.id,
label: 'Add .ZIP Library...',
label: nls.localize('arduino/library/addZip', 'Add .ZIP Library...'),
order: '1',
});
}
@@ -50,12 +54,15 @@ export class AddZipLibrary extends SketchContribution {
const homeUri = await this.envVariableServer.getHomeDirUri();
const defaultPath = await this.fileService.fsPath(new URI(homeUri));
const { canceled, filePaths } = await remote.dialog.showOpenDialog({
title: "Select a zip file containing the library you'd like to add",
title: nls.localize(
'arduino/selectZip',
"Select a zip file containing the library you'd like to add"
),
defaultPath,
properties: ['openFile'],
filters: [
{
name: 'Library',
name: nls.localize('arduino/library/zipLibrary', 'Library'),
extensions: ['zip'],
},
],
@@ -68,9 +75,12 @@ export class AddZipLibrary extends SketchContribution {
if (error instanceof AlreadyInstalledError) {
const result = await new ConfirmDialog({
msg: error.message,
title: 'Do you want to overwrite the existing library?',
ok: 'Yes',
cancel: 'No',
title: nls.localize(
'arduino/library/overwriteExistingLibrary',
'Do you want to overwrite the existing library?'
),
ok: nls.localize('vscode/extensionsUtils/yes', 'Yes'),
cancel: nls.localize('vscode/extensionsUtils/no', 'No'),
}).open();
if (result) {
await this.doInstall(zipUri, true);
@@ -84,14 +94,18 @@ export class AddZipLibrary extends SketchContribution {
try {
await Installable.doWithProgress({
messageService: this.messageService,
progressText: `Processing ${new URI(zipUri).path.base}`,
progressText:
nls.localize('arduino/common/processing', 'Processing') +
` ${new URI(zipUri).path.base}`,
responseService: this.responseService,
run: () => this.libraryService.installZip({ zipUri, overwrite }),
});
this.messageService.info(
`Successfully installed library from ${
nls.localize(
'arduino/library/successfullyInstalledZipLibrary',
'Successfully installed library from {0} archive',
new URI(zipUri).path.base
} archive`,
),
{ timeout: 3000 }
);
} catch (error) {
@@ -101,12 +115,19 @@ export class AddZipLibrary extends SketchContribution {
const name = match[1].trim();
if (name) {
throw new AlreadyInstalledError(
`A library folder named ${name} already exists. Do you want to overwrite it?`,
nls.localize(
'arduino/library/namedLibraryAlreadyExists',
'A library folder named {0} already exists. Do you want to overwrite it?',
name
),
name
);
} else {
throw new AlreadyInstalledError(
'A library already exists. Do you want to overwrite it?'
nls.localize(
'arduino/library/libraryAlreadyExists',
'A library already exists. Do you want to overwrite it?'
)
);
}
}

View File

@@ -9,6 +9,7 @@ import {
CommandRegistry,
MenuModelRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class ArchiveSketch extends SketchContribution {
@@ -21,7 +22,7 @@ export class ArchiveSketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id,
label: 'Archive Sketch',
label: nls.localize('arduino/sketch/archiveSketch', 'Archive Sketch'),
order: '1',
});
}
@@ -42,7 +43,10 @@ export class ArchiveSketch extends SketchContribution {
new URI(config.sketchDirUri).resolve(archiveBasename)
);
const { filePath, canceled } = await remote.dialog.showSaveDialog({
title: 'Save sketch folder as...',
title: nls.localize(
'arduino/sketch/saveSketchAs',
'Save sketch folder as...'
),
defaultPath,
});
if (!filePath || canceled) {
@@ -53,9 +57,16 @@ export class ArchiveSketch extends SketchContribution {
return;
}
await this.sketchService.archive(sketch, destinationUri.toString());
this.messageService.info(`Created archive '${archiveBasename}'.`, {
timeout: 2000,
});
this.messageService.info(
nls.localize(
'arduino/sketch/createdArchive',
"Created archive '{0}'.",
archiveBasename
),
{
timeout: 2000,
}
);
}
}

View File

@@ -23,6 +23,7 @@ import {
Port,
} from '../../common/protocol';
import { SketchContribution, Command, CommandRegistry } from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class BoardSelection extends SketchContribution {
@@ -53,19 +54,29 @@ export class BoardSelection extends SketchContribution {
this.boardsServiceProvider.boardsConfig;
if (!selectedBoard) {
this.messageService.info(
'Please select a board to obtain board info.'
nls.localize(
'arduino/board/selectBoardForInfo',
'Please select a board to obtain board info.'
)
);
return;
}
if (!selectedBoard.fqbn) {
this.messageService.info(
`The platform for the selected '${selectedBoard.name}' board is not installed.`
nls.localize(
'arduino/board/platformMissing',
"The platform for the selected '{0}' board is not installed.",
selectedBoard.name
)
);
return;
}
if (!selectedPort) {
this.messageService.info(
'Please select a port to obtain board info.'
nls.localize(
'arduino/board/selectPortForInfo',
'Please select a port to obtain board info.'
)
);
return;
}
@@ -78,11 +89,11 @@ export class BoardSelection extends SketchContribution {
VID: ${VID}
PID: ${PID}`;
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
message: 'Board Info',
title: 'Board Info',
message: nls.localize('arduino/board/boardInfo', 'Board Info'),
title: nls.localize('arduino/board/boardInfo', 'Board Info'),
type: 'info',
detail,
buttons: ['OK'],
buttons: [nls.localize('vscode/issueMainService/ok', 'OK')],
});
}
},
@@ -99,6 +110,9 @@ PID: ${PID}`;
this.boardsServiceProvider.onAvailableBoardsChanged(
this.updateMenus.bind(this)
);
this.boardsServiceProvider.onAvailablePortsChanged(
this.updateMenus.bind(this)
);
}
protected async updateMenus(): Promise<void> {
@@ -127,7 +141,11 @@ PID: ${PID}`;
// The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order.
this.menuModelRegistry.registerSubmenu(
boardsSubmenuPath,
`Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`,
nls.localize(
'arduino/board/board',
'Board{0}',
!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''
),
{ order: '100' }
);
this.toDisposeBeforeMenuRebuild.push(
@@ -144,7 +162,11 @@ PID: ${PID}`;
const portsSubmenuLabel = config.selectedPort?.address;
this.menuModelRegistry.registerSubmenu(
portsSubmenuPath,
`Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`,
nls.localize(
'arduino/board/port',
'Port{0}',
portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''
),
{ order: '101' }
);
this.toDisposeBeforeMenuRebuild.push(
@@ -155,7 +177,7 @@ PID: ${PID}`;
const getBoardInfo = {
commandId: BoardSelection.Commands.GET_BOARD_INFO.id,
label: 'Get Board Info',
label: nls.localize('arduino/board/getBoardInfo', 'Get Board Info'),
order: '103',
};
this.menuModelRegistry.registerMenuAction(
@@ -173,17 +195,26 @@ PID: ${PID}`;
this.menuModelRegistry.registerMenuAction(boardsManagerGroup, {
commandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
label: 'Boards Manager...',
label: `${BoardsListWidget.WIDGET_LABEL}...`,
});
// Installed boards
for (const board of installedBoards) {
const { packageId, packageName, fqbn, name } = board;
const { packageId, packageName, fqbn, name, manuallyInstalled } = board;
const packageLabel =
packageName +
`${
manuallyInstalled
? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)')
: ''
}`;
// Platform submenu
const platformMenuPath = [...boardsPackagesGroup, packageId];
// Note: Registering the same submenu twice is a noop. No need to group the boards per platform.
this.menuModelRegistry.registerSubmenu(platformMenuPath, packageName);
this.menuModelRegistry.registerSubmenu(platformMenuPath, packageLabel, {
order: packageName.toLowerCase(),
});
const id = `arduino-select-board--${fqbn}`;
const command = { id };
@@ -219,19 +250,25 @@ PID: ${PID}`;
}
// Installed ports
const registerPorts = (ports: AvailablePorts) => {
const registerPorts = (
protocol: string,
protocolOrder: number,
ports: AvailablePorts
) => {
const addresses = Object.keys(ports);
if (!addresses.length) {
return;
}
// Register placeholder for protocol
const [port] = ports[addresses[0]];
const protocol = port.protocol;
const menuPath = [...portsSubmenuPath, protocol];
const menuPath = [
...portsSubmenuPath,
`${protocolOrder.toString()}_${protocol}`,
];
const placeholder = new PlaceholderMenuNode(
menuPath,
`${firstToUpperCase(port.protocol)} ports`
`${firstToUpperCase(protocol)} ports`,
{ order: protocolOrder.toString() }
);
this.menuModelRegistry.registerMenuNode(menuPath, placeholder);
this.toDisposeBeforeMenuRebuild.push(
@@ -240,63 +277,76 @@ PID: ${PID}`;
)
);
for (const address of addresses) {
if (!!ports[address]) {
const [port, boards] = ports[address];
if (!boards.length) {
boards.push({
name: '',
});
}
for (const { name, fqbn } of boards) {
const id = `arduino-select-port--${address}${
fqbn ? `--${fqbn}` : ''
}`;
const command = { id };
const handler = {
execute: () => {
if (
!Port.equals(
port,
this.boardsServiceProvider.boardsConfig.selectedPort
)
) {
this.boardsServiceProvider.boardsConfig = {
selectedBoard:
this.boardsServiceProvider.boardsConfig.selectedBoard,
selectedPort: port,
};
}
},
isToggled: () =>
Port.equals(
port,
this.boardsServiceProvider.boardsConfig.selectedPort
),
};
const label = `${address}${name ? ` (${name})` : ''}`;
const menuAction = {
commandId: id,
label,
order: `1${label}`, // `1` comes after the placeholder which has order `0`
};
this.commandRegistry.registerCommand(command, handler);
this.toDisposeBeforeMenuRebuild.push(
Disposable.create(() =>
this.commandRegistry.unregisterCommand(command)
)
);
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
}
// First we show addresses with recognized boards connected,
// then all the rest.
const sortedAddresses = Object.keys(ports);
sortedAddresses.sort((left: string, right: string): number => {
const [, leftBoards] = ports[left];
const [, rightBoards] = ports[right];
return rightBoards.length - leftBoards.length;
});
for (let i = 0; i < sortedAddresses.length; i++) {
const address = sortedAddresses[i];
const [port, boards] = ports[address];
let label = `${address}`;
if (boards.length) {
const boardsList = boards.map((board) => board.name).join(', ');
label = `${label} (${boardsList})`;
}
const id = `arduino-select-port--${address}`;
const command = { id };
const handler = {
execute: () => {
if (
!Port.equals(
port,
this.boardsServiceProvider.boardsConfig.selectedPort
)
) {
this.boardsServiceProvider.boardsConfig = {
selectedBoard:
this.boardsServiceProvider.boardsConfig.selectedBoard,
selectedPort: port,
};
}
},
isToggled: () =>
Port.equals(
port,
this.boardsServiceProvider.boardsConfig.selectedPort
),
};
const menuAction = {
commandId: id,
label,
order: `${protocolOrder + i + 1}`,
};
this.commandRegistry.registerCommand(command, handler);
this.toDisposeBeforeMenuRebuild.push(
Disposable.create(() =>
this.commandRegistry.unregisterCommand(command)
)
);
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
}
};
const { serial, network, unknown } =
AvailablePorts.groupByProtocol(availablePorts);
registerPorts(serial);
registerPorts(network);
registerPorts(unknown);
const grouped = AvailablePorts.byProtocol(availablePorts);
let protocolOrder = 100;
// We first show serial and network ports, then all the rest
['serial', 'network'].forEach((protocol) => {
const ports = grouped.get(protocol);
if (ports) {
registerPorts(protocol, protocolOrder, ports);
grouped.delete(protocol);
protocolOrder = protocolOrder + 100;
}
});
grouped.forEach((ports, protocol) => {
registerPorts(protocol, protocolOrder, ports);
protocolOrder = protocolOrder + 100;
});
this.mainMenuManager.update();
}

View File

@@ -1,9 +1,9 @@
import { inject, injectable } from 'inversify';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
import { CoreService } from '../../common/protocol';
import { ArduinoMenus } from '../menu/arduino-menus';
import { BoardsDataStore } from '../boards/boards-data-store';
import { MonitorConnection } from '../monitor/monitor-connection';
import { SerialConnectionManager } from '../serial/serial-connection-manager';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import {
SketchContribution,
@@ -11,14 +11,15 @@ import {
CommandRegistry,
MenuModelRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class BurnBootloader extends SketchContribution {
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(MonitorConnection)
protected readonly monitorConnection: MonitorConnection;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@@ -38,19 +39,18 @@ export class BurnBootloader extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, {
commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id,
label: 'Burn Bootloader',
label: nls.localize(
'arduino/bootloader/burnBootloader',
'Burn Bootloader'
),
order: 'z99',
});
}
async burnBootloader(): Promise<void> {
const monitorConfig = this.monitorConnection.monitorConfig;
if (monitorConfig) {
await this.monitorConnection.disconnect();
}
try {
const { boardsConfig } = this.boardsServiceClientImpl;
const port = boardsConfig.selectedPort?.address;
const port = boardsConfig.selectedPort;
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
await Promise.all([
this.boardsDataStore.appendConfigToFqbn(
@@ -68,15 +68,25 @@ export class BurnBootloader extends SketchContribution {
verify,
verbose,
});
this.messageService.info('Done burning bootloader.', {
timeout: 3000,
});
this.messageService.info(
nls.localize(
'arduino/bootloader/doneBurningBootloader',
'Done burning bootloader.'
),
{
timeout: 3000,
}
);
} catch (e) {
this.messageService.error(e.toString());
} finally {
if (monitorConfig) {
await this.monitorConnection.connect(monitorConfig);
let errorMessage = "";
if (typeof e === "string") {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
await this.serialConnection.reconnectAfterUpload();
}
}
}

View File

@@ -15,6 +15,7 @@ import {
KeybindingRegistry,
URI,
} from './contribution';
import { nls } from '@theia/core/lib/common';
/**
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
@@ -64,10 +65,25 @@ export class Close extends SketchContribution {
if (isTemp && (await this.wasTouched(uri))) {
const { response } = await remote.dialog.showMessageBox({
type: 'question',
buttons: ["Don't Save", 'Cancel', 'Save'],
message:
'Do you want to save changes to this sketch before closing?',
detail: "If you don't save, your changes will be lost.",
buttons: [
nls.localize(
'vscode/abstractTaskService/saveBeforeRun.dontSave',
"Don't Save"
),
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
nls.localize(
'vscode/abstractTaskService/saveBeforeRun.save',
'Save'
),
],
message: nls.localize(
'arduino/common/saveChangesToSketch',
'Do you want to save changes to this sketch before closing?'
),
detail: nls.localize(
'arduino/common/loseChanges',
"If you don't save, your changes will be lost."
),
});
if (response === 1) {
// Cancel
@@ -93,7 +109,7 @@ export class Close extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: Close.Commands.CLOSE.id,
label: 'Close',
label: nls.localize('vscode/editor.contribution/close', 'Close'),
order: '5',
});
}

View File

@@ -9,7 +9,7 @@ import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { MessageService } from '@theia/core/lib/common/message-service';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
import {
MenuModelRegistry,
MenuContribution,
@@ -33,7 +33,7 @@ import {
CommandService,
} from '@theia/core/lib/common/command';
import { EditorMode } from '../editor-mode';
import { SettingsService } from '../settings';
import { SettingsService } from '../dialogs/settings/settings';
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
import {
SketchesService,

View File

@@ -12,6 +12,7 @@ import {
SketchContribution,
TabBarToolbarRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class Debug extends SketchContribution {
@@ -33,7 +34,10 @@ export class Debug extends SketchContribution {
/**
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
*/
protected _disabledMessages?: string = 'No board selected'; // Initial pessimism.
protected _disabledMessages?: string = nls.localize(
'arduino/common/noBoardSelected',
'No board selected'
); // Initial pessimism.
protected disabledMessageDidChangeEmitter = new Emitter<string | undefined>();
protected onDisabledMessageDidChange =
this.disabledMessageDidChangeEmitter.event;
@@ -51,8 +55,12 @@ export class Debug extends SketchContribution {
command: Debug.Commands.START_DEBUGGING.id,
tooltip: `${
this.disabledMessage
? `Debug - ${this.disabledMessage}`
: 'Start Debugging'
? nls.localize(
'arduino/debug/debugWithMessage',
'Debug - {0}',
this.disabledMessage
)
: Debug.Commands.START_DEBUGGING.label
}`,
priority: 3,
onDidChange: this.onDisabledMessageDidChange as Event<void>,
@@ -63,8 +71,12 @@ export class Debug extends SketchContribution {
() =>
(this.debugToolbarItem.tooltip = `${
this.disabledMessage
? `Debug - ${this.disabledMessage}`
: 'Start Debugging'
? nls.localize(
'arduino/debug/debugWithMessage',
'Debug - {0}',
this.disabledMessage
)
: Debug.Commands.START_DEBUGGING.label
}`)
);
const refreshState = async (
@@ -72,22 +84,37 @@ export class Debug extends SketchContribution {
.selectedBoard
) => {
if (!board) {
this.disabledMessage = 'No board selected';
this.disabledMessage = nls.localize(
'arduino/common/noBoardSelected',
'No board selected'
);
return;
}
const fqbn = board.fqbn;
if (!fqbn) {
this.disabledMessage = `Platform is not installed for '${board.name}'`;
this.disabledMessage = nls.localize(
'arduino/debug/noPlatformInstalledFor',
"Platform is not installed for '{0}'",
board.name
);
return;
}
const details = await this.boardService.getBoardDetails({ fqbn });
if (!details) {
this.disabledMessage = `Platform is not installed for '${board.name}'`;
this.disabledMessage = nls.localize(
'arduino/debug/noPlatformInstalledFor',
"Platform is not installed for '{0}'",
board.name
);
return;
}
const { debuggingSupported } = details;
if (!debuggingSupported) {
this.disabledMessage = `Debugging is not supported by '${board.name}'`;
this.disabledMessage = nls.localize(
'arduino/debug/debuggingNotSupported',
"Debugging is not supported by '{0}'",
board.name
);
} else {
this.disabledMessage = undefined;
}
@@ -155,10 +182,13 @@ export class Debug extends SketchContribution {
export namespace Debug {
export namespace Commands {
export const START_DEBUGGING: Command = {
id: 'arduino-start-debug',
label: 'Start Debugging',
category: 'Arduino',
};
export const START_DEBUGGING = Command.toLocalizedCommand(
{
id: 'arduino-start-debug',
label: 'Start Debugging',
category: 'Arduino',
},
'vscode/debug.contribution/startDebuggingHelp'
);
}
}

View File

@@ -11,6 +11,7 @@ import {
CommandRegistry,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
// TODO: [macOS]: to remove `Start Dictation...` and `Emoji & Symbol` see this thread: https://github.com/electron/electron/issues/8283#issuecomment-269522072
// Depends on https://github.com/eclipse-theia/theia/pull/7964
@@ -79,16 +80,6 @@ export class EditContributions extends Contribution {
{ execute: () => this.run('editor.action.formatDocument') }
);
registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, {
execute: async () => {
const value = await this.currentValue();
if (value !== undefined) {
this.clipboardService.writeText(`[code]
${value}
[/code]`);
}
},
});
registry.registerCommand(EditContributions.Commands.COPY_FOR_GITHUB, {
execute: async () => {
const value = await this.currentValue();
if (value !== undefined) {
@@ -111,80 +102,99 @@ ${value}
});
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
commandId: EditContributions.Commands.COPY_FOR_FORUM.id,
label: 'Copy for Forum',
label: nls.localize(
'arduino/editor/copyForForum',
'Copy for Forum (Markdown)'
),
order: '2',
});
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
commandId: EditContributions.Commands.COPY_FOR_GITHUB.id,
label: 'Copy for GitHub',
commandId: CommonCommands.PASTE.id,
order: '3',
});
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
commandId: CommonCommands.PASTE.id,
commandId: CommonCommands.SELECT_ALL.id,
order: '4',
});
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
commandId: CommonCommands.SELECT_ALL.id,
order: '5',
});
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
commandId: EditContributions.Commands.GO_TO_LINE.id,
label: 'Go to Line...',
order: '6',
label: nls.localize(
'vscode/standaloneStrings/gotoLineActionLabel',
'Go to Line...'
),
order: '5',
});
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
commandId: EditContributions.Commands.TOGGLE_COMMENT.id,
label: 'Comment/Uncomment',
label: nls.localize(
'arduino/editor/commentUncomment',
'Comment/Uncomment'
),
order: '0',
});
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
commandId: EditContributions.Commands.INDENT_LINES.id,
label: 'Increase Indent',
label: nls.localize('arduino/editor/increaseIndent', 'Increase Indent'),
order: '1',
});
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
commandId: EditContributions.Commands.OUTDENT_LINES.id,
label: 'Decrease Indent',
label: nls.localize('arduino/editor/decreaseIndent', 'Decrease Indent'),
order: '2',
});
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id,
label: 'Increase Font Size',
label: nls.localize(
'arduino/editor/increaseFontSize',
'Increase Font Size'
),
order: '0',
});
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
commandId: EditContributions.Commands.DECREASE_FONT_SIZE.id,
label: 'Decrease Font Size',
label: nls.localize(
'arduino/editor/decreaseFontSize',
'Decrease Font Size'
),
order: '1',
});
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
commandId: EditContributions.Commands.FIND.id,
label: 'Find',
label: nls.localize('vscode/findController/startFindAction', 'Find'),
order: '0',
});
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
commandId: EditContributions.Commands.FIND_NEXT.id,
label: 'Find Next',
label: nls.localize(
'vscode/findController/findNextMatchAction',
'Find Next'
),
order: '1',
});
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
commandId: EditContributions.Commands.FIND_PREVIOUS.id,
label: 'Find Previous',
label: nls.localize(
'vscode/findController/findPreviousMatchAction',
'Find Previous'
),
order: '2',
});
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
commandId: EditContributions.Commands.USE_FOR_FIND.id,
label: 'Use Selection for Find', // XXX: The Java IDE uses `Use Selection For Find`.
label: nls.localize(
'vscode/findController/startFindWithSelectionAction',
'Use Selection for Find'
), // XXX: The Java IDE uses `Use Selection For Find`.
order: '3',
});
// `Tools`
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: EditContributions.Commands.AUTO_FORMAT.id,
label: 'Auto Format', // XXX: The Java IDE uses `Use Selection For Find`.
label: nls.localize('arduino/editor/autoFormat', 'Auto Format'), // XXX: The Java IDE uses `Use Selection For Find`.
order: '0',
});
}
@@ -195,11 +205,6 @@ ${value}
keybinding: 'CtrlCmd+Shift+C',
when: 'editorFocus',
});
registry.registerKeybinding({
command: EditContributions.Commands.COPY_FOR_GITHUB.id,
keybinding: 'CtrlCmd+Alt+C',
when: 'editorFocus',
});
registry.registerKeybinding({
command: EditContributions.Commands.GO_TO_LINE.id,
keybinding: 'CtrlCmd+L',
@@ -280,9 +285,6 @@ export namespace EditContributions {
export const COPY_FOR_FORUM: Command = {
id: 'arduino-copy-for-forum',
};
export const COPY_FOR_GITHUB: Command = {
id: 'arduino-copy-for-github',
};
export const GO_TO_LINE: Command = {
id: 'arduino-go-to-line',
};

View File

@@ -22,6 +22,7 @@ import {
} from './contribution';
import { NotificationCenter } from '../notification-center';
import { Board, Sketch, SketchContainer } from '../../common/protocol';
import { nls } from '@theia/core/lib/common';
@injectable()
export abstract class Examples extends SketchContribution {
@@ -69,9 +70,13 @@ export abstract class Examples extends SketchContribution {
}
// Registering the same submenu multiple times has no side-effect.
// TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300
registry.registerSubmenu(ArduinoMenus.FILE__EXAMPLES_SUBMENU, 'Examples', {
order: '4',
});
registry.registerSubmenu(
ArduinoMenus.FILE__EXAMPLES_SUBMENU,
nls.localize('arduino/examples/menu', 'Examples'),
{
order: '4',
}
);
}
registerRecursively(
@@ -166,11 +171,19 @@ export class BuiltInExamples extends Examples {
sketchContainers = await this.examplesService.builtIns();
} catch (e) {
console.error('Could not initialize built-in examples.', e);
this.messageService.error('Could not initialize built-in examples.');
this.messageService.error(
nls.localize(
'arduino/examples/couldNotInitializeExamples',
'Could not initialize built-in examples.'
)
);
return;
}
this.toDispose.dispose();
for (const container of ['Built-in examples', ...sketchContainers]) {
for (const container of [
nls.localize('arduino/examples/builtInExamples', 'Built-in examples'),
...sketchContainers,
]) {
this.registerRecursively(
container,
ArduinoMenus.EXAMPLES__BUILT_IN_GROUP,
@@ -211,13 +224,22 @@ export class LibraryExamples extends Examples {
fqbn,
});
if (user.length) {
(user as any).unshift('Examples from Custom Libraries');
(user as any).unshift(
nls.localize(
'arduino/examples/customLibrary',
'Examples from Custom Libraries'
)
);
}
if (name && fqbn && current.length) {
(current as any).unshift(`Examples for ${name}`);
(current as any).unshift(
nls.localize('arduino/examples/for', 'Examples for {0}', name)
);
}
if (any.length) {
(any as any).unshift('Examples for any board');
(any as any).unshift(
nls.localize('arduino/examples/forAny', 'Examples for any board')
);
}
for (const container of user) {
this.registerRecursively(

View File

@@ -3,8 +3,8 @@ import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { CommandHandler } from '@theia/core/lib/common/command';
import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service';
import { ArduinoMenus } from '../menu/arduino-menus';
import { QuickInputService } from '@theia/core/lib/browser/quick-input/quick-input-service';
import {
Contribution,
Command,
@@ -12,6 +12,7 @@ import {
CommandRegistry,
KeybindingRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class Help extends Contribution {
@@ -60,9 +61,9 @@ export class Help extends Contribution {
}
}
if (!searchFor) {
searchFor = await this.quickInputService.open({
prompt: 'Search on Arduino.cc',
placeHolder: 'Type a keyword',
searchFor = await this.quickInputService.input({
prompt: nls.localize('arduino/help/search', 'Search on Arduino.cc'),
placeHolder: nls.localize('arduino/help/keyword', 'Type a keyword'),
});
}
if (searchFor) {
@@ -128,37 +129,37 @@ export namespace Help {
export namespace Commands {
export const GETTING_STARTED: Command = {
id: 'arduino-getting-started',
label: 'Getting Started',
label: nls.localize('arduino/help/gettingStarted', 'Getting Started'),
category: 'Arduino',
};
export const ENVIRONMENT: Command = {
id: 'arduino-environment',
label: 'Environment',
label: nls.localize('arduino/help/environment', 'Environment'),
category: 'Arduino',
};
export const TROUBLESHOOTING: Command = {
id: 'arduino-troubleshooting',
label: 'Troubleshooting',
label: nls.localize('arduino/help/troubleshooting', 'Troubleshooting'),
category: 'Arduino',
};
export const REFERENCE: Command = {
id: 'arduino-reference',
label: 'Reference',
label: nls.localize('arduino/help/reference', 'Reference'),
category: 'Arduino',
};
export const FIND_IN_REFERENCE: Command = {
id: 'arduino-find-in-reference',
label: 'Find in Reference',
label: nls.localize('arduino/help/findInReference', 'Find in Reference'),
category: 'Arduino',
};
export const FAQ: Command = {
id: 'arduino-faq',
label: 'Frequently Asked Questions',
label: nls.localize('arduino/help/faq', 'Frequently Asked Questions'),
category: 'Arduino',
};
export const VISIT_ARDUINO: Command = {
id: 'arduino-visit-arduino',
label: 'Visit Arduino.cc',
label: nls.localize('arduino/help/visit', 'Visit Arduino.cc'),
category: 'Arduino',
};
}

View File

@@ -15,6 +15,7 @@ import { LibraryListWidget } from '../library/library-list-widget';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { SketchContribution, Command, CommandRegistry } from './contribution';
import { NotificationCenter } from '../notification-center';
import { nls } from '@theia/core/lib/common';
@injectable()
export class IncludeLibrary extends SketchContribution {
@@ -59,13 +60,20 @@ export class IncludeLibrary extends SketchContribution {
...ArduinoMenus.SKETCH__UTILS_GROUP,
'0_include',
];
registry.registerSubmenu(includeLibMenuPath, 'Include Library', {
order: '1',
});
registry.registerSubmenu(
includeLibMenuPath,
nls.localize('arduino/library/include', 'Include Library'),
{
order: '1',
}
);
// `Manage Libraries...` group.
registry.registerMenuAction([...includeLibMenuPath, '0_manage'], {
commandId: `${LibraryListWidget.WIDGET_ID}:toggle`,
label: 'Manage Libraries...',
label: nls.localize(
'arduino/library/manageLibraries',
'Manage Libraries...'
),
});
}
@@ -101,10 +109,17 @@ export class IncludeLibrary extends SketchContribution {
const userMenuPath = [...includeLibMenuPath, '3_contributed'];
const { user, rest } = LibraryPackage.groupByLocation(libraries);
if (rest.length) {
(rest as any).unshift('Arduino libraries');
(rest as any).unshift(
nls.localize('arduino/library/arduinoLibraries', 'Arduino libraries')
);
}
if (user.length) {
(user as any).unshift('Contributed libraries');
(user as any).unshift(
nls.localize(
'arduino/library/contributedLibraries',
'Contributed libraries'
)
);
}
for (const library of user) {

View File

@@ -1,3 +1,4 @@
import { nls } from '@theia/core/lib/common';
import { injectable } from 'inversify';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
@@ -27,7 +28,7 @@ export class NewSketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: NewSketch.Commands.NEW_SKETCH.id,
label: 'New',
label: nls.localize('arduino/sketch/new', 'New'),
order: '0',
});
}
@@ -43,7 +44,7 @@ export class NewSketch extends SketchContribution {
registry.registerItem({
id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
tooltip: 'New',
tooltip: nls.localize('arduino/sketch/new', 'New'),
priority: 3,
});
}

View File

@@ -14,6 +14,7 @@ import { ArduinoMenus } from '../menu/arduino-menus';
import { MainMenuManager } from '../../common/main-menu-manager';
import { OpenSketch } from './open-sketch';
import { NotificationCenter } from '../notification-center';
import { nls } from '@theia/core/lib/common';
@injectable()
export class OpenRecentSketch extends SketchContribution {
@@ -48,7 +49,7 @@ export class OpenRecentSketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerSubmenu(
ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
'Open Recent',
nls.localize('arduino/sketch/openRecent', 'Open Recent'),
{ order: '2' }
);
}

View File

@@ -9,6 +9,7 @@ import {
MenuModelRegistry,
KeybindingRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class OpenSketchExternal extends SketchContribution {
@@ -21,7 +22,7 @@ export class OpenSketchExternal extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
label: 'Show Sketch Folder',
label: nls.localize('arduino/sketch/showFolder', 'Show Sketch Folder'),
order: '0',
});
}
@@ -36,7 +37,7 @@ export class OpenSketchExternal extends SketchContribution {
protected async openExternal(): Promise<void> {
const uri = await this.sketchServiceClient.currentSketchFile();
if (uri) {
const exists = this.fileService.exists(new URI(uri));
const exists = await this.fileService.exists(new URI(uri));
if (exists) {
const fsPath = await this.fileService.fsPath(new URI(uri));
if (fsPath) {

View File

@@ -22,6 +22,7 @@ import { ExamplesService } from '../../common/protocol/examples-service';
import { BuiltInExamples } from './examples';
import { Sketchbook } from './sketchbook';
import { SketchContainer } from '../../common/protocol';
import { nls } from '@theia/core/lib/common';
@injectable()
export class OpenSketch extends SketchContribution {
@@ -70,7 +71,10 @@ export class OpenSketch extends SketchContribution {
ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP,
{
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
label: 'Open...',
label: nls.localize(
'vscode/workspaceActions/openFileFolder',
'Open...'
),
}
);
this.toDispose.push(
@@ -115,7 +119,7 @@ export class OpenSketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
label: 'Open...',
label: nls.localize('vscode/workspaceActions/openFileFolder', 'Open...'),
order: '1',
});
}
@@ -131,7 +135,7 @@ export class OpenSketch extends SketchContribution {
registry.registerItem({
id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
tooltip: 'Open',
tooltip: nls.localize('vscode/dialogMainService/open', 'Open'),
priority: 4,
});
}
@@ -155,7 +159,7 @@ export class OpenSketch extends SketchContribution {
properties: ['createDirectory', 'openFile'],
filters: [
{
name: 'Sketch',
name: nls.localize('arduino/sketch/sketch', 'Sketch'),
extensions: ['ino', 'pde'],
},
],
@@ -178,10 +182,18 @@ export class OpenSketch extends SketchContribution {
const name = new URI(sketchFileUri).path.name;
const nameWithExt = this.labelProvider.getName(new URI(sketchFileUri));
const { response } = await remote.dialog.showMessageBox({
title: 'Moving',
title: nls.localize('arduino/sketch/moving', 'Moving'),
type: 'question',
buttons: ['Cancel', 'OK'],
message: `The file "${nameWithExt}" needs to be inside a sketch folder named as "${name}".\nCreate this folder, move the file, and continue?`,
buttons: [
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
nls.localize('vscode/issueMainService/ok', 'OK'),
],
message: nls.localize(
'arduino/sketch/movingMsg',
'The file "{0}" needs to be inside a sketch folder named as "{1}".\nCreate this folder, move the file, and continue?',
nameWithExt,
name
),
});
if (response === 1) {
// OK
@@ -190,8 +202,12 @@ export class OpenSketch extends SketchContribution {
if (exists) {
await remote.dialog.showMessageBox({
type: 'error',
title: 'Error',
message: `A folder named "${name}" already exists. Can't open sketch.`,
title: nls.localize('vscode/dialog/dialogErrorMessage', 'Error'),
message: nls.localize(
'arduino/sketch/cantOpen',
'A folder named "{0}" already exists. Can\'t open sketch.',
name
),
});
return undefined;
}

View File

@@ -9,6 +9,7 @@ import {
CommandRegistry,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
@injectable()
export class QuitApp extends Contribution {
@@ -25,7 +26,7 @@ export class QuitApp extends Contribution {
if (!isOSX) {
registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, {
commandId: QuitApp.Commands.QUIT_APP.id,
label: 'Quit',
label: nls.localize('vscode/bulkEditService/quit', 'Quit'),
order: '0',
});
}

View File

@@ -10,6 +10,7 @@ import {
MenuModelRegistry,
KeybindingRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class SaveAsSketch extends SketchContribution {
@@ -22,7 +23,7 @@ export class SaveAsSketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
label: 'Save As...',
label: nls.localize('vscode/fileCommands/saveAs', 'Save As...'),
order: '7',
});
}
@@ -73,7 +74,10 @@ export class SaveAsSketch extends SketchContribution {
: sketchDirUri.resolve(sketch.name);
const defaultPath = await this.fileService.fsPath(defaultUri);
const { filePath, canceled } = await remote.dialog.showSaveDialog({
title: 'Save sketch folder as...',
title: nls.localize(
'arduino/sketch/saveFolderAs',
'Save sketch folder as...'
),
defaultPath,
});
if (!filePath || canceled) {

View File

@@ -2,6 +2,7 @@ import { injectable } from 'inversify';
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { SaveAsSketch } from './save-as-sketch';
import {
SketchContribution,
Command,
@@ -10,6 +11,7 @@ import {
KeybindingRegistry,
TabBarToolbarRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class SaveSketch extends SketchContribution {
@@ -28,7 +30,7 @@ export class SaveSketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: SaveSketch.Commands.SAVE_SKETCH.id,
label: 'Save',
label: nls.localize('vscode/fileCommands/save', 'Save'),
order: '6',
});
}
@@ -44,12 +46,28 @@ export class SaveSketch extends SketchContribution {
registry.registerItem({
id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
tooltip: 'Save',
tooltip: nls.localize('vscode/fileCommands/save', 'Save'),
priority: 5,
});
}
async saveSketch(): Promise<void> {
const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) {
return;
}
const isTemp = await this.sketchService.isTemp(sketch);
if (isTemp) {
return this.commandService.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{
execOnlyIfTemp: false,
openAfterMove: true,
wipeOriginal: true,
}
);
}
return this.commandService.executeCommand(CommonCommands.SAVE_ALL.id);
}
}

View File

@@ -7,7 +7,9 @@ import {
KeybindingRegistry,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { Settings as Preferences, SettingsDialog } from '../settings';
import { Settings as Preferences } from '../dialogs/settings/settings';
import { SettingsDialog } from '../dialogs/settings/settings-dialog';
import { nls } from '@theia/core/lib/common';
@injectable()
export class Settings extends SketchContribution {
@@ -40,7 +42,11 @@ export class Settings extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, {
commandId: Settings.Commands.OPEN.id,
label: 'Preferences...',
label:
nls.localize(
'vscode/preferences.contribution/preferences',
'Preferences'
) + '...',
order: '0',
});
registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced');
@@ -58,7 +64,11 @@ export namespace Settings {
export namespace Commands {
export const OPEN: Command = {
id: 'arduino-settings-open',
label: 'Open Preferences...',
label:
nls.localize(
'vscode/preferences.contribution/openSettings2',
'Open Preferences'
) + '...',
category: 'Arduino',
};
}

View File

@@ -21,6 +21,7 @@ import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider';
import { nls } from '@theia/core/lib/common';
@injectable()
export class SketchControl extends SketchContribution {
@@ -87,13 +88,13 @@ export class SketchControl extends SketchContribution {
currentSketch &&
parentsketch &&
parentsketch.uri === currentSketch.uri &&
(await this.allowRename(parentsketch.uri))
this.allowRename(parentsketch.uri)
) {
this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.FILE_RENAME.id,
label: 'Rename',
label: nls.localize('vscode/fileActions/rename', 'Rename'),
order: '1',
}
);
@@ -107,7 +108,7 @@ export class SketchControl extends SketchContribution {
} else {
const renamePlaceholder = new PlaceholderMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
'Rename'
nls.localize('vscode/fileActions/rename', 'Rename')
);
this.menuRegistry.registerMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
@@ -124,13 +125,13 @@ export class SketchControl extends SketchContribution {
currentSketch &&
parentsketch &&
parentsketch.uri === currentSketch.uri &&
(await this.allowDelete(parentsketch.uri))
this.allowDelete(parentsketch.uri)
) {
this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.FILE_DELETE.id, // TODO: customize delete. Wipe sketch if deleting main file. Close window.
label: 'Delete',
label: nls.localize('vscode/fileActions/delete', 'Delete'),
order: '2',
}
);
@@ -144,7 +145,7 @@ export class SketchControl extends SketchContribution {
} else {
const deletePlaceholder = new PlaceholderMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
'Delete'
nls.localize('vscode/fileActions/delete', 'Delete')
);
this.menuRegistry.registerMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
@@ -204,7 +205,7 @@ export class SketchControl extends SketchContribution {
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.NEW_FILE.id,
label: 'New Tab',
label: nls.localize('vscode/menubar/mNewTab', 'New Tab'),
order: '0',
}
);
@@ -213,7 +214,7 @@ export class SketchControl extends SketchContribution {
ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP,
{
commandId: CommonCommands.PREVIOUS_TAB.id,
label: 'Previous Tab',
label: nls.localize('vscode/menubar/mShowPreviousTab', 'Previous Tab'),
order: '0',
}
);
@@ -221,7 +222,7 @@ export class SketchControl extends SketchContribution {
ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP,
{
commandId: CommonCommands.NEXT_TAB.id,
label: 'Next Tab',
label: nls.localize('vscode/menubar/mShowNextTab', 'Next Tab'),
order: '0',
}
);
@@ -249,20 +250,24 @@ export class SketchControl extends SketchContribution {
});
}
protected async isCloudSketch(uri: string) {
const cloudCacheLocation = this.localCacheFsProvider.from(new URI(uri));
protected isCloudSketch(uri: string): boolean {
try {
const cloudCacheLocation = this.localCacheFsProvider.from(new URI(uri));
if (cloudCacheLocation) {
return true;
if (cloudCacheLocation) {
return true;
}
return false;
} catch {
return false;
}
return false;
}
protected async allowRename(uri: string) {
protected allowRename(uri: string): boolean {
return !this.isCloudSketch(uri);
}
protected async allowDelete(uri: string) {
protected allowDelete(uri: string): boolean {
return !this.isCloudSketch(uri);
}
}

View File

@@ -7,6 +7,7 @@ import { NotificationCenter } from '../notification-center';
import { Examples } from './examples';
import { SketchContainer } from '../../common/protocol';
import { OpenSketch } from './open-sketch';
import { nls } from '@theia/core/lib/common';
@injectable()
export class Sketchbook extends Examples {
@@ -38,7 +39,7 @@ export class Sketchbook extends Examples {
registerMenus(registry: MenuModelRegistry): void {
registry.registerSubmenu(
ArduinoMenus.FILE__SKETCHBOOK_SUBMENU,
'Sketchbook',
nls.localize('arduino/sketch/sketchbook', 'Sketchbook'),
{ order: '3' }
);
}

View File

@@ -0,0 +1,140 @@
import { inject, injectable } from 'inversify';
import {
Command,
MenuModelRegistry,
CommandRegistry,
Contribution,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { UploadCertificateDialog } from '../dialogs/certificate-uploader/certificate-uploader-dialog';
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
import {
PreferenceScope,
PreferenceService,
} from '@theia/core/lib/browser/preferences/preference-service';
import { ArduinoPreferences } from '../arduino-preferences';
import {
arduinoCert,
certificateList,
} from '../dialogs/certificate-uploader/utils';
import { ArduinoFirmwareUploader } from '../../common/protocol/arduino-firmware-uploader';
import { nls } from '@theia/core/lib/common';
@injectable()
export class UploadCertificate extends Contribution {
@inject(UploadCertificateDialog)
protected readonly dialog: UploadCertificateDialog;
@inject(ContextMenuRenderer)
protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@inject(ArduinoPreferences)
protected readonly arduinoPreferences: ArduinoPreferences;
@inject(ArduinoFirmwareUploader)
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
protected dialogOpened = false;
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UploadCertificate.Commands.OPEN, {
execute: async () => {
try {
this.dialogOpened = true;
await this.dialog.open();
} finally {
this.dialogOpened = false;
}
},
isEnabled: () => !this.dialogOpened,
});
registry.registerCommand(UploadCertificate.Commands.REMOVE_CERT, {
execute: async (certToRemove) => {
const certs = this.arduinoPreferences.get('arduino.board.certificates');
this.preferenceService.set(
'arduino.board.certificates',
certificateList(certs)
.filter((c) => c !== certToRemove)
.join(','),
PreferenceScope.User
);
},
isEnabled: (certToRemove) => certToRemove !== arduinoCert,
});
registry.registerCommand(UploadCertificate.Commands.UPLOAD_CERT, {
execute: async ({ fqbn, address, urls }) => {
return this.arduinoFirmwareUploader.uploadCertificates(
`-b ${fqbn} -a ${address} ${urls
.map((url: string) => `-u ${url}`)
.join(' ')}`
);
},
isEnabled: () => true,
});
registry.registerCommand(UploadCertificate.Commands.OPEN_CERT_CONTEXT, {
execute: async (args: any) => {
this.contextMenuRenderer.render({
menuPath: ArduinoMenus.ROOT_CERTIFICATES__CONTEXT,
anchor: {
x: args.x,
y: args.y,
},
args: [args.cert],
});
},
isEnabled: () => true,
});
}
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, {
commandId: UploadCertificate.Commands.OPEN.id,
label: UploadCertificate.Commands.OPEN.label,
order: '1',
});
registry.registerMenuAction(ArduinoMenus.ROOT_CERTIFICATES__CONTEXT, {
commandId: UploadCertificate.Commands.REMOVE_CERT.id,
label: UploadCertificate.Commands.REMOVE_CERT.label,
order: '1',
});
}
}
export namespace UploadCertificate {
export namespace Commands {
export const OPEN: Command = {
id: 'arduino-upload-certificate-open',
label: nls.localize(
'arduino/certificate/uploadRootCertificates',
'Upload SSL Root Certificates'
),
category: 'Arduino',
};
export const OPEN_CERT_CONTEXT: Command = {
id: 'arduino-certificate-open-context',
label: nls.localize('arduino/certificate/openContext', 'Open context'),
category: 'Arduino',
};
export const REMOVE_CERT: Command = {
id: 'arduino-certificate-remove',
label: nls.localize('arduino/certificate/remove', 'Remove'),
category: 'Arduino',
};
export const UPLOAD_CERT: Command = {
id: 'arduino-certificate-upload',
label: nls.localize('arduino/certificate/upload', 'Upload'),
category: 'Arduino',
};
}
}

View File

@@ -0,0 +1,53 @@
import { inject, injectable } from 'inversify';
import {
Command,
MenuModelRegistry,
CommandRegistry,
Contribution,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { UploadFirmwareDialog } from '../dialogs/firmware-uploader/firmware-uploader-dialog';
import { nls } from '@theia/core/lib/common';
@injectable()
export class UploadFirmware extends Contribution {
@inject(UploadFirmwareDialog)
protected readonly dialog: UploadFirmwareDialog;
protected dialogOpened = false;
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UploadFirmware.Commands.OPEN, {
execute: async () => {
try {
this.dialogOpened = true;
await this.dialog.open();
} finally {
this.dialogOpened = false;
}
},
isEnabled: () => !this.dialogOpened,
});
}
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, {
commandId: UploadFirmware.Commands.OPEN.id,
label: UploadFirmware.Commands.OPEN.label,
order: '0',
});
}
}
export namespace UploadFirmware {
export namespace Commands {
export const OPEN: Command = {
id: 'arduino-upload-firmware-open',
label: nls.localize(
'arduino/firmware/updater',
'WiFi101 / WiFiNINA Firmware Updater'
),
category: 'Arduino',
};
}
}

View File

@@ -1,10 +1,10 @@
import { inject, injectable } from 'inversify';
import { inject, injectable, postConstruct } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import { CoreService } from '../../common/protocol';
import { ArduinoMenus } from '../menu/arduino-menus';
import { BoardUserField, CoreService } from '../../common/protocol';
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { BoardsDataStore } from '../boards/boards-data-store';
import { MonitorConnection } from '../monitor/monitor-connection';
import { SerialConnectionManager } from '../serial/serial-connection-manager';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import {
SketchContribution,
@@ -14,14 +14,19 @@ import {
KeybindingRegistry,
TabBarToolbarRegistry,
} from './contribution';
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
import { DisposableCollection, nls } from '@theia/core/lib/common';
@injectable()
export class UploadSketch extends SketchContribution {
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(MonitorConnection)
protected readonly monitorConnection: MonitorConnection;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(MenuModelRegistry)
protected readonly menuRegistry: MenuModelRegistry;
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@@ -29,16 +34,89 @@ export class UploadSketch extends SketchContribution {
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(UserFieldsDialog)
protected readonly userFieldsDialog: UserFieldsDialog;
protected cachedUserFields: Map<string, BoardUserField[]> = new Map();
protected readonly onDidChangeEmitter = new Emitter<Readonly<void>>();
readonly onDidChange = this.onDidChangeEmitter.event;
protected uploadInProgress = false;
protected boardRequiresUserFields = false;
protected readonly menuActionsDisposables = new DisposableCollection();
@postConstruct()
protected init(): void {
this.boardsServiceClientImpl.onBoardsConfigChanged(async () => {
const userFields =
await this.boardsServiceClientImpl.selectedBoardUserFields();
this.boardRequiresUserFields = userFields.length > 0;
this.registerMenus(this.menuRegistry);
});
}
private selectedFqbnAddress(): string {
const { boardsConfig } = this.boardsServiceClientImpl;
const fqbn = boardsConfig.selectedBoard?.fqbn;
if (!fqbn) {
return '';
}
const address =
boardsConfig.selectedBoard?.port?.address ||
boardsConfig.selectedPort?.address;
if (!address) {
return '';
}
return fqbn + '|' + address;
}
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
execute: () => this.uploadSketch(),
execute: async () => {
const key = this.selectedFqbnAddress();
if (!key) {
return;
}
if (this.boardRequiresUserFields && !this.cachedUserFields.has(key)) {
// Deep clone the array of board fields to avoid editing the cached ones
this.userFieldsDialog.value = (
await this.boardsServiceClientImpl.selectedBoardUserFields()
).map((f) => ({ ...f }));
const result = await this.userFieldsDialog.open();
if (!result) {
return;
}
this.cachedUserFields.set(key, result);
}
this.uploadSketch();
},
isEnabled: () => !this.uploadInProgress,
});
registry.registerCommand(UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION, {
execute: async () => {
const key = this.selectedFqbnAddress();
if (!key) {
return;
}
const cached = this.cachedUserFields.get(key);
// Deep clone the array of board fields to avoid editing the cached ones
this.userFieldsDialog.value = (
cached ??
(await this.boardsServiceClientImpl.selectedBoardUserFields())
).map((f) => ({ ...f }));
const result = await this.userFieldsDialog.open();
if (!result) {
return;
}
this.cachedUserFields.set(key, result);
this.uploadSketch();
},
isEnabled: () => !this.uploadInProgress && this.boardRequiresUserFields,
});
registry.registerCommand(
UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER,
{
@@ -57,16 +135,46 @@ export class UploadSketch extends SketchContribution {
}
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
label: 'Upload',
order: '1',
});
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
label: 'Upload Using Programmer',
order: '2',
});
this.menuActionsDisposables.dispose();
this.menuActionsDisposables.push(
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
label: nls.localize('arduino/sketch/upload', 'Upload'),
order: '1',
})
);
if (this.boardRequiresUserFields) {
this.menuActionsDisposables.push(
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
label: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
order: '2',
})
);
} else {
this.menuActionsDisposables.push(
registry.registerMenuNode(
ArduinoMenus.SKETCH__MAIN_GROUP,
new PlaceholderMenuNode(
ArduinoMenus.SKETCH__MAIN_GROUP,
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label!,
{ order: '2' }
)
)
);
}
this.menuActionsDisposables.push(
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
label: nls.localize(
'arduino/sketch/uploadUsingProgrammer',
'Upload Using Programmer'
),
order: '3',
})
);
}
registerKeybindings(registry: KeybindingRegistry): void {
@@ -84,7 +192,7 @@ export class UploadSketch extends SketchContribution {
registry.registerItem({
id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
tooltip: 'Upload',
tooltip: nls.localize('arduino/sketch/upload', 'Upload'),
priority: 1,
onDidChange: this.onDidChange,
});
@@ -104,15 +212,7 @@ export class UploadSketch extends SketchContribution {
if (!sketch) {
return;
}
let shouldAutoConnect = false;
const monitorConfig = this.monitorConnection.monitorConfig;
if (monitorConfig) {
await this.monitorConnection.disconnect();
if (this.monitorConnection.autoConnect) {
shouldAutoConnect = true;
}
this.monitorConnection.autoConnect = false;
}
try {
const { boardsConfig } = this.boardsServiceClientImpl;
const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] =
@@ -130,7 +230,18 @@ export class UploadSketch extends SketchContribution {
const sketchUri = sketch.uri;
const optimizeForDebug = this.editorMode.compileForDebug;
const { selectedPort } = boardsConfig;
const port = selectedPort?.address;
const port = selectedPort;
const userFields =
this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
if (userFields.length === 0 && this.boardRequiresUserFields) {
this.messageService.error(
nls.localize(
'arduino/sketch/userFieldsNotFoundError',
"Can't find user fields for connected board"
)
);
return;
}
if (usingProgrammer) {
const programmer = selectedProgrammer;
@@ -143,6 +254,7 @@ export class UploadSketch extends SketchContribution {
verbose,
verify,
sourceOverride,
userFields,
};
} else {
options = {
@@ -153,6 +265,7 @@ export class UploadSketch extends SketchContribution {
verbose,
verify,
sourceOverride,
userFields,
};
}
this.outputChannelManager.getChannel('Arduino').clear();
@@ -161,32 +274,23 @@ export class UploadSketch extends SketchContribution {
} else {
await this.coreService.upload(options);
}
this.messageService.info('Done uploading.', { timeout: 3000 });
this.messageService.info(
nls.localize('arduino/sketch/doneUploading', 'Done uploading.'),
{ timeout: 3000 }
);
} catch (e) {
this.messageService.error(e.toString());
let errorMessage = '';
if (typeof e === 'string') {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
this.uploadInProgress = false;
this.onDidChangeEmitter.fire();
if (monitorConfig) {
const { board, port } = monitorConfig;
try {
await this.boardsServiceClientImpl.waitUntilAvailable(
Object.assign(board, { port }),
10_000
);
if (shouldAutoConnect) {
// Enabling auto-connect will trigger a connect.
this.monitorConnection.autoConnect = true;
} else {
await this.monitorConnection.connect(monitorConfig);
}
} catch (waitError) {
this.messageService.error(
`Could not reconnect to serial monitor. ${waitError.toString()}`
);
}
}
setTimeout(() => this.serialConnection.reconnectAfterUpload(), 5000);
}
}
}
@@ -196,6 +300,14 @@ export namespace UploadSketch {
export const UPLOAD_SKETCH: Command = {
id: 'arduino-upload-sketch',
};
export const UPLOAD_WITH_CONFIGURATION: Command = {
id: 'arduino-upload-with-configuration-sketch',
label: nls.localize(
'arduino/sketch/configureAndUpload',
'Configure And Upload'
),
category: 'Arduino',
};
export const UPLOAD_SKETCH_USING_PROGRAMMER: Command = {
id: 'arduino-upload-sketch-using-programmer',
};

View File

@@ -13,6 +13,7 @@ import {
KeybindingRegistry,
TabBarToolbarRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
@injectable()
export class VerifySketch extends SketchContribution {
@@ -52,13 +53,16 @@ export class VerifySketch extends SketchContribution {
registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
label: 'Verify/Compile',
label: nls.localize('arduino/sketch/verifyOrCompile', 'Verify/Compile'),
order: '0',
});
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: VerifySketch.Commands.EXPORT_BINARIES.id,
label: 'Export compiled Binary',
order: '3',
label: nls.localize(
'arduino/sketch/exportBinary',
'Export Compiled Binary'
),
order: '4',
});
}
@@ -77,7 +81,7 @@ export class VerifySketch extends SketchContribution {
registry.registerItem({
id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
tooltip: 'Verify',
tooltip: nls.localize('arduino/sketch/verify', 'Verify'),
priority: 0,
onDidChange: this.onDidChange,
});
@@ -118,9 +122,18 @@ export class VerifySketch extends SketchContribution {
sourceOverride,
compilerWarnings,
});
this.messageService.info('Done compiling.', { timeout: 3000 });
this.messageService.info(
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
{ timeout: 3000 }
);
} catch (e) {
this.messageService.error(e.toString());
let errorMessage = "";
if (typeof e === "string") {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
this.verifyInProgress = false;
this.onDidChangeEmitter.fire();

View File

@@ -15,6 +15,47 @@ export namespace ResponseResultProvider {
export const JSON: ResponseResultProvider = (response) => response.json();
}
export function Utf8ArrayToStr(array: Uint8Array): string {
let out, i, c;
let char2, char3;
out = '';
const len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
);
break;
}
}
return out;
}
type ResourceType = 'f' | 'd';
@injectable()
@@ -275,9 +316,7 @@ export class CreateApi {
// parse the secret file
const secrets = (
typeof content === 'string'
? content
: new TextDecoder().decode(content)
typeof content === 'string' ? content : Utf8ArrayToStr(content)
)
.split(/\r?\n/)
.reduce((prev, curr) => {
@@ -341,7 +380,7 @@ export class CreateApi {
const headers = await this.headers();
let data: string =
typeof content === 'string' ? content : new TextDecoder().decode(content);
typeof content === 'string' ? content : Utf8ArrayToStr(content);
data = await this.toggleSecretsInclude(posixPath, data, 'remove');
const payload = { data: btoa(data) };

View File

@@ -97,6 +97,7 @@
"editorWhitespace.foreground": "#bfbfbf",
"editor.lineHighlightBackground": "#434f5410",
"editor.selectionBackground": "#ffcb00",
"editorWidget.background": "#F7F9F9",
"focusBorder": "#7fcbcd99",
"menubar.selectionBackground": "#ffffff",
"menubar.selectionForeground": "#212121",

View File

@@ -0,0 +1,46 @@
import { nls } from '@theia/core/lib/common';
import * as React from 'react';
export const CertificateAddComponent = ({
addCertificate,
}: {
addCertificate: (cert: string) => void;
}): React.ReactElement => {
const [value, setValue] = React.useState('');
const handleChange = React.useCallback((event) => {
setValue(event.target.value);
}, []);
return (
<form
className="certificate-add"
onSubmit={(event) => {
event.preventDefault();
event.stopPropagation();
addCertificate(value);
setValue('');
}}
>
<label>
<div>
{nls.localize(
'arduino/certificate/addURL',
'Add URL to fetch SSL certificate'
)}
</div>
<input
className="theia-input"
placeholder={nls.localize(
'arduino/certificate/enterURL',
'Enter URL'
)}
type="text"
name="add"
onChange={handleChange}
value={value}
/>
</label>
</form>
);
};

View File

@@ -0,0 +1,51 @@
import * as React from 'react';
export const CertificateListComponent = ({
certificates,
selectedCerts,
setSelectedCerts,
openContextMenu,
}: {
certificates: string[];
selectedCerts: string[];
setSelectedCerts: React.Dispatch<React.SetStateAction<string[]>>;
openContextMenu: (x: number, y: number, cert: string) => void;
}): React.ReactElement => {
const handleOnChange = (event: any) => {
const target = event.target;
const newSelectedCerts = selectedCerts.filter(
(cert) => cert !== target.name
);
if (target.checked) {
newSelectedCerts.push(target.name);
}
setSelectedCerts(newSelectedCerts);
};
const handleContextMenu = (event: React.MouseEvent, cert: string) => {
openContextMenu(event.clientX, event.clientY, cert);
};
return (
<div className="certificate-list">
{certificates.map((certificate, i) => (
<label
key={i}
className="certificate-row"
onContextMenu={(e) => handleContextMenu(e, certificate)}
>
<span className="fl1">{certificate}</span>
<input
type="checkbox"
name={certificate}
checked={selectedCerts.includes(certificate)}
onChange={handleOnChange}
/>
</label>
))}
</div>
);
};

View File

@@ -0,0 +1,178 @@
import * as React from 'react';
import Tippy from '@tippyjs/react';
import { AvailableBoard } from '../../boards/boards-service-provider';
import { CertificateListComponent } from './certificate-list';
import { SelectBoardComponent } from './select-board-components';
import { CertificateAddComponent } from './certificate-add-new';
import { nls } from '@theia/core/lib/common';
export const CertificateUploaderComponent = ({
availableBoards,
certificates,
addCertificate,
updatableFqbns,
uploadCertificates,
openContextMenu,
}: {
availableBoards: AvailableBoard[];
certificates: string[];
addCertificate: (cert: string) => void;
updatableFqbns: string[];
uploadCertificates: (
fqbn: string,
address: string,
urls: string[]
) => Promise<any>;
openContextMenu: (x: number, y: number, cert: string) => void;
}): React.ReactElement => {
const [installFeedback, setInstallFeedback] = React.useState<
'ok' | 'fail' | 'installing' | null
>(null);
const [showAdd, setShowAdd] = React.useState(false);
const [selectedCerts, setSelectedCerts] = React.useState<string[]>([]);
const [selectedBoard, setSelectedBoard] =
React.useState<AvailableBoard | null>(null);
const installCertificates = async () => {
if (!selectedBoard || !selectedBoard.fqbn || !selectedBoard.port) {
return;
}
setInstallFeedback('installing');
try {
await uploadCertificates(
selectedBoard.fqbn,
selectedBoard.port.address,
selectedCerts
);
setInstallFeedback('ok');
} catch {
setInstallFeedback('fail');
}
};
const onBoardSelect = React.useCallback(
(board: AvailableBoard) => {
const newFqbn = (board && board.fqbn) || null;
const prevFqbn = (selectedBoard && selectedBoard.fqbn) || null;
if (newFqbn !== prevFqbn) {
setInstallFeedback(null);
setSelectedBoard(board);
}
},
[selectedBoard]
);
return (
<>
<div className="dialogSection">
<div className="dialogRow">
<strong className="fl1">
{nls.localize(
'arduino/certificate/selectCertificateToUpload',
'1. Select certificate to upload'
)}
</strong>
<Tippy
content={
<CertificateAddComponent
addCertificate={(cert) => {
addCertificate(cert);
setShowAdd(false);
}}
/>
}
placement="bottom-end"
onClickOutside={() => setShowAdd(false)}
visible={showAdd}
interactive={true}
>
<button
type="button"
className="theia-button primary add-cert-btn"
onClick={() => {
showAdd ? setShowAdd(false) : setShowAdd(true);
}}
>
{nls.localize('arduino/certificate/addNew', 'Add New')}{' '}
<span className="fa fa-caret-down caret"></span>
</button>
</Tippy>
</div>
<div className="dialogRow">
<CertificateListComponent
certificates={certificates}
selectedCerts={selectedCerts}
setSelectedCerts={setSelectedCerts}
openContextMenu={openContextMenu}
/>
</div>
</div>
<div className="dialogSection">
<div className="dialogRow">
<strong>
{nls.localize(
'arduino/certificate/selectDestinationBoardToUpload',
'2. Select destination board and upload certificate'
)}
</strong>
</div>
<div className="dialogRow">
<div className="fl1">
<SelectBoardComponent
availableBoards={availableBoards}
updatableFqbns={updatableFqbns}
onBoardSelect={onBoardSelect}
selectedBoard={selectedBoard}
busy={installFeedback === 'installing'}
/>
</div>
</div>
<div className="dialogRow">
<div className="upload-status">
{installFeedback === 'installing' && (
<div className="success">
<div className="spinner" />
{nls.localize(
'arduino/certificate/uploadingCertificates',
'Uploading certificates.'
)}
</div>
)}
{installFeedback === 'ok' && (
<div className="success">
<i className="fa fa-info status-icon" />
{nls.localize(
'arduino/certificate/certificatesUploaded',
'Certificates uploaded.'
)}
</div>
)}
{installFeedback === 'fail' && (
<div className="warn">
<i className="fa fa-exclamation status-icon" />
{nls.localize(
'arduino/certificate/uploadFailed',
'Upload failed. Please try again.'
)}
</div>
)}
</div>
<button
type="button"
className="theia-button primary install-cert-btn"
onClick={installCertificates}
disabled={selectedCerts.length === 0 || !selectedBoard}
>
{nls.localize('arduino/certificate/upload', 'Upload')}
</button>
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,197 @@
import * as React from 'react';
import { inject, injectable, postConstruct } from 'inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import {
AvailableBoard,
BoardsServiceProvider,
} from '../../boards/boards-service-provider';
import { CertificateUploaderComponent } from './certificate-uploader-component';
import { ArduinoPreferences } from '../../arduino-preferences';
import {
PreferenceScope,
PreferenceService,
} from '@theia/core/lib/browser/preferences/preference-service';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { certificateList, sanifyCertString } from './utils';
import { ArduinoFirmwareUploader } from '../../../common/protocol/arduino-firmware-uploader';
import { nls } from '@theia/core/lib/common';
@injectable()
export class UploadCertificateDialogWidget extends ReactWidget {
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(ArduinoPreferences)
protected readonly arduinoPreferences: ArduinoPreferences;
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry;
@inject(ArduinoFirmwareUploader)
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
protected certificates: string[] = [];
protected updatableFqbns: string[] = [];
protected availableBoards: AvailableBoard[] = [];
public busyCallback = (busy: boolean) => {
return;
};
constructor() {
super();
}
@postConstruct()
protected init(): void {
this.arduinoPreferences.ready.then(() => {
this.certificates = certificateList(
this.arduinoPreferences.get('arduino.board.certificates')
);
});
this.arduinoPreferences.onPreferenceChanged((event) => {
if (
event.preferenceName === 'arduino.board.certificates' &&
event.newValue !== event.oldValue
) {
this.certificates = certificateList(event.newValue);
this.update();
}
});
this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => {
this.updatableFqbns = fqbns;
this.update();
});
this.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => {
this.availableBoards = availableBoards;
this.update();
});
}
private addCertificate(certificate: string) {
const certString = sanifyCertString(certificate);
if (certString.length > 0) {
this.certificates.push(sanifyCertString(certificate));
}
this.preferenceService.set(
'arduino.board.certificates',
this.certificates.join(','),
PreferenceScope.User
);
}
protected openContextMenu(x: number, y: number, cert: string): void {
this.commandRegistry.executeCommand(
'arduino-certificate-open-context',
Object.assign({}, { x, y, cert })
);
}
protected uploadCertificates(
fqbn: string,
address: string,
urls: string[]
): Promise<any> {
this.busyCallback(true);
return this.commandRegistry
.executeCommand('arduino-certificate-upload', {
fqbn,
address,
urls,
})
.finally(() => this.busyCallback(false));
}
protected render(): React.ReactNode {
return (
<CertificateUploaderComponent
availableBoards={this.availableBoards}
certificates={this.certificates}
updatableFqbns={this.updatableFqbns}
addCertificate={this.addCertificate.bind(this)}
uploadCertificates={this.uploadCertificates.bind(this)}
openContextMenu={this.openContextMenu.bind(this)}
/>
);
}
}
@injectable()
export class UploadCertificateDialogProps extends DialogProps {}
@injectable()
export class UploadCertificateDialog extends AbstractDialog<void> {
@inject(UploadCertificateDialogWidget)
protected readonly widget: UploadCertificateDialogWidget;
private busy = false;
constructor(
@inject(UploadCertificateDialogProps)
protected readonly props: UploadCertificateDialogProps
) {
super({
title: nls.localize(
'arduino/certificate/uploadRootCertificates',
'Upload SSL Root Certificates'
),
});
this.contentNode.classList.add('certificate-uploader-dialog');
this.acceptButton = undefined;
}
get value(): void {
return;
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
this.widget.busyCallback = this.busyCallback.bind(this);
super.onAfterAttach(msg);
this.update();
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.widget.activate();
}
protected handleEnter(event: KeyboardEvent): boolean | void {
return false;
}
close(): void {
if (this.busy) {
return;
}
super.close();
}
busyCallback(busy: boolean): void {
this.busy = busy;
if (busy) {
this.closeCrossNode.classList.add('disabled');
} else {
this.closeCrossNode.classList.remove('disabled');
}
}
}

View File

@@ -0,0 +1,108 @@
import { nls } from '@theia/core/lib/common';
import * as React from 'react';
import { AvailableBoard } from '../../boards/boards-service-provider';
import { ArduinoSelect } from '../../widgets/arduino-select';
type BoardOption = { value: string; label: string };
export const SelectBoardComponent = ({
availableBoards,
updatableFqbns,
onBoardSelect,
selectedBoard,
busy,
}: {
availableBoards: AvailableBoard[];
updatableFqbns: string[];
onBoardSelect: (board: AvailableBoard | null) => void;
selectedBoard: AvailableBoard | null;
busy: boolean;
}): React.ReactElement => {
const [selectOptions, setSelectOptions] = React.useState<BoardOption[]>([]);
const [selectBoardPlaceholder, setSelectBoardPlaceholder] =
React.useState('');
const selectOption = React.useCallback(
(boardOpt: BoardOption) => {
onBoardSelect(
(boardOpt &&
availableBoards.find((board) => board.fqbn === boardOpt.value)) ||
null
);
},
[availableBoards, onBoardSelect]
);
React.useEffect(() => {
// if there is activity going on, skip updating the boards (avoid flickering)
if (busy) {
return;
}
let placeholderTxt = nls.localize(
'arduino/certificate/selectBoard',
'Select a board...'
);
let selBoard = -1;
const updatableBoards = availableBoards.filter(
(board) => board.port && board.fqbn && updatableFqbns.includes(board.fqbn)
);
const boardsList: BoardOption[] = updatableBoards.map((board, i) => {
if (board.selected) {
selBoard = i;
}
return {
label: nls.localize(
'arduino/certificate/boardAtPort',
'{0} at {1}',
board.name,
board.port?.address ?? ''
),
value: board.fqbn || '',
};
});
if (boardsList.length === 0) {
placeholderTxt = nls.localize(
'arduino/certificate/noSupportedBoardConnected',
'No supported board connected'
);
}
setSelectBoardPlaceholder(placeholderTxt);
setSelectOptions(boardsList);
if (selectedBoard) {
selBoard = boardsList
.map((boardOpt) => boardOpt.value)
.indexOf(selectedBoard.fqbn || '');
}
selectOption(boardsList[selBoard] || null);
}, [busy, availableBoards, selectOption, updatableFqbns, selectedBoard]);
return (
<ArduinoSelect
id="board-select"
menuPosition="fixed"
isDisabled={selectOptions.length === 0 || busy}
placeholder={selectBoardPlaceholder}
options={selectOptions}
value={
(selectedBoard && {
value: selectedBoard.fqbn,
label: nls.localize(
'arduino/certificate/boardAtPort',
'{0} at {1}',
selectedBoard.name,
selectedBoard.port?.address ?? ''
),
}) ||
null
}
tabSelectsValue={false}
onChange={selectOption}
/>
);
};

View File

@@ -0,0 +1,38 @@
export const arduinoCert = 'arduino.cc:443';
export function sanifyCertString(cert: string): string {
const regex = /^(?:.*:\/\/)*(\S+\.+[^:]*):*(\d*)*$/gm;
const m = regex.exec(cert);
if (!m) {
return '';
}
const domain = m[1] || '';
const port = m[2] || '443';
if (domain.length === 0 || port.length === 0) {
return '';
}
return `${domain}:${port}`;
}
export function certificateList(certificates: string): string[] {
let certs = certificates
.split(',')
.map((cert) => sanifyCertString(cert.trim()))
.filter((cert) => {
// remove empty certificates
if (!cert || cert.length === 0) {
return false;
}
return true;
});
// add arduino certificate at the top of the list
certs = certs.filter((cert) => cert !== arduinoCert);
certs.unshift(arduinoCert);
return certs;
}

View File

@@ -3,12 +3,10 @@ import { inject, injectable } from 'inversify';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { clipboard } from 'electron';
import {
AbstractDialog,
ReactWidget,
DialogProps,
} from '@theia/core/lib/browser';
import { ReactWidget, DialogProps } from '@theia/core/lib/browser';
import { AbstractDialog } from '../theia/dialogs/dialogs';
import { CreateApi } from '../create/create-api';
import { nls } from '@theia/core/lib/common';
const RadioButton = (props: {
id: string;
@@ -42,10 +40,6 @@ export const ShareSketchComponent = ({
createApi: CreateApi;
domain?: string;
}): React.ReactElement => {
// const [publicVisibility, setPublicVisibility] = React.useState<boolean>(
// treeNode.isPublic
// );
const [loading, setloading] = React.useState<boolean>(false);
const radioChangeHandler = async (event: React.BaseSyntheticEvent) => {
@@ -66,12 +60,20 @@ export const ShareSketchComponent = ({
return (
<div id="widget-container arduino-sharesketch-dialog">
<p>Choose visibility of your Sketch:</p>
<p>
{nls.localize(
'arduino/cloud/chooseSketchVisibility',
'Choose visibility of your Sketch:'
)}
</p>
<RadioButton
changed={radioChangeHandler}
id="1"
isSelected={treeNode.isPublic === false}
label="Private. Only you can view the Sketch."
label={nls.localize(
'arduino/cloud/privateVisibility',
'Private. Only you can view the Sketch.'
)}
value="private"
isDisabled={loading}
/>
@@ -79,14 +81,17 @@ export const ShareSketchComponent = ({
changed={radioChangeHandler}
id="2"
isSelected={treeNode.isPublic === true}
label="Public. Anyone with the link can view the Sketch."
label={nls.localize(
'arduino/cloud/publicVisibility',
'Public. Anyone with the link can view the Sketch.'
)}
value="public"
isDisabled={loading}
/>
{treeNode.isPublic && (
<div>
<p>Link:</p>
<p>{nls.localize('arduino/cloud/link', 'Link:')}</p>
<div className="sketch-link">
<input
type="text"
@@ -99,10 +104,10 @@ export const ShareSketchComponent = ({
value="copy"
className="theia-button secondary"
>
Copy
{nls.localize('vscode/textInputActions/copy', 'Copy')}
</button>
</div>
<p>Embed:</p>
<p>{nls.localize('arduino/cloud/embed', 'Embed:')}</p>
<div className="sketch-link-embed">
<textarea
readOnly

View File

@@ -6,9 +6,10 @@ import {
ConfirmDialogProps,
DialogError,
} from '@theia/core/lib/browser/dialogs';
import { nls } from '@theia/core/lib/common';
@injectable()
export class DoNotAskAgainConfirmDialogProps extends ConfirmDialogProps {
export class DoNotAskAgainDialogProps extends ConfirmDialogProps {
readonly onAccept: () => Promise<void>;
}
@@ -17,8 +18,8 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
protected readonly doNotAskAgainCheckbox: HTMLInputElement;
constructor(
@inject(DoNotAskAgainConfirmDialogProps)
protected readonly props: DoNotAskAgainConfirmDialogProps
@inject(DoNotAskAgainDialogProps)
protected readonly props: DoNotAskAgainDialogProps
) {
super(props);
this.controlPanel.removeChild(this.errorMessageNode);
@@ -31,7 +32,10 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
const doNotAskAgainLabel = document.createElement('label');
doNotAskAgainLabel.classList.add('flex-line');
doNotAskAgainNode.appendChild(doNotAskAgainLabel);
doNotAskAgainLabel.textContent = "Don't ask again";
doNotAskAgainLabel.textContent = nls.localize(
'arduino/dialog/dontAskAgain',
"Don't ask again"
);
this.doNotAskAgainCheckbox = document.createElement('input');
this.doNotAskAgainCheckbox.setAttribute('align-self', 'center');
doNotAskAgainLabel.appendChild(this.doNotAskAgainCheckbox);

View File

@@ -0,0 +1,222 @@
import { nls } from '@theia/core/lib/common';
import * as React from 'react';
import {
ArduinoFirmwareUploader,
FirmwareInfo,
} from '../../../common/protocol/arduino-firmware-uploader';
import { AvailableBoard } from '../../boards/boards-service-provider';
import { ArduinoSelect } from '../../widgets/arduino-select';
import { SelectBoardComponent } from '../certificate-uploader/select-board-components';
type FirmwareOption = { value: string; label: string };
export const FirmwareUploaderComponent = ({
availableBoards,
firmwareUploader,
updatableFqbns,
flashFirmware,
isOpen,
}: {
availableBoards: AvailableBoard[];
firmwareUploader: ArduinoFirmwareUploader;
updatableFqbns: string[];
flashFirmware: (firmware: FirmwareInfo, port: string) => Promise<any>;
isOpen: any;
}): React.ReactElement => {
// boolean states for buttons
const [firmwaresFetching, setFirmwaresFetching] = React.useState(false);
const [installFeedback, setInstallFeedback] = React.useState<
'ok' | 'fail' | 'installing' | null
>(null);
const [selectedBoard, setSelectedBoard] =
React.useState<AvailableBoard | null>(null);
const [availableFirmwares, setAvailableFirmwares] = React.useState<
FirmwareInfo[]
>([]);
React.useEffect(() => {
setAvailableFirmwares([]);
}, [isOpen]);
const [selectedFirmware, setSelectedFirmware] =
React.useState<FirmwareOption | null>(null);
const [firmwareOptions, setFirmwareOptions] = React.useState<
FirmwareOption[]
>([]);
const fetchFirmwares = React.useCallback(async () => {
setInstallFeedback(null);
setFirmwaresFetching(true);
if (!selectedBoard) {
return;
}
// fetch the firmwares for the selected board
const firmwaresForFqbn = await firmwareUploader.availableFirmwares(
selectedBoard.fqbn || ''
);
setAvailableFirmwares(firmwaresForFqbn);
const firmwaresOpts = firmwaresForFqbn.map((f) => ({
label: f.firmware_version,
value: f.firmware_version,
}));
setFirmwareOptions(firmwaresOpts);
if (firmwaresForFqbn.length > 0) setSelectedFirmware(firmwaresOpts[0]);
setFirmwaresFetching(false);
}, [firmwareUploader, selectedBoard]);
const installFirmware = React.useCallback(async () => {
setInstallFeedback('installing');
const firmwareToFlash = availableFirmwares.find(
(firmware) => firmware.firmware_version === selectedFirmware?.value
);
try {
const installStatus =
!!firmwareToFlash &&
!!selectedBoard?.port &&
(await flashFirmware(firmwareToFlash, selectedBoard?.port.address));
setInstallFeedback((installStatus && 'ok') || 'fail');
} catch {
setInstallFeedback('fail');
}
}, [firmwareUploader, selectedBoard, selectedFirmware, availableFirmwares]);
const onBoardSelect = React.useCallback(
(board: AvailableBoard) => {
const newFqbn = (board && board.fqbn) || null;
const prevFqbn = (selectedBoard && selectedBoard.fqbn) || null;
if (newFqbn !== prevFqbn) {
setInstallFeedback(null);
setAvailableFirmwares([]);
setSelectedBoard(board);
}
},
[selectedBoard]
);
return (
<>
<div className="dialogSection">
<div className="dialogRow">
<label htmlFor="board-select">
{nls.localize('arduino/firmware/selectBoard', 'Select Board')}
</label>
</div>
<div className="dialogRow">
<div className="fl1">
<SelectBoardComponent
availableBoards={availableBoards}
updatableFqbns={updatableFqbns}
onBoardSelect={onBoardSelect}
selectedBoard={selectedBoard}
busy={installFeedback === 'installing'}
/>
</div>
<button
type="button"
className="theia-button secondary"
disabled={
selectedBoard === null ||
firmwaresFetching ||
installFeedback === 'installing'
}
onClick={fetchFirmwares}
>
{nls.localize('arduino/firmware/checkUpdates', 'Check Updates')}
</button>
</div>
</div>
{availableFirmwares.length > 0 && (
<>
<div className="dialogSection">
<div className="dialogRow">
<label htmlFor="firmware-select" className="fl1">
{nls.localize(
'arduino/firmware/selectVersion',
'Select firmware version'
)}
</label>
<ArduinoSelect
id="firmware-select"
menuPosition="fixed"
isDisabled={
!selectedBoard ||
firmwaresFetching ||
installFeedback === 'installing'
}
options={firmwareOptions}
value={selectedFirmware}
tabSelectsValue={false}
onChange={(value) => {
if (value) {
setInstallFeedback(null);
setSelectedFirmware(value);
}
}}
/>
<button
type="button"
className="theia-button primary"
disabled={
selectedFirmware === null ||
firmwaresFetching ||
installFeedback === 'installing'
}
onClick={installFirmware}
>
{nls.localize('arduino/firmware/install', 'Install')}
</button>
</div>
</div>
<div className="dialogSection">
{installFeedback === null && (
<div className="dialogRow warn">
<i className="fa fa-exclamation status-icon" />
{nls.localize(
'arduino/firmware/overwriteSketch',
'Installation will overwrite the Sketch on the board.'
)}
</div>
)}
{installFeedback === 'installing' && (
<div className="dialogRow success">
<div className="spinner" />
{nls.localize(
'arduino/firmware/installingFirmware',
'Installing firmware.'
)}
</div>
)}
{installFeedback === 'ok' && (
<div className="dialogRow success">
<i className="fa fa-info status-icon" />
{nls.localize(
'arduino/firmware/successfullyInstalled',
'Firmware succesfully installed.'
)}
</div>
)}
{installFeedback === 'fail' && (
<div className="dialogRow warn">
<i className="fa fa-exclamation status-icon" />
{nls.localize(
'arduino/firmware/failedInstall',
'Installation failed. Please try again.'
)}
</div>
)}
</div>
</>
)}
</>
);
};

View File

@@ -0,0 +1,142 @@
import * as React from 'react';
import { inject, injectable, postConstruct } from 'inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import {
AvailableBoard,
BoardsServiceProvider,
} from '../../boards/boards-service-provider';
import {
ArduinoFirmwareUploader,
FirmwareInfo,
} from '../../../common/protocol/arduino-firmware-uploader';
import { FirmwareUploaderComponent } from './firmware-uploader-component';
import { UploadFirmware } from '../../contributions/upload-firmware';
@injectable()
export class UploadFirmwareDialogWidget extends ReactWidget {
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(ArduinoFirmwareUploader)
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
protected updatableFqbns: string[] = [];
protected availableBoards: AvailableBoard[] = [];
protected isOpen = new Object();
public busyCallback = (busy: boolean) => {
return;
};
constructor() {
super();
}
@postConstruct()
protected init(): void {
this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => {
this.updatableFqbns = fqbns;
this.update();
});
this.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => {
this.availableBoards = availableBoards;
this.update();
});
}
protected flashFirmware(firmware: FirmwareInfo, port: string): Promise<any> {
this.busyCallback(true);
return this.arduinoFirmwareUploader
.flash(firmware, port)
.finally(() => this.busyCallback(false));
}
onCloseRequest(msg: Message): void {
super.onCloseRequest(msg);
this.isOpen = new Object();
}
protected render(): React.ReactNode {
return (
<form>
<FirmwareUploaderComponent
availableBoards={this.availableBoards}
firmwareUploader={this.arduinoFirmwareUploader}
flashFirmware={this.flashFirmware.bind(this)}
updatableFqbns={this.updatableFqbns}
isOpen={this.isOpen}
/>
</form>
);
}
}
@injectable()
export class UploadFirmwareDialogProps extends DialogProps {}
@injectable()
export class UploadFirmwareDialog extends AbstractDialog<void> {
@inject(UploadFirmwareDialogWidget)
protected readonly widget: UploadFirmwareDialogWidget;
private busy = false;
constructor(
@inject(UploadFirmwareDialogProps)
protected readonly props: UploadFirmwareDialogProps
) {
super({ title: UploadFirmware.Commands.OPEN.label || '' });
this.contentNode.classList.add('firmware-uploader-dialog');
this.acceptButton = undefined;
}
get value(): void {
return;
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
this.widget.busyCallback = this.busyCallback.bind(this);
super.onAfterAttach(msg);
this.update();
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.widget.activate();
}
protected handleEnter(event: KeyboardEvent): boolean | void {
return false;
}
close(): void {
if (this.busy) {
return;
}
this.widget.close();
super.close();
}
busyCallback(busy: boolean): void {
this.busy = busy;
if (busy) {
this.closeCrossNode.classList.add('disabled');
} else {
this.closeCrossNode.classList.remove('disabled');
}
}
}

View File

@@ -1,308 +1,22 @@
import * as React from 'react';
import { injectable, inject, postConstruct } from 'inversify';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
import { Disable } from 'react-disable';
import URI from '@theia/core/lib/common/uri';
import { Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { deepClone } from '@theia/core/lib/common/objects';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { MaybePromise } from '@theia/core/lib/common/types';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog/file-dialog-service';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import {
AbstractDialog,
DialogProps,
PreferenceService,
PreferenceScope,
DialogError,
ReactWidget,
} from '@theia/core/lib/browser';
import { Index } from '../common/types';
import {
CompilerWarnings,
CompilerWarningLiterals,
ConfigService,
FileSystemExt,
Network,
ProxySettings,
} from '../common/protocol';
export interface Settings extends Index {
editorFontSize: number; // `editor.fontSize`
themeId: string; // `workbench.colorTheme`
autoSave: 'on' | 'off'; // `editor.autoSave`
quickSuggestions: Record<'other' | 'comments' | 'strings', boolean>; // `editor.quickSuggestions`
autoScaleInterface: boolean; // `arduino.window.autoScale`
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
verboseOnCompile: boolean; // `arduino.compile.verbose`
compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
verboseOnUpload: boolean; // `arduino.upload.verbose`
verifyAfterUpload: boolean; // `arduino.upload.verify`
enableLsLogs: boolean; // `arduino.language.log`
sketchbookShowAllFiles: boolean; // `arduino.sketchbook.showAllFiles`
sketchbookPath: string; // CLI
additionalUrls: string[]; // CLI
network: Network; // CLI
}
export namespace Settings {
export function belongsToCli<K extends keyof Settings>(key: K): boolean {
return key === 'sketchbookPath' || key === 'additionalUrls';
}
}
@injectable()
export class SettingsService {
@inject(FileService)
protected readonly fileService: FileService;
@inject(FileSystemExt)
protected readonly fileSystemExt: FileSystemExt;
@inject(ConfigService)
protected readonly configService: ConfigService;
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@inject(FrontendApplicationStateService)
protected readonly appStateService: FrontendApplicationStateService;
protected readonly onDidChangeEmitter = new Emitter<Readonly<Settings>>();
readonly onDidChange = this.onDidChangeEmitter.event;
protected ready = new Deferred<void>();
protected _settings: Settings;
@postConstruct()
protected async init(): Promise<void> {
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
const settings = await this.loadSettings();
this._settings = deepClone(settings);
this.ready.resolve();
}
protected async loadSettings(): Promise<Settings> {
await this.preferenceService.ready;
const [
editorFontSize,
themeId,
autoSave,
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
verifyAfterUpload,
enableLsLogs,
sketchbookShowAllFiles,
cliConfig,
] = await Promise.all([
this.preferenceService.get<number>('editor.fontSize', 12),
this.preferenceService.get<string>(
'workbench.colorTheme',
'arduino-theme'
),
this.preferenceService.get<'on' | 'off'>('editor.autoSave', 'on'),
this.preferenceService.get<Record<string, unknown>>(
'editor.quickSuggestion',
{
other: false,
comments: false,
strings: false,
}
),
this.preferenceService.get<boolean>('arduino.window.autoScale', true),
this.preferenceService.get<number>('arduino.window.zoomLevel', 0),
// this.preferenceService.get<string>('arduino.ide.autoUpdate', true),
this.preferenceService.get<boolean>('arduino.compile.verbose', true),
this.preferenceService.get<any>('arduino.compile.warnings', 'None'),
this.preferenceService.get<boolean>('arduino.upload.verbose', true),
this.preferenceService.get<boolean>('arduino.upload.verify', true),
this.preferenceService.get<boolean>('arduino.language.log', true),
this.preferenceService.get<boolean>(
'arduino.sketchbook.showAllFiles',
false
),
this.configService.getConfiguration(),
]);
const { additionalUrls, sketchDirUri, network } = cliConfig;
const sketchbookPath = await this.fileService.fsPath(new URI(sketchDirUri));
return {
editorFontSize,
themeId,
autoSave,
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
verifyAfterUpload,
enableLsLogs,
sketchbookShowAllFiles,
additionalUrls,
sketchbookPath,
network,
};
}
async settings(): Promise<Settings> {
await this.ready.promise;
return this._settings;
}
async update(settings: Settings, fireDidChange = false): Promise<void> {
await this.ready.promise;
for (const key of Object.keys(settings)) {
this._settings[key] = settings[key];
}
if (fireDidChange) {
this.onDidChangeEmitter.fire(this._settings);
}
}
async reset(): Promise<void> {
const settings = await this.loadSettings();
return this.update(settings, true);
}
async validate(
settings: MaybePromise<Settings> = this.settings()
): Promise<string | true> {
try {
const { sketchbookPath, editorFontSize, themeId } = await settings;
const sketchbookDir = await this.fileSystemExt.getUri(sketchbookPath);
if (!(await this.fileService.exists(new URI(sketchbookDir)))) {
return `Invalid sketchbook location: ${sketchbookPath}`;
}
if (editorFontSize <= 0) {
return 'Invalid editor font size. It must be a positive integer.';
}
if (
!ThemeService.get()
.getThemes()
.find(({ id }) => id === themeId)
) {
return 'Invalid theme.';
}
return true;
} catch (err) {
if (err instanceof Error) {
return err.message;
}
return String(err);
}
}
async save(): Promise<string | true> {
await this.ready.promise;
const {
editorFontSize,
themeId,
autoSave,
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
verifyAfterUpload,
enableLsLogs,
sketchbookPath,
additionalUrls,
network,
sketchbookShowAllFiles,
} = this._settings;
const [config, sketchDirUri] = await Promise.all([
this.configService.getConfiguration(),
this.fileSystemExt.getUri(sketchbookPath),
]);
(config as any).additionalUrls = additionalUrls;
(config as any).sketchDirUri = sketchDirUri;
(config as any).network = network;
await Promise.all([
this.preferenceService.set(
'editor.fontSize',
editorFontSize,
PreferenceScope.User
),
this.preferenceService.set(
'workbench.colorTheme',
themeId,
PreferenceScope.User
),
this.preferenceService.set(
'editor.autoSave',
autoSave,
PreferenceScope.User
),
this.preferenceService.set(
'editor.quickSuggestions',
quickSuggestions,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.window.autoScale',
autoScaleInterface,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.window.zoomLevel',
interfaceScale,
PreferenceScope.User
),
// this.preferenceService.set('arduino.ide.autoUpdate', checkForUpdates, PreferenceScope.User),
this.preferenceService.set(
'arduino.compile.verbose',
verboseOnCompile,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.compile.warnings',
compilerWarnings,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.upload.verbose',
verboseOnUpload,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.upload.verify',
verifyAfterUpload,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.language.log',
enableLsLogs,
PreferenceScope.User
),
this.preferenceService.set(
'arduino.sketchbook.showAllFiles',
sketchbookShowAllFiles,
PreferenceScope.User
),
this.configService.setConfiguration(config),
]);
this.onDidChangeEmitter.fire(this._settings);
return true;
}
}
} from '../../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { Settings, SettingsService } from './settings';
import { AdditionalUrlsDialog } from './settings-dialog';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
export class SettingsComponent extends React.Component<
SettingsComponent.Props,
@@ -349,8 +63,8 @@ export class SettingsComponent extends React.Component<
return (
<Tabs>
<TabList>
<Tab>Settings</Tab>
<Tab>Network</Tab>
<Tab>{nls.localize('vscode/settingsTree/settings', 'Settings')}</Tab>
<Tab>{nls.localize('arduino/preferences/network', 'Network')}</Tab>
</TabList>
<TabPanel>{this.renderSettings()}</TabPanel>
<TabPanel>{this.renderNetwork()}</TabPanel>
@@ -361,7 +75,10 @@ export class SettingsComponent extends React.Component<
protected renderSettings(): React.ReactNode {
return (
<div className="content noselect">
Sketchbook location:
{nls.localize(
'arduino/preferences/sketchbook.location',
'Sketchbook location'
) + ':'}
<div className="flex-line">
<input
className="theia-input stretch"
@@ -373,7 +90,7 @@ export class SettingsComponent extends React.Component<
className="theia-button shrink"
onClick={this.browseSketchbookDidClick}
>
Browse
{nls.localize('arduino/preferences/browse', 'Browse')}
</button>
</div>
<label className="flex-line">
@@ -382,15 +99,49 @@ export class SettingsComponent extends React.Component<
checked={this.state.sketchbookShowAllFiles === true}
onChange={this.sketchbookShowAllFilesDidChange}
/>
Show files inside Sketches
{nls.localize(
'arduino/preferences/files.inside.sketches',
'Show files inside Sketches'
)}
</label>
<div className="flex-line">
<div className="column">
<div className="flex-line">Editor font size:</div>
<div className="flex-line">Interface scale:</div>
<div className="flex-line">Theme:</div>
<div className="flex-line">Show verbose output during:</div>
<div className="flex-line">Compiler warnings:</div>
<div className="flex-line">
{nls.localize(
'arduino/preferences/editorFontSize',
'Editor font size'
) + ':'}
</div>
<div className="flex-line">
{nls.localize(
'arduino/preferences/interfaceScale',
'Interface scale'
) + ':'}
</div>
<div className="flex-line">
{nls.localize(
'vscode/themes.contribution/selectTheme.label',
'Theme'
) + ':'}
</div>
<div className="flex-line">
{nls.localize(
'vscode/editorStatus/status.editor.mode',
'Language'
) + ':'}
</div>
<div className="flex-line">
{nls.localize(
'arduino/preferences/showVerbose',
'Show verbose output during'
)}
</div>
<div className="flex-line">
{nls.localize(
'arduino/preferences/compilerWarnings',
'Compiler warnings'
)}
</div>
</div>
<div className="column">
<div className="flex-line">
@@ -411,7 +162,7 @@ export class SettingsComponent extends React.Component<
checked={this.state.autoScaleInterface}
onChange={this.autoScaleInterfaceDidChange}
/>
Automatic
{nls.localize('arduino/preferences/automatic', 'Automatic')}
</label>
<input
className="theia-input small with-margin"
@@ -431,7 +182,7 @@ export class SettingsComponent extends React.Component<
ThemeService.get()
.getThemes()
.find(({ id }) => id === this.state.themeId)?.label ||
'Unknown'
nls.localize('arduino/common/unknown', 'Unknown')
}
onChange={this.themeDidChange}
>
@@ -444,6 +195,27 @@ export class SettingsComponent extends React.Component<
))}
</select>
</div>
<div className="flex-line">
<select
className="theia-select"
value={this.state.currentLanguage}
onChange={this.languageDidChange}
>
{this.state.languages.map((label) => (
<option key={label} value={label}>
{label}
</option>
))}
</select>
<span style={{ marginLeft: '5px' }}>
(
{nls.localize(
'vscode/extensionsActions/reloadRequired',
'Reload required'
)}
)
</span>
</div>
<div className="flex-line">
<label className="flex-line">
<input
@@ -451,7 +223,7 @@ export class SettingsComponent extends React.Component<
checked={this.state.verboseOnCompile}
onChange={this.verboseOnCompileDidChange}
/>
compile
{nls.localize('arduino/preferences/compile', 'compile')}
</label>
<label className="flex-line">
<input
@@ -459,7 +231,7 @@ export class SettingsComponent extends React.Component<
checked={this.state.verboseOnUpload}
onChange={this.verboseOnUploadDidChange}
/>
upload
{nls.localize('arduino/preferences/upload', 'upload')}
</label>
</div>
<div className="flex-line">
@@ -483,7 +255,10 @@ export class SettingsComponent extends React.Component<
checked={this.state.verifyAfterUpload}
onChange={this.verifyAfterUploadDidChange}
/>
Verify code after upload
{nls.localize(
'arduino/preferences/verifyAfterUpload',
'Verify code after upload'
)}
</label>
<label className="flex-line">
<input
@@ -492,7 +267,10 @@ export class SettingsComponent extends React.Component<
onChange={this.checkForUpdatesDidChange}
disabled={true}
/>
Check for updates on startup
{nls.localize(
'arduino/preferences/checkForUpdates',
'Check for updates on startup'
)}
</label>
<label className="flex-line">
<input
@@ -500,7 +278,10 @@ export class SettingsComponent extends React.Component<
checked={this.state.autoSave === 'on'}
onChange={this.autoSaveDidChange}
/>
Auto save
{nls.localize(
'vscode/fileActions.contribution/miAutoSave',
'Auto save'
)}
</label>
<label className="flex-line">
<input
@@ -508,18 +289,16 @@ export class SettingsComponent extends React.Component<
checked={this.state.quickSuggestions.other === true}
onChange={this.quickSuggestionsOtherDidChange}
/>
Editor Quick Suggestions
</label>
<label className="flex-line">
<input
type="checkbox"
checked={this.state.enableLsLogs}
onChange={this.enableLsLogsDidChange}
/>
Enable language server logging
{nls.localize(
'arduino/preferences/editorQuickSuggestions',
'Editor Quick Suggestions'
)}
</label>
<div className="flex-line">
Additional boards manager URLs:
{nls.localize(
'arduino/preferences/additionalManagerURLs',
'Additional boards manager URLs'
) + ':'}
<input
className="theia-input stretch with-margin"
type="text"
@@ -545,7 +324,7 @@ export class SettingsComponent extends React.Component<
checked={this.state.network === 'none'}
onChange={this.noProxyDidChange}
/>
No proxy
{nls.localize('arduino/preferences/noProxy', 'No proxy')}
</label>
<label className="flex-line">
<input
@@ -553,7 +332,10 @@ export class SettingsComponent extends React.Component<
checked={this.state.network !== 'none'}
onChange={this.manualProxyDidChange}
/>
Manual proxy configuration
{nls.localize(
'arduino/preferences/manualProxy',
'Manual proxy configuration'
)}
</label>
</form>
{this.renderProxySettings()}
@@ -686,8 +468,11 @@ export class SettingsComponent extends React.Component<
protected browseSketchbookDidClick = async () => {
const uri = await this.props.fileDialogService.showOpenDialog({
title: 'Select new sketchbook location',
openLabel: 'Choose',
title: nls.localize(
'arduino/preferences/newSketchbookLocation',
'Select new sketchbook location'
),
openLabel: nls.localize('arduino/preferences/choose', 'Choose'),
canSelectFiles: false,
canSelectMany: false,
canSelectFolders: true,
@@ -731,12 +516,6 @@ export class SettingsComponent extends React.Component<
this.setState({ autoScaleInterface: event.target.checked });
};
protected enableLsLogsDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
this.setState({ enableLsLogs: event.target.checked });
};
protected interfaceScaleDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
@@ -799,6 +578,13 @@ export class SettingsComponent extends React.Component<
}
};
protected languageDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
const selectedLanguage = event.target.value;
this.setState({ currentLanguage: selectedLanguage });
};
protected compilerWarningsDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
@@ -921,167 +707,7 @@ export namespace SettingsComponent {
readonly fileService: FileService;
readonly fileDialogService: FileDialogService;
readonly windowService: WindowService;
readonly localizationProvider: AsyncLocalizationProvider;
}
export type State = Settings;
}
@injectable()
export class SettingsWidget extends ReactWidget {
@inject(SettingsService)
protected readonly settingsService: SettingsService;
@inject(FileService)
protected readonly fileService: FileService;
@inject(FileDialogService)
protected readonly fileDialogService: FileDialogService;
@inject(WindowService)
protected readonly windowService: WindowService;
protected render(): React.ReactNode {
return (
<SettingsComponent
settingsService={this.settingsService}
fileService={this.fileService}
fileDialogService={this.fileDialogService}
windowService={this.windowService}
/>
);
}
}
@injectable()
export class SettingsDialogProps extends DialogProps {}
@injectable()
export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
@inject(SettingsService)
protected readonly settingsService: SettingsService;
@inject(SettingsWidget)
protected readonly widget: SettingsWidget;
constructor(
@inject(SettingsDialogProps)
protected readonly props: SettingsDialogProps
) {
super(props);
this.contentNode.classList.add('arduino-settings-dialog');
this.appendCloseButton('CANCEL');
this.appendAcceptButton('OK');
}
@postConstruct()
protected init(): void {
this.toDispose.push(
this.settingsService.onDidChange(this.validate.bind(this))
);
}
protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
const result = await this.settingsService.validate(settings);
if (typeof result === 'string') {
return result;
}
return '';
}
get value(): Promise<Settings> {
return this.settingsService.settings();
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
this.toDisposeOnDetach.push(
this.settingsService.onDidChange(() => this.update())
);
super.onAfterAttach(msg);
this.update();
}
protected onUpdateRequest(msg: Message) {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
// calling settingsService.reset() in order to reload the settings from the preferenceService
// and update the UI including changes triggerd from the command palette
this.settingsService.reset();
this.widget.activate();
}
}
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
protected readonly textArea: HTMLTextAreaElement;
constructor(urls: string[], windowService: WindowService) {
super({ title: 'Additional Boards Manager URLs' });
this.contentNode.classList.add('additional-urls-dialog');
const description = document.createElement('div');
description.textContent = 'Enter additional URLs, one for each row';
description.style.marginBottom = '5px';
this.contentNode.appendChild(description);
this.textArea = document.createElement('textarea');
this.textArea.className = 'theia-input';
this.textArea.setAttribute('style', 'flex: 0;');
this.textArea.value = urls
.filter((url) => url.trim())
.filter((url) => !!url)
.join('\n');
this.textArea.wrap = 'soft';
this.textArea.cols = 90;
this.textArea.rows = 5;
this.contentNode.appendChild(this.textArea);
const anchor = document.createElement('div');
anchor.classList.add('link');
anchor.textContent = 'Click for a list of unofficial board support URLs';
anchor.style.marginTop = '5px';
anchor.style.cursor = 'pointer';
this.addEventListener(anchor, 'click', () =>
windowService.openNewWindow(
'https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls',
{ external: true }
)
);
this.contentNode.appendChild(anchor);
this.appendAcceptButton('OK');
this.appendCloseButton('Cancel');
}
get value(): string[] {
return this.textArea.value
.split('\n')
.map((url) => url.trim())
.filter((url) => !!url);
}
protected onAfterAttach(message: Message): void {
super.onAfterAttach(message);
this.addUpdateListener(this.textArea, 'input');
}
protected onActivateRequest(message: Message): void {
super.onActivateRequest(message);
this.textArea.focus();
}
protected handleEnter(event: KeyboardEvent): boolean | void {
if (event.target instanceof HTMLInputElement) {
return super.handleEnter(event);
}
return false;
}
export type State = Settings & { languages: string[] };
}

View File

@@ -0,0 +1,193 @@
import * as React from 'react';
import { injectable, inject, postConstruct } from 'inversify';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { DialogError, ReactWidget } from '@theia/core/lib/browser';
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser';
import { Settings, SettingsService } from './settings';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog/file-dialog-service';
import { nls } from '@theia/core/lib/common';
import { SettingsComponent } from './settings-component';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
@injectable()
export class SettingsWidget extends ReactWidget {
@inject(SettingsService)
protected readonly settingsService: SettingsService;
@inject(FileService)
protected readonly fileService: FileService;
@inject(FileDialogService)
protected readonly fileDialogService: FileDialogService;
@inject(WindowService)
protected readonly windowService: WindowService;
@inject(AsyncLocalizationProvider)
protected readonly localizationProvider: AsyncLocalizationProvider;
protected render(): React.ReactNode {
return (
<SettingsComponent
settingsService={this.settingsService}
fileService={this.fileService}
fileDialogService={this.fileDialogService}
windowService={this.windowService}
localizationProvider={this.localizationProvider}
/>
);
}
}
@injectable()
export class SettingsDialogProps extends DialogProps {}
@injectable()
export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
@inject(SettingsService)
protected readonly settingsService: SettingsService;
@inject(SettingsWidget)
protected readonly widget: SettingsWidget;
constructor(
@inject(SettingsDialogProps)
protected readonly props: SettingsDialogProps
) {
super(props);
this.contentNode.classList.add('arduino-settings-dialog');
this.appendCloseButton(
nls.localize('vscode/issueMainService/cancel', 'Cancel')
);
this.appendAcceptButton(nls.localize('vscode/issueMainService/ok', 'OK'));
}
@postConstruct()
protected init(): void {
this.toDispose.push(
this.settingsService.onDidChange(this.validate.bind(this))
);
}
protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
const result = await this.settingsService.validate(settings);
if (typeof result === 'string') {
return result;
}
return '';
}
get value(): Promise<Settings> {
return this.settingsService.settings();
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
this.toDisposeOnDetach.push(
this.settingsService.onDidChange(() => this.update())
);
super.onAfterAttach(msg);
this.update();
}
protected onUpdateRequest(msg: Message) {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
// calling settingsService.reset() in order to reload the settings from the preferenceService
// and update the UI including changes triggerd from the command palette
this.settingsService.reset();
this.widget.activate();
}
}
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
protected readonly textArea: HTMLTextAreaElement;
constructor(urls: string[], windowService: WindowService) {
super({
title: nls.localize(
'arduino/preferences/additionalManagerURLs',
'Additional Boards Manager URLs'
),
});
this.contentNode.classList.add('additional-urls-dialog');
const description = document.createElement('div');
description.textContent = nls.localize(
'arduino/preferences/enterAdditionalURLs',
'Enter additional URLs, one for each row'
);
description.style.marginBottom = '5px';
this.contentNode.appendChild(description);
this.textArea = document.createElement('textarea');
this.textArea.className = 'theia-input';
this.textArea.setAttribute('style', 'flex: 0;');
this.textArea.value = urls
.filter((url) => url.trim())
.filter((url) => !!url)
.join('\n');
this.textArea.wrap = 'soft';
this.textArea.cols = 90;
this.textArea.rows = 5;
this.contentNode.appendChild(this.textArea);
const anchor = document.createElement('div');
anchor.classList.add('link');
anchor.textContent = nls.localize(
'arduino/preferences/unofficialBoardSupport',
'Click for a list of unofficial board support URLs'
);
anchor.style.marginTop = '5px';
anchor.style.cursor = 'pointer';
this.addEventListener(anchor, 'click', () =>
windowService.openNewWindow(
'https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls',
{ external: true }
)
);
this.contentNode.appendChild(anchor);
this.appendAcceptButton(nls.localize('vscode/issueMainService/ok', 'OK'));
this.appendCloseButton(
nls.localize('vscode/issueMainService/cancel', 'Cancel')
);
}
get value(): string[] {
return this.textArea.value
.split('\n')
.map((url) => url.trim())
.filter((url) => !!url);
}
protected onAfterAttach(message: Message): void {
super.onAfterAttach(message);
this.addUpdateListener(this.textArea, 'input');
}
protected onActivateRequest(message: Message): void {
super.onActivateRequest(message);
this.textArea.focus();
}
protected handleEnter(event: KeyboardEvent): boolean | void {
if (event.target instanceof HTMLInputElement) {
return super.handleEnter(event);
}
return false;
}
}

View File

@@ -0,0 +1,325 @@
import { injectable, inject, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { deepClone } from '@theia/core/lib/common/objects';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { MaybePromise } from '@theia/core/lib/common/types';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { PreferenceService, PreferenceScope } from '@theia/core/lib/browser';
import { Index } from '../../../common/types';
import {
CompilerWarnings,
ConfigService,
FileSystemExt,
Network,
} from '../../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
const EDITOR_SETTING = 'editor';
const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
const ARDUINO_SETTING = 'arduino';
const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
// const IDE_SETTING = `${ARDUINO_SETTING}.ide`;
const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
// const AUTO_UPDATE_SETTING = `${IDE_SETTING}.autoUpdate`;
const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export interface Settings extends Index {
editorFontSize: number; // `editor.fontSize`
themeId: string; // `workbench.colorTheme`
autoSave: 'on' | 'off'; // `editor.autoSave`
quickSuggestions: Record<'other' | 'comments' | 'strings', boolean>; // `editor.quickSuggestions`
languages: string[]; // `languages from the plugins`
currentLanguage: string;
autoScaleInterface: boolean; // `arduino.window.autoScale`
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
verboseOnCompile: boolean; // `arduino.compile.verbose`
compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
verboseOnUpload: boolean; // `arduino.upload.verbose`
verifyAfterUpload: boolean; // `arduino.upload.verify`
sketchbookShowAllFiles: boolean; // `arduino.sketchbook.showAllFiles`
sketchbookPath: string; // CLI
additionalUrls: string[]; // CLI
network: Network; // CLI
}
export namespace Settings {
export function belongsToCli<K extends keyof Settings>(key: K): boolean {
return key === 'sketchbookPath' || key === 'additionalUrls';
}
}
@injectable()
export class SettingsService {
@inject(FileService)
protected readonly fileService: FileService;
@inject(FileSystemExt)
protected readonly fileSystemExt: FileSystemExt;
@inject(ConfigService)
protected readonly configService: ConfigService;
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@inject(FrontendApplicationStateService)
protected readonly appStateService: FrontendApplicationStateService;
@inject(AsyncLocalizationProvider)
protected readonly localizationProvider: AsyncLocalizationProvider;
protected readonly onDidChangeEmitter = new Emitter<Readonly<Settings>>();
readonly onDidChange = this.onDidChangeEmitter.event;
protected ready = new Deferred<void>();
protected _settings: Settings;
@postConstruct()
protected async init(): Promise<void> {
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
const settings = await this.loadSettings();
this._settings = deepClone(settings);
this.ready.resolve();
}
protected async loadSettings(): Promise<Settings> {
await this.preferenceService.ready;
const [
languages,
currentLanguage,
editorFontSize,
themeId,
autoSave,
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
verifyAfterUpload,
sketchbookShowAllFiles,
cliConfig,
] = await Promise.all([
['en', ...(await this.localizationProvider.getAvailableLanguages())],
this.localizationProvider.getCurrentLanguage(),
this.preferenceService.get<number>(FONT_SIZE_SETTING, 12),
this.preferenceService.get<string>(
'workbench.colorTheme',
'arduino-theme'
),
this.preferenceService.get<'on' | 'off'>(AUTO_SAVE_SETTING, 'on'),
this.preferenceService.get<
Record<'other' | 'comments' | 'strings', boolean>
>(QUICK_SUGGESTIONS_SETTING, {
other: false,
comments: false,
strings: false,
}),
this.preferenceService.get<boolean>(AUTO_SCALE_SETTING, true),
this.preferenceService.get<number>(ZOOM_LEVEL_SETTING, 0),
// this.preferenceService.get<string>(AUTO_UPDATE_SETTING, true),
this.preferenceService.get<boolean>(COMPILE_VERBOSE_SETTING, true),
this.preferenceService.get<any>(COMPILE_WARNINGS_SETTING, 'None'),
this.preferenceService.get<boolean>(UPLOAD_VERBOSE_SETTING, true),
this.preferenceService.get<boolean>(UPLOAD_VERIFY_SETTING, true),
this.preferenceService.get<boolean>(SHOW_ALL_FILES_SETTING, false),
this.configService.getConfiguration(),
]);
const { additionalUrls, sketchDirUri, network } = cliConfig;
const sketchbookPath = await this.fileService.fsPath(new URI(sketchDirUri));
return {
editorFontSize,
themeId,
languages,
currentLanguage,
autoSave,
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
verifyAfterUpload,
sketchbookShowAllFiles,
additionalUrls,
sketchbookPath,
network,
};
}
async settings(): Promise<Settings> {
await this.ready.promise;
return this._settings;
}
async update(settings: Settings, fireDidChange = false): Promise<void> {
await this.ready.promise;
for (const key of Object.keys(settings)) {
this._settings[key] = settings[key];
}
if (fireDidChange) {
this.onDidChangeEmitter.fire(this._settings);
}
}
async reset(): Promise<void> {
const settings = await this.loadSettings();
return this.update(settings, true);
}
async validate(
settings: MaybePromise<Settings> = this.settings()
): Promise<string | true> {
try {
const { sketchbookPath, editorFontSize, themeId } = await settings;
const sketchbookDir = await this.fileSystemExt.getUri(sketchbookPath);
if (!(await this.fileService.exists(new URI(sketchbookDir)))) {
return nls.localize(
'arduino/preferences/invalid.sketchbook.location',
'Invalid sketchbook location: {0}',
sketchbookPath
);
}
if (editorFontSize <= 0) {
return nls.localize(
'arduino/preferences/invalid.editorFontSize',
'Invalid editor font size. It must be a positive integer.'
);
}
if (
!ThemeService.get()
.getThemes()
.find(({ id }) => id === themeId)
) {
return nls.localize(
'arduino/preferences/invalid.theme',
'Invalid theme.'
);
}
return true;
} catch (err) {
if (err instanceof Error) {
return err.message;
}
return String(err);
}
}
async save(): Promise<string | true> {
await this.ready.promise;
const {
currentLanguage,
editorFontSize,
themeId,
autoSave,
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
verifyAfterUpload,
sketchbookPath,
additionalUrls,
network,
sketchbookShowAllFiles,
} = this._settings;
const [config, sketchDirUri] = await Promise.all([
this.configService.getConfiguration(),
this.fileSystemExt.getUri(sketchbookPath),
]);
(config as any).additionalUrls = additionalUrls;
(config as any).sketchDirUri = sketchDirUri;
(config as any).network = network;
(config as any).locale = currentLanguage;
await Promise.all([
this.preferenceService.set(
'editor.fontSize',
editorFontSize,
PreferenceScope.User
),
this.preferenceService.set(
'workbench.colorTheme',
themeId,
PreferenceScope.User
),
this.preferenceService.set(
'editor.autoSave',
autoSave,
PreferenceScope.User
),
this.preferenceService.set(
'editor.quickSuggestions',
quickSuggestions,
PreferenceScope.User
),
this.preferenceService.set(
AUTO_SCALE_SETTING,
autoScaleInterface,
PreferenceScope.User
),
this.preferenceService.set(
ZOOM_LEVEL_SETTING,
interfaceScale,
PreferenceScope.User
),
// this.preferenceService.set(AUTO_UPDATE_SETTING, checkForUpdates, PreferenceScope.User),
this.preferenceService.set(
COMPILE_VERBOSE_SETTING,
verboseOnCompile,
PreferenceScope.User
),
this.preferenceService.set(
COMPILE_WARNINGS_SETTING,
compilerWarnings,
PreferenceScope.User
),
this.preferenceService.set(
UPLOAD_VERBOSE_SETTING,
verboseOnUpload,
PreferenceScope.User
),
this.preferenceService.set(
UPLOAD_VERIFY_SETTING,
verifyAfterUpload,
PreferenceScope.User
),
this.preferenceService.set(
SHOW_ALL_FILES_SETTING,
sketchbookShowAllFiles,
PreferenceScope.User
),
this.configService.setConfiguration(config),
]);
this.onDidChangeEmitter.fire(this._settings);
// after saving all the settings, if we need to change the language we need to perform a reload
if (currentLanguage !== nls.locale) {
window.localStorage.setItem(nls.localeId, currentLanguage);
window.location.reload();
}
return true;
}
}

View File

@@ -0,0 +1,98 @@
import * as React from 'react';
import { BoardUserField } from '../../../common/protocol';
import { nls } from '@theia/core/lib/common';
export const UserFieldsComponent = ({
initialBoardUserFields,
updateUserFields,
cancel,
accept,
}: {
initialBoardUserFields: BoardUserField[];
updateUserFields: (userFields: BoardUserField[]) => void;
cancel: () => void;
accept: () => Promise<void>;
}): React.ReactElement => {
const [boardUserFields, setBoardUserFields] = React.useState<
BoardUserField[]
>(initialBoardUserFields);
const [uploadButtonDisabled, setUploadButtonDisabled] =
React.useState<boolean>(true);
React.useEffect(() => {
setBoardUserFields(initialBoardUserFields);
}, [initialBoardUserFields]);
const updateUserField =
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const newBoardUserFields = [...boardUserFields];
newBoardUserFields[index].value = e.target.value;
setBoardUserFields(newBoardUserFields);
};
const allFieldsHaveValues = (userFields: BoardUserField[]): boolean => {
return (
userFields &&
userFields.length > 0 &&
userFields
.map<boolean>((field: BoardUserField): boolean => {
return field.value.length > 0;
})
.reduce((previous: boolean, current: boolean): boolean => {
return previous && current;
})
);
};
React.useEffect(() => {
updateUserFields(boardUserFields);
setUploadButtonDisabled(!allFieldsHaveValues(boardUserFields));
}, [boardUserFields]);
return (
<div>
<div className="user-fields-container">
<div className="user-fields-list">
{boardUserFields.map((field, index) => {
return (
<div className="dialogSection" key={index}>
<div className="dialogRow">
<label className="field-label">{field.label}</label>
</div>
<div className="dialogRow">
<input
type={field.secret ? 'password' : 'text'}
value={field.value}
className="theia-input"
placeholder={'Enter ' + field.label}
onChange={updateUserField(index)}
/>
</div>
</div>
);
})}
</div>
</div>
<div className="dialogSection">
<div className="dialogRow button-container">
<button
type="button"
className="theia-button secondary install-cert-btn"
onClick={cancel}
>
{nls.localize('arduino/userFields/cancel', 'Cancel')}
</button>
<button
type="button"
className="theia-button primary install-cert-btn"
disabled={uploadButtonDisabled}
onClick={accept}
>
{nls.localize('arduino/userFields/upload', 'Upload')}
</button>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,121 @@
import * as React from 'react';
import { inject, injectable } from 'inversify';
import {
AbstractDialog,
DialogProps,
ReactWidget,
} from '@theia/core/lib/browser';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { UploadSketch } from '../../contributions/upload-sketch';
import { UserFieldsComponent } from './user-fields-component';
import { BoardUserField } from '../../../common/protocol';
@injectable()
export class UserFieldsDialogWidget extends ReactWidget {
protected _currentUserFields: BoardUserField[] = [];
constructor(private cancel: () => void, private accept: () => Promise<void>) {
super();
}
set currentUserFields(userFields: BoardUserField[]) {
this.setUserFields(userFields);
}
get currentUserFields(): BoardUserField[] {
return this._currentUserFields;
}
resetUserFieldsValue(): void {
this._currentUserFields = this._currentUserFields.map((field) => {
field.value = '';
return field;
});
}
protected setUserFields(userFields: BoardUserField[]): void {
this._currentUserFields = userFields;
}
protected render(): React.ReactNode {
return (
<form>
<UserFieldsComponent
initialBoardUserFields={this._currentUserFields}
updateUserFields={this.setUserFields.bind(this)}
cancel={this.cancel}
accept={this.accept}
/>
</form>
);
}
}
@injectable()
export class UserFieldsDialogProps extends DialogProps {}
@injectable()
export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
protected readonly widget: UserFieldsDialogWidget;
constructor(
@inject(UserFieldsDialogProps)
protected readonly props: UserFieldsDialogProps
) {
super({
title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '',
});
this.titleNode.classList.add('user-fields-dialog-title');
this.contentNode.classList.add('user-fields-dialog-content');
this.acceptButton = undefined;
this.widget = new UserFieldsDialogWidget(
this.close.bind(this),
this.accept.bind(this)
);
}
set value(userFields: BoardUserField[]) {
this.widget.currentUserFields = userFields;
}
get value(): BoardUserField[] {
return this.widget.currentUserFields;
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
super.onAfterAttach(msg);
this.update();
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.widget.activate();
}
protected async accept(): Promise<void> {
// If the user presses enter and at least
// a field is empty don't accept the input
for (const field of this.value) {
if (field.value.length === 0) {
return;
}
}
return super.accept();
}
close(): void {
this.widget.resetUserFieldsValue();
this.widget.close();
super.close();
}
}

View File

@@ -1,4 +1,7 @@
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
<!--Copyright (C) 2019 TypeFox and others.-->
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
<svg width="28" height="28" viewBox="0 0 28 28" fill="#F6F6F6" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M15.1673 18.0687V23.0247C15.1673 23.5637 15.2723 24.5 14.7315 24.5H12.8328V23.3327H14V19.6122L13.7988 19.4022C13.0604 20.0803 12.1008 20.4669 11.0986 20.49C10.0964 20.5132 9.11994 20.1714 8.351 19.5282C7 18.1737 7.13826 16.3327 8.60475 14H4.66726V15.1672H3.50001V13.2685C3.50001 12.7277 4.43626 12.8327 4.97526 12.8327H9.76326L15.1673 18.0687ZM11.6673 5.83275H10.5V4.66725H12.775C13.3123 4.66725 14 4.9245 14 5.4635V9.366L14.8593 10.3862C14.927 9.83979 15.1906 9.33644 15.6013 8.96958C16.0119 8.60271 16.5416 8.39723 17.0923 8.39125C17.2298 8.37945 17.3684 8.39492 17.5 8.43675V5.83275H18.6673V8.88825C18.703 8.99154 18.7618 9.08536 18.8391 9.16265C18.9164 9.23995 19.0102 9.29871 19.1135 9.3345H22.1673V10.5H19.5615C19.593 10.5 19.6105 10.675 19.6105 10.85C19.6058 11.4034 19.4011 11.9365 19.0341 12.3508C18.6671 12.7651 18.1626 13.0326 17.6138 13.104L18.634 14H22.5383C23.0773 14 23.3345 14.6807 23.3345 15.225V17.5H22.1673V16.3327H19.2273L11.6673 8.98275V5.83275ZM14 0C11.2311 0 8.52431 0.821086 6.22202 2.35943C3.91973 3.89776 2.12532 6.08426 1.06569 8.64243C0.00606593 11.2006 -0.271181 14.0155 0.269012 16.7313C0.809205 19.447 2.14258 21.9416 4.10051 23.8995C6.05845 25.8574 8.55301 27.1908 11.2687 27.731C13.9845 28.2712 16.7994 27.9939 19.3576 26.9343C21.9157 25.8747 24.1022 24.0803 25.6406 21.778C27.1789 19.4757 28 16.7689 28 14C28 10.287 26.525 6.72601 23.8995 4.1005C21.274 1.475 17.713 0 14 0V0ZM25.6673 14C25.6692 16.6908 24.7364 19.2988 23.0283 21.378L6.622 4.97175C8.33036 3.57269 10.4009 2.68755 12.5927 2.41935C14.7845 2.15115 17.0074 2.51091 19.0027 3.45676C20.998 4.40262 22.6836 5.89567 23.8635 7.76217C25.0433 9.62867 25.6689 11.7919 25.6673 14ZM2.33276 14C2.33066 11.3091 3.26351 8.70111 4.97176 6.622L21.378 23.03C19.6693 24.4284 17.5987 25.313 15.407 25.5807C13.2153 25.8485 10.9926 25.4884 8.99754 24.5425C7.00244 23.5965 5.31693 22.1036 4.13708 20.2373C2.95722 18.3709 2.33152 16.208 2.33276 14Z" fill="white"/></g><defs><clipPath id="clip0"><rect width="28" height="28" fill="#F6F6F6"/></clipPath></defs></svg>
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.6259 14.2462C13.4329 14.2464 13.2452 14.1827 13.0922 14.0652C12.9391 13.9476 12.8292 13.7827 12.7796 13.5962C12.73 13.4097 12.7434 13.212 12.8178 13.034C12.8922 12.8559 13.0234 12.7074 13.1909 12.6116L19.7355 8.87405L9.25 2.88232V9.25C9.25 9.48206 9.15781 9.70462 8.99372 9.86872C8.82962 10.0328 8.60706 10.125 8.375 10.125C8.14294 10.125 7.92038 10.0328 7.75628 9.86872C7.59219 9.70462 7.5 9.48206 7.5 9.25V1.375C7.49991 1.22171 7.54012 1.07108 7.61658 0.938223C7.69304 0.805362 7.80308 0.694931 7.93567 0.617993C8.06825 0.541055 8.21873 0.500314 8.37202 0.499851C8.52531 0.499389 8.67603 0.539221 8.80908 0.615357L21.9341 8.11438C22.068 8.19089 22.1793 8.30145 22.2568 8.43486C22.3342 8.56826 22.375 8.71977 22.375 8.87402C22.375 9.02827 22.3342 9.17978 22.2568 9.31319C22.1793 9.4466 22.068 9.55716 21.9341 9.63367L14.0591 14.1309C13.9273 14.2066 13.7779 14.2464 13.6259 14.2462Z" fill="#BDC7C7"/>
<path d="M11 17.125C11.2321 17.125 11.4546 17.0328 11.6187 16.8687C11.7828 16.7046 11.875 16.4821 11.875 16.25C11.875 16.0179 11.7828 15.7954 11.6187 15.6313C11.4546 15.4672 11.2321 15.375 11 15.375H10.0724C10.0297 15.0014 9.95355 14.6325 9.84495 14.2725L10.7462 13.3712C10.8278 13.2897 10.8925 13.1928 10.9367 13.0862C10.9808 12.9796 11.0035 12.8654 11.0035 12.75C11.0035 12.6346 10.9808 12.5204 10.9367 12.4138C10.8925 12.3072 10.8278 12.2104 10.7462 12.1288C10.6646 12.0472 10.5678 11.9825 10.4612 11.9383C10.3546 11.8942 10.2404 11.8714 10.125 11.8714C10.0096 11.8714 9.89537 11.8942 9.78878 11.9383C9.68219 11.9825 9.58533 12.0472 9.50375 12.1288L9.04 12.5925C8.72733 12.1175 8.30488 11.7248 7.80837 11.4477C7.31186 11.1705 6.75589 11.0169 6.1875 11C5.6191 11.0169 5.06314 11.1705 4.56663 11.4477C4.07011 11.7249 3.64766 12.1176 3.335 12.5926L2.87125 12.1288C2.70649 11.964 2.48303 11.8715 2.25002 11.8715C2.01701 11.8715 1.79355 11.964 1.62878 12.1288C1.46401 12.2935 1.37145 12.517 1.37144 12.75C1.37144 12.983 1.46399 13.2065 1.62875 13.3712L2.53 14.2725C2.4214 14.6325 2.34526 15.0014 2.3025 15.375H1.375C1.14294 15.375 0.920376 15.4672 0.756282 15.6313C0.592187 15.7954 0.5 16.0179 0.5 16.25C0.5 16.4821 0.592187 16.7046 0.756282 16.8687C0.920376 17.0328 1.14294 17.125 1.375 17.125H2.30255C2.34532 17.4986 2.42145 17.8675 2.53005 18.2275L2.5038 18.2538L1.6288 19.1288C1.46485 19.2939 1.37285 19.5172 1.37285 19.75C1.37285 19.9827 1.46485 20.206 1.6288 20.3712C1.79466 20.5338 2.01771 20.625 2.25002 20.625C2.48233 20.625 2.70537 20.5338 2.87123 20.3712L3.33498 19.9074C3.64765 20.3824 4.0701 20.7751 4.56661 21.0523C5.06313 21.3295 5.6191 21.4831 6.1875 21.5C6.7559 21.4831 7.31186 21.3295 7.80837 21.0523C8.30489 20.7751 8.72734 20.3824 9.04 19.9074L9.50375 20.3712C9.66961 20.5339 9.89265 20.625 10.125 20.625C10.3573 20.625 10.5803 20.5339 10.7462 20.3712C10.9101 20.206 11.0021 19.9827 11.0021 19.75C11.0021 19.5172 10.9101 19.2939 10.7462 19.1288L9.87118 18.2538L9.84493 18.2275C9.95353 17.8675 10.0297 17.4986 10.0724 17.125L11 17.125ZM6.1875 12.75C6.98367 12.75 7.68375 13.4588 8.06875 14.5H4.30625C4.69125 13.4588 5.39133 12.75 6.1875 12.75ZM6.1875 19.75C4.9975 19.75 4 18.1487 4 16.25H8.375C8.375 18.1487 7.3775 19.75 6.1875 19.75Z" fill="#BDC7C7"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,7 +1,8 @@
import { injectable, postConstruct, inject } from 'inversify';
import { Message } from '@phosphor/messaging';
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser/dialogs';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../theia/dialogs/dialogs';
import {
LibraryPackage,
LibraryService,
@@ -9,11 +10,15 @@ import {
import { ListWidget } from '../widgets/component-list/list-widget';
import { Installable } from '../../common/protocol';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
@injectable()
export class LibraryListWidget extends ListWidget<LibraryPackage> {
static WIDGET_ID = 'library-list-widget';
static WIDGET_LABEL = 'Library Manager';
static WIDGET_LABEL = nls.localize(
'arduino/library/title',
'Library Manager'
);
constructor(
@inject(LibraryService) protected service: LibraryService,
@@ -23,7 +28,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
super({
id: LibraryListWidget.WIDGET_ID,
label: LibraryListWidget.WIDGET_LABEL,
iconClass: 'library-tab-icon',
iconClass: 'fa fa-arduino-library',
searchable: service,
installable: service,
itemLabel: (item: LibraryPackage) => item.name,
@@ -60,11 +65,20 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
let installDependencies: boolean | undefined = undefined;
if (dependencies.length) {
const message = document.createElement('div');
message.innerHTML = `The library <b>${item.name}:${version}</b> needs ${
message.innerHTML =
dependencies.length === 1
? 'another dependency'
: 'some other dependencies'
} currently not installed:`;
? nls.localize(
'arduino/library/needsOneDependency',
'The library <b>{0}:{1}</b> needs another dependency currently not installed:',
item.name,
version
)
: nls.localize(
'arduino/library/needsMultipleDependencies',
'The library <b>{0}:{1}</b> needs some other dependencies currently not installed:',
item.name,
version
);
const listContainer = document.createElement('div');
listContainer.style.maxHeight = '300px';
listContainer.style.overflowY = 'auto';
@@ -79,16 +93,34 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
listContainer.appendChild(list);
message.appendChild(listContainer);
const question = document.createElement('div');
question.textContent = `Would you like to install ${
question.textContent =
dependencies.length === 1
? 'the missing dependency'
: 'all the missing dependencies'
}?`;
? nls.localize(
'arduino/library/installOneMissingDependency',
'Would you like to install the missing dependency?'
)
: nls.localize(
'arduino/library/installMissingDependencies',
'Would you like to install all the missing dependencies?'
);
message.appendChild(question);
const result = await new MessageBoxDialog({
title: `Dependencies for library ${item.name}:${version}`,
title: nls.localize(
'arduino/library/dependenciesForLibrary',
'Dependencies for library {0}:{1}',
item.name,
version
),
message,
buttons: ['Install all', `Install ${item.name} only`, 'Cancel'],
buttons: [
nls.localize('arduino/library/installAll', 'Install all'),
nls.localize(
'arduino/library/installOnly',
'Install {0} only',
item.name
),
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
],
maxWidth: 740, // Aligned with `settings-dialog.css`.
}).open();
@@ -115,7 +147,12 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
installDependencies,
});
this.messageService.info(
`Successfully installed library ${item.name}:${version}`,
nls.localize(
'arduino/library/installedSuccessfully',
'Successfully installed library {0}:{1}',
item.name,
version
),
{ timeout: 3000 }
);
}
@@ -130,7 +167,12 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
}): Promise<void> {
await super.uninstall({ item, progressId });
this.messageService.info(
`Successfully uninstalled library ${item.name}:${item.installedVersion}`,
nls.localize(
'arduino/library/uninstalledSuccessfully',
'Successfully uninstalled library {0}:{1}',
item.name,
item.installedVersion!
),
{ timeout: 3000 }
);
}
@@ -142,7 +184,9 @@ class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
constructor(protected readonly options: MessageBoxDialog.Options) {
super(options);
this.contentNode.appendChild(this.createMessageNode(this.options.message));
(options.buttons || ['OK']).forEach((text, index) => {
(
options.buttons || [nls.localize('vscode/issueMainService/ok', 'OK')]
).forEach((text, index) => {
const button = this.createButton(text);
button.classList.add(index === 0 ? 'main' : 'secondary');
this.controlPanel.appendChild(button);

View File

@@ -4,6 +4,7 @@ import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-con
import { MenuModelRegistry } from '@theia/core';
import { LibraryListWidget } from './library-list-widget';
import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
@injectable()
export class LibraryListWidgetFrontendContribution
@@ -31,7 +32,10 @@ export class LibraryListWidgetFrontendContribution
if (this.toggleCommand) {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: this.toggleCommand.id,
label: 'Manage Libraries...',
label: nls.localize(
'arduino/library/manageLibraries',
'Manage Libraries...'
),
order: '3',
});
}

View File

@@ -86,8 +86,13 @@ export namespace ArduinoMenus {
// -- Tools
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
// `Auto Format`, `Library Manager...`, `Boards Manager...`
// `Auto Format`, `Archive Sketch`, `Manage Libraries...`, `Serial Monitor`, Serial Plotter
export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main'];
// `WiFi101 / WiFiNINA Firmware Updater`
export const TOOLS__FIRMWARE_UPLOADER_GROUP = [
...TOOLS,
'1_firmware_uploader',
];
// `Board`, `Port`, and `Get Board Info`.
export const TOOLS__BOARD_SELECTION_GROUP = [...TOOLS, '2_board_selection'];
// Core settings, such as `Processor` and `Programmers` for the board and `Burn Bootloader`
@@ -143,6 +148,11 @@ export namespace ArduinoMenus {
...SKETCH_CONTROL__CONTEXT,
'2_resources',
];
// -- ROOT SSL CERTIFICATES
export const ROOT_CERTIFICATES__CONTEXT = [
'arduino-root-certificates--context',
];
}
/**

View File

@@ -1,354 +0,0 @@
import { injectable, inject, postConstruct } from 'inversify';
import { deepClone } from '@theia/core/lib/common/objects';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import {
MonitorService,
MonitorConfig,
MonitorError,
Status,
} from '../../common/protocol/monitor-service';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import {
Port,
Board,
BoardsService,
AttachedBoardsChangeEvent,
} from '../../common/protocol/boards-service';
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
import { BoardsConfig } from '../boards/boards-config';
import { MonitorModel } from './monitor-model';
import { NotificationCenter } from '../notification-center';
@injectable()
export class MonitorConnection {
@inject(MonitorModel)
protected readonly monitorModel: MonitorModel;
@inject(MonitorService)
protected readonly monitorService: MonitorService;
@inject(MonitorServiceClientImpl)
protected readonly monitorServiceClient: MonitorServiceClientImpl;
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
@inject(MessageService)
protected messageService: MessageService;
@inject(FrontendApplicationStateService)
protected readonly applicationState: FrontendApplicationStateService;
protected state: MonitorConnection.State | undefined;
/**
* Note: The idea is to toggle this property from the UI (`Monitor` view)
* and the boards config and the boards attachment/detachment logic can be at on place, here.
*/
protected _autoConnect = false;
protected readonly onConnectionChangedEmitter = new Emitter<
MonitorConnection.State | undefined
>();
/**
* This emitter forwards all read events **iff** the connection is established.
*/
protected readonly onReadEmitter = new Emitter<{ message: string }>();
/**
* Array for storing previous monitor errors received from the server, and based on the number of elements in this array,
* we adjust the reconnection delay.
* Super naive way: we wait `array.length * 1000` ms. Once we hit 10 errors, we do not try to reconnect and clean the array.
*/
protected monitorErrors: MonitorError[] = [];
protected reconnectTimeout?: number;
@postConstruct()
protected init(): void {
this.monitorServiceClient.onError(async (error) => {
let shouldReconnect = false;
if (this.state) {
const { code, config } = error;
const { board, port } = config;
const options = { timeout: 3000 };
switch (code) {
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
console.debug(
`Connection was canceled by client: ${MonitorConnection.State.toString(
this.state
)}.`
);
break;
}
case MonitorError.ErrorCodes.DEVICE_BUSY: {
this.messageService.warn(
`Connection failed. Serial port is busy: ${Port.toString(port)}.`,
options
);
shouldReconnect = this.autoConnect;
this.monitorErrors.push(error);
break;
}
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
this.messageService.info(
`Disconnected ${Board.toString(board, {
useFqbn: false,
})} from ${Port.toString(port)}.`,
options
);
break;
}
case undefined: {
this.messageService.error(
`Unexpected error. Reconnecting ${Board.toString(
board
)} on port ${Port.toString(port)}.`,
options
);
console.error(JSON.stringify(error));
shouldReconnect = this.connected && this.autoConnect;
break;
}
}
const oldState = this.state;
this.state = undefined;
this.onConnectionChangedEmitter.fire(this.state);
if (shouldReconnect) {
if (this.monitorErrors.length >= 10) {
this.messageService.warn(
`Failed to reconnect ${Board.toString(board, {
useFqbn: false,
})} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(
port
)} serial port is busy. after 10 consecutive attempts.`
);
this.monitorErrors.length = 0;
} else {
const attempts = this.monitorErrors.length || 1;
if (this.reconnectTimeout !== undefined) {
// Clear the previous timer.
window.clearTimeout(this.reconnectTimeout);
}
const timeout = attempts * 1000;
this.messageService.warn(
`Reconnecting ${Board.toString(board, {
useFqbn: false,
})} to ${Port.toString(port)} in ${attempts} seconds...`,
{ timeout }
);
this.reconnectTimeout = window.setTimeout(
() => this.connect(oldState.config),
timeout
);
}
}
}
});
this.boardsServiceProvider.onBoardsConfigChanged(
this.handleBoardConfigChange.bind(this)
);
this.notificationCenter.onAttachedBoardsChanged((event) => {
if (this.autoConnect && this.connected) {
const { boardsConfig } = this.boardsServiceProvider;
if (
this.boardsServiceProvider.canUploadTo(boardsConfig, {
silent: false,
})
) {
const { attached } = AttachedBoardsChangeEvent.diff(event);
if (
attached.boards.some(
(board) =>
!!board.port && BoardsConfig.Config.sameAs(boardsConfig, board)
)
) {
const { selectedBoard: board, selectedPort: port } = boardsConfig;
const { baudRate } = this.monitorModel;
this.disconnect().then(() =>
this.connect({ board, port, baudRate })
);
}
}
}
});
// Handles the `baudRate` changes by reconnecting if required.
this.monitorModel.onChange(({ property }) => {
if (property === 'baudRate' && this.autoConnect && this.connected) {
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
}
});
}
get connected(): boolean {
return !!this.state;
}
get monitorConfig(): MonitorConfig | undefined {
return this.state ? this.state.config : undefined;
}
get autoConnect(): boolean {
return this._autoConnect;
}
set autoConnect(value: boolean) {
const oldValue = this._autoConnect;
this._autoConnect = value;
// When we enable the auto-connect, we have to connect
if (!oldValue && value) {
// We have to make sure the previous boards config has been restored.
// Otherwise, we might start the auto-connection without configured boards.
this.applicationState.reachedState('started_contributions').then(() => {
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
});
} else if (oldValue && !value) {
if (this.reconnectTimeout !== undefined) {
window.clearTimeout(this.reconnectTimeout);
this.monitorErrors.length = 0;
}
}
}
async connect(config: MonitorConfig): Promise<Status> {
if (this.connected) {
const disconnectStatus = await this.disconnect();
if (!Status.isOK(disconnectStatus)) {
return disconnectStatus;
}
}
console.info(
`>>> Creating serial monitor connection for ${Board.toString(
config.board
)} on port ${Port.toString(config.port)}...`
);
const connectStatus = await this.monitorService.connect(config);
if (Status.isOK(connectStatus)) {
const requestMessage = () => {
this.monitorService.request().then(({ message }) => {
if (this.connected) {
this.onReadEmitter.fire({ message });
requestMessage();
}
});
};
requestMessage();
this.state = { config };
console.info(
`<<< Serial monitor connection created for ${Board.toString(
config.board,
{ useFqbn: false }
)} on port ${Port.toString(config.port)}.`
);
}
this.onConnectionChangedEmitter.fire(this.state);
return Status.isOK(connectStatus);
}
async disconnect(): Promise<Status> {
if (!this.connected) {
return Status.OK;
}
const stateCopy = deepClone(this.state);
if (!stateCopy) {
return Status.OK;
}
console.log('>>> Disposing existing monitor connection...');
const status = await this.monitorService.disconnect();
if (Status.isOK(status)) {
console.log(
`<<< Disposed connection. Was: ${MonitorConnection.State.toString(
stateCopy
)}`
);
} else {
console.warn(
`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(
stateCopy
)}`
);
}
this.state = undefined;
this.onConnectionChangedEmitter.fire(this.state);
return status;
}
/**
* Sends the data to the connected serial monitor.
* The desired EOL is appended to `data`, you do not have to add it.
* It is a NOOP if connected.
*/
async send(data: string): Promise<Status> {
if (!this.connected) {
return Status.NOT_CONNECTED;
}
return new Promise<Status>((resolve) => {
this.monitorService
.send(data + this.monitorModel.lineEnding)
.then(() => resolve(Status.OK));
});
}
get onConnectionChanged(): Event<MonitorConnection.State | undefined> {
return this.onConnectionChangedEmitter.event;
}
get onRead(): Event<{ message: string }> {
return this.onReadEmitter.event;
}
protected async handleBoardConfigChange(
boardsConfig: BoardsConfig.Config
): Promise<void> {
if (this.autoConnect) {
if (
this.boardsServiceProvider.canUploadTo(boardsConfig, {
silent: false,
})
) {
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
this.boardsService.getAvailablePorts().then((ports) => {
if (
ports.some((port) => Port.equals(port, boardsConfig.selectedPort))
) {
new Promise<void>((resolve) => {
// First, disconnect if connected.
if (this.connected) {
this.disconnect().then(() => resolve());
return;
}
resolve();
}).then(() => {
// Then (re-)connect.
const { selectedBoard: board, selectedPort: port } = boardsConfig;
const { baudRate } = this.monitorModel;
this.connect({ board, port, baudRate });
});
}
});
}
}
}
}
export namespace MonitorConnection {
export interface State {
readonly config: MonitorConfig;
}
export namespace State {
export function toString(state: State): string {
const { config } = state;
const { board, port } = config;
return `${Board.toString(board)} ${Port.toString(port)}`;
}
}
}

View File

@@ -1,16 +0,0 @@
import { injectable } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import {
MonitorServiceClient,
MonitorError,
} from '../../common/protocol/monitor-service';
@injectable()
export class MonitorServiceClientImpl implements MonitorServiceClient {
protected readonly onErrorEmitter = new Emitter<MonitorError>();
readonly onError = this.onErrorEmitter.event;
notifyError(error: MonitorError): void {
this.onErrorEmitter.fire(error);
}
}

View File

@@ -1,394 +0,0 @@
import * as React from 'react';
import * as dateFormat from 'dateformat';
import { postConstruct, injectable, inject } from 'inversify';
import { OptionsType } from 'react-select/src/types';
import { isOSX } from '@theia/core/lib/common/os';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
import {
DisposableCollection,
Disposable,
} from '@theia/core/lib/common/disposable';
import {
ReactWidget,
Message,
Widget,
MessageLoop,
} from '@theia/core/lib/browser/widgets';
import { Board, Port } from '../../common/protocol/boards-service';
import { MonitorConfig } from '../../common/protocol/monitor-service';
import { ArduinoSelect } from '../widgets/arduino-select';
import { MonitorModel } from './monitor-model';
import { MonitorConnection } from './monitor-connection';
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
@injectable()
export class MonitorWidget extends ReactWidget {
static readonly ID = 'serial-monitor';
@inject(MonitorModel)
protected readonly monitorModel: MonitorModel;
@inject(MonitorConnection)
protected readonly monitorConnection: MonitorConnection;
@inject(MonitorServiceClientImpl)
protected readonly monitorServiceClient: MonitorServiceClientImpl;
protected widgetHeight: number;
/**
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
*/
protected focusNode: HTMLElement | undefined;
/**
* Guard against re-rendering the view after the close was requested.
* See: https://github.com/eclipse-theia/theia/issues/6704
*/
protected closing = false;
protected readonly clearOutputEmitter = new Emitter<void>();
constructor() {
super();
this.id = MonitorWidget.ID;
this.title.label = 'Serial Monitor';
this.title.iconClass = 'monitor-tab-icon';
this.title.closable = true;
this.scrollOptions = undefined;
this.toDispose.push(this.clearOutputEmitter);
this.toDispose.push(
Disposable.create(() => {
this.monitorConnection.autoConnect = false;
if (this.monitorConnection.connected) {
this.monitorConnection.disconnect();
}
})
);
}
@postConstruct()
protected init(): void {
this.update();
this.toDispose.push(
this.monitorConnection.onConnectionChanged(() => this.clearConsole())
);
}
clearConsole(): void {
this.clearOutputEmitter.fire(undefined);
this.update();
}
dispose(): void {
super.dispose();
}
protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.monitorConnection.autoConnect = true;
}
onCloseRequest(msg: Message): void {
this.closing = true;
super.onCloseRequest(msg);
}
protected onUpdateRequest(msg: Message): void {
// TODO: `this.isAttached`
// See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713
if (!this.closing && this.isAttached) {
super.onUpdateRequest(msg);
}
}
protected onResize(msg: Widget.ResizeMessage): void {
super.onResize(msg);
this.widgetHeight = msg.height;
this.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
(this.focusNode || this.node).focus();
}
protected onFocusResolved = (element: HTMLElement | undefined) => {
if (this.closing || !this.isAttached) {
return;
}
this.focusNode = element;
requestAnimationFrame(() =>
MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest)
);
};
protected get lineEndings(): OptionsType<SelectOption<MonitorModel.EOL>> {
return [
{
label: 'No Line Ending',
value: '',
},
{
label: 'New Line',
value: '\n',
},
{
label: 'Carriage Return',
value: '\r',
},
{
label: 'Both NL & CR',
value: '\r\n',
},
];
}
protected get baudRates(): OptionsType<SelectOption<MonitorConfig.BaudRate>> {
const baudRates: Array<MonitorConfig.BaudRate> = [
300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,
];
return baudRates.map((baudRate) => ({
label: baudRate + ' baud',
value: baudRate,
}));
}
protected render(): React.ReactNode {
const { baudRates, lineEndings } = this;
const lineEnding =
lineEndings.find((item) => item.value === this.monitorModel.lineEnding) ||
lineEndings[1]; // Defaults to `\n`.
const baudRate =
baudRates.find((item) => item.value === this.monitorModel.baudRate) ||
baudRates[4]; // Defaults to `9600`.
return (
<div className="serial-monitor">
<div className="head">
<div className="send">
<SerialMonitorSendInput
monitorConfig={this.monitorConnection.monitorConfig}
resolveFocus={this.onFocusResolved}
onSend={this.onSend}
/>
</div>
<div className="config">
<div className="select">
<ArduinoSelect
maxMenuHeight={this.widgetHeight - 40}
options={lineEndings}
defaultValue={lineEnding}
onChange={this.onChangeLineEnding}
/>
</div>
<div className="select">
<ArduinoSelect
className="select"
maxMenuHeight={this.widgetHeight - 40}
options={baudRates}
defaultValue={baudRate}
onChange={this.onChangeBaudRate}
/>
</div>
</div>
</div>
<div className="body">
<SerialMonitorOutput
monitorModel={this.monitorModel}
monitorConnection={this.monitorConnection}
clearConsoleEvent={this.clearOutputEmitter.event}
/>
</div>
</div>
);
}
protected readonly onSend = (value: string) => this.doSend(value);
protected async doSend(value: string): Promise<void> {
this.monitorConnection.send(value);
}
protected readonly onChangeLineEnding = (
option: SelectOption<MonitorModel.EOL>
) => {
this.monitorModel.lineEnding = option.value;
};
protected readonly onChangeBaudRate = (
option: SelectOption<MonitorConfig.BaudRate>
) => {
this.monitorModel.baudRate = option.value;
};
}
export namespace SerialMonitorSendInput {
export interface Props {
readonly monitorConfig?: MonitorConfig;
readonly onSend: (text: string) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
}
export interface State {
text: string;
}
}
export class SerialMonitorSendInput extends React.Component<
SerialMonitorSendInput.Props,
SerialMonitorSendInput.State
> {
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
super(props);
this.state = { text: '' };
this.onChange = this.onChange.bind(this);
this.onSend = this.onSend.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
render(): React.ReactNode {
return (
<input
ref={this.setRef}
type="text"
className={`theia-input ${this.props.monitorConfig ? '' : 'warning'}`}
placeholder={this.placeholder}
value={this.state.text}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
);
}
protected get placeholder(): string {
const { monitorConfig } = this.props;
if (!monitorConfig) {
return 'Not connected. Select a board and a port to connect automatically.';
}
const { board, port } = monitorConfig;
return `Message (${
isOSX ? '⌘' : 'Ctrl'
}+Enter to send message to '${Board.toString(board, {
useFqbn: false,
})}' on '${Port.toString(port)}')`;
}
protected setRef = (element: HTMLElement | null) => {
if (this.props.resolveFocus) {
this.props.resolveFocus(element || undefined);
}
};
protected onChange(event: React.ChangeEvent<HTMLInputElement>): void {
this.setState({ text: event.target.value });
}
protected onSend(): void {
this.props.onSend(this.state.text);
this.setState({ text: '' });
}
protected onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
const keyCode = KeyCode.createKeyCode(event.nativeEvent);
if (keyCode) {
const { key, meta, ctrl } = keyCode;
if (key === Key.ENTER && ((isOSX && meta) || (!isOSX && ctrl))) {
this.onSend();
}
}
}
}
export namespace SerialMonitorOutput {
export interface Props {
readonly monitorModel: MonitorModel;
readonly monitorConnection: MonitorConnection;
readonly clearConsoleEvent: Event<void>;
}
export interface State {
content: string;
timestamp: boolean;
}
}
export class SerialMonitorOutput extends React.Component<
SerialMonitorOutput.Props,
SerialMonitorOutput.State
> {
/**
* Do not touch it. It is used to be able to "follow" the serial monitor log.
*/
protected anchor: HTMLElement | null;
protected toDisposeBeforeUnmount = new DisposableCollection();
constructor(props: Readonly<SerialMonitorOutput.Props>) {
super(props);
this.state = {
content: '',
timestamp: this.props.monitorModel.timestamp,
};
}
render(): React.ReactNode {
return (
<React.Fragment>
<div style={{ whiteSpace: 'pre', fontFamily: 'monospace' }}>
{this.state.content}
</div>
<div
style={{ float: 'left', clear: 'both' }}
ref={(element) => {
this.anchor = element;
}}
/>
</React.Fragment>
);
}
componentDidMount(): void {
this.scrollToBottom();
this.toDisposeBeforeUnmount.pushAll([
this.props.monitorConnection.onRead(({ message }) => {
const rawLines = message.split('\n');
const lines: string[] = [];
const timestamp = () =>
this.state.timestamp
? `${dateFormat(new Date(), 'H:M:ss.l')} -> `
: '';
for (let i = 0; i < rawLines.length; i++) {
if (i === 0 && this.state.content.length !== 0) {
lines.push(rawLines[i]);
} else {
lines.push(timestamp() + rawLines[i]);
}
}
const content = this.state.content + lines.join('\n');
this.setState({ content });
}),
this.props.clearConsoleEvent(() => this.setState({ content: '' })),
this.props.monitorModel.onChange(({ property }) => {
if (property === 'timestamp') {
const { timestamp } = this.props.monitorModel;
this.setState({ timestamp });
}
}),
]);
}
componentDidUpdate(): void {
this.scrollToBottom();
}
componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose();
}
protected scrollToBottom(): void {
if (this.props.monitorModel.autoscroll && this.anchor) {
this.anchor.scrollIntoView();
}
}
}
export interface SelectOption<T> {
readonly label: string;
readonly value: T;
}

View File

@@ -1,15 +1,15 @@
import { inject, injectable } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
import {
ResponseService,
OutputMessage,
ProgressMessage,
ResponseServiceArduino,
} from '../common/protocol/response-service';
@injectable()
export class ResponseServiceImpl implements ResponseService {
export class ResponseServiceImpl implements ResponseServiceArduino {
@inject(OutputContribution)
protected outputContribution: OutputContribution;
@@ -17,8 +17,13 @@ export class ResponseServiceImpl implements ResponseService {
protected outputChannelManager: OutputChannelManager;
protected readonly progressDidChangeEmitter = new Emitter<ProgressMessage>();
readonly onProgressDidChange = this.progressDidChangeEmitter.event;
clearArduinoChannel(): void {
this.outputChannelManager.getChannel('Arduino').clear();
}
appendToOutput(message: OutputMessage): void {
const { chunk } = message;
const channel = this.outputChannelManager.getChannel('Arduino');
@@ -26,10 +31,6 @@ export class ResponseServiceImpl implements ResponseService {
channel.append(chunk);
}
clearArduinoChannel(): void {
this.outputChannelManager.getChannel('Arduino').clear();
}
reportProgress(progress: ProgressMessage): void {
this.progressDidChangeEmitter.fire(progress);
}

View File

@@ -0,0 +1,69 @@
import { Line, SerialMonitorOutput } from './serial-monitor-send-output';
export function messagesToLines(
messages: string[],
prevLines: Line[] = [],
charCount = 0,
separator = '\n'
): [Line[], number] {
const linesToAdd: Line[] = prevLines.length
? [prevLines[prevLines.length - 1]]
: [{ message: '', lineLen: 0 }];
if (!(Symbol.iterator in Object(messages))) return [prevLines, charCount];
for (const message of messages) {
const messageLen = message.length;
charCount += messageLen;
const lastLine = linesToAdd[linesToAdd.length - 1];
// if the previous messages ends with "separator" add a new line
if (lastLine.message.charAt(lastLine.message.length - 1) === separator) {
linesToAdd.push({
message,
timestamp: new Date(),
lineLen: messageLen,
});
} else {
// concatenate to the last line
linesToAdd[linesToAdd.length - 1].message += message;
linesToAdd[linesToAdd.length - 1].lineLen += messageLen;
if (!linesToAdd[linesToAdd.length - 1].timestamp) {
linesToAdd[linesToAdd.length - 1].timestamp = new Date();
}
}
}
prevLines.splice(prevLines.length - 1, 1, ...linesToAdd);
return [prevLines, charCount];
}
export function truncateLines(
lines: Line[],
charCount: number,
maxCharacters: number = SerialMonitorOutput.MAX_CHARACTERS
): [Line[], number] {
let charsToDelete = charCount - maxCharacters;
let lineIndex = 0;
while (charsToDelete > 0 || lineIndex > 0) {
const firstLineLength = lines[lineIndex]?.lineLen;
if (charsToDelete >= firstLineLength) {
// every time a full line to delete is found, move the index.
lineIndex++;
charsToDelete -= firstLineLength;
charCount -= firstLineLength;
continue;
}
// delete all previous lines
lines.splice(0, lineIndex);
lineIndex = 0;
const newFirstLine = lines[0]?.message?.substring(charsToDelete);
const deletedCharsCount = firstLineLength - newFirstLine.length;
charCount -= deletedCharsCount;
charsToDelete -= deletedCharsCount;
lines[0].message = newFirstLine;
}
return [lines, charCount];
}

View File

@@ -1,31 +1,41 @@
import * as React from 'react';
import { injectable, inject } from 'inversify';
import { AbstractViewContribution } from '@theia/core/lib/browser';
import { AbstractViewContribution, codicon } from '@theia/core/lib/browser';
import { MonitorWidget } from './monitor-widget';
import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core';
import {
TabBarToolbarContribution,
TabBarToolbarRegistry,
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { MonitorModel } from './monitor-model';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
import { SerialModel } from '../serial-model';
import { ArduinoMenus } from '../../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
export namespace SerialMonitor {
export namespace Commands {
export const AUTOSCROLL: Command = {
id: 'serial-monitor-autoscroll',
label: 'Autoscroll',
};
export const TIMESTAMP: Command = {
id: 'serial-monitor-timestamp',
label: 'Timestamp',
};
export const CLEAR_OUTPUT: Command = {
id: 'serial-monitor-clear-output',
label: 'Clear Output',
iconClass: 'clear-all',
};
export const AUTOSCROLL = Command.toLocalizedCommand(
{
id: 'serial-monitor-autoscroll',
label: 'Autoscroll',
},
'arduino/serial/autoscroll'
);
export const TIMESTAMP = Command.toLocalizedCommand(
{
id: 'serial-monitor-timestamp',
label: 'Timestamp',
},
'arduino/serial/timestamp'
);
export const CLEAR_OUTPUT = Command.toLocalizedCommand(
{
id: 'serial-monitor-clear-output',
label: 'Clear Output',
iconClass: codicon('clear-all'),
},
'vscode/output.contribution/clearOutput.label'
);
}
}
@@ -38,12 +48,12 @@ export class MonitorViewContribution
static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR =
MonitorWidget.ID + ':toggle-toolbar';
@inject(MonitorModel) protected readonly model: MonitorModel;
@inject(SerialModel) protected readonly model: SerialModel;
constructor() {
super({
widgetId: MonitorWidget.ID,
widgetName: 'Serial Monitor',
widgetName: MonitorWidget.LABEL,
defaultWidgetOptions: {
area: 'bottom',
},
@@ -56,7 +66,7 @@ export class MonitorViewContribution
if (this.toggleCommand) {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: this.toggleCommand.id,
label: 'Serial Monitor',
label: MonitorWidget.LABEL,
order: '5',
});
}
@@ -78,7 +88,10 @@ export class MonitorViewContribution
registry.registerItem({
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
command: SerialMonitor.Commands.CLEAR_OUTPUT.id,
tooltip: 'Clear Output',
tooltip: nls.localize(
'vscode/output.contribution/clearOutput.label',
'Clear Output'
),
});
}
@@ -120,7 +133,10 @@ export class MonitorViewContribution
return (
<React.Fragment key="autoscroll-toolbar-item">
<div
title="Toggle Autoscroll"
title={nls.localize(
'vscode/output.contribution/toggleAutoScroll',
'Toggle Autoscroll'
)}
className={`item enabled fa fa-angle-double-down arduino-monitor ${
this.model.autoscroll ? 'toggled' : ''
}`}
@@ -139,7 +155,10 @@ export class MonitorViewContribution
return (
<React.Fragment key="line-ending-toolbar-item">
<div
title="Toggle Timestamp"
title={nls.localize(
'arduino/serial/toggleTimestamp',
'Toggle Timestamp'
)}
className={`item enabled fa fa-clock-o arduino-monitor ${
this.model.timestamp ? 'toggled' : ''
}`}

View File

@@ -0,0 +1,225 @@
import * as React from 'react';
import { postConstruct, injectable, inject } from 'inversify';
import { OptionsType } from 'react-select/src/types';
import { Emitter } from '@theia/core/lib/common/event';
import { Disposable } from '@theia/core/lib/common/disposable';
import {
ReactWidget,
Message,
Widget,
MessageLoop,
} from '@theia/core/lib/browser/widgets';
import { SerialConfig } from '../../../common/protocol/serial-service';
import { ArduinoSelect } from '../../widgets/arduino-select';
import { SerialModel } from '../serial-model';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialMonitorSendInput } from './serial-monitor-send-input';
import { SerialMonitorOutput } from './serial-monitor-send-output';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { nls } from '@theia/core/lib/common';
@injectable()
export class MonitorWidget extends ReactWidget {
static readonly LABEL = nls.localize(
'arduino/common/serialMonitor',
'Serial Monitor'
);
static readonly ID = 'serial-monitor';
@inject(SerialModel)
protected readonly serialModel: SerialModel;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
protected widgetHeight: number;
/**
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
*/
protected focusNode: HTMLElement | undefined;
/**
* Guard against re-rendering the view after the close was requested.
* See: https://github.com/eclipse-theia/theia/issues/6704
*/
protected closing = false;
protected readonly clearOutputEmitter = new Emitter<void>();
constructor() {
super();
this.id = MonitorWidget.ID;
this.title.label = MonitorWidget.LABEL;
this.title.iconClass = 'monitor-tab-icon';
this.title.closable = true;
this.scrollOptions = undefined;
this.toDispose.push(this.clearOutputEmitter);
this.toDispose.push(
Disposable.create(() => this.serialConnection.closeWStoBE())
);
}
@postConstruct()
protected init(): void {
this.update();
this.toDispose.push(
this.serialConnection.onConnectionChanged(() => this.clearConsole())
);
this.toDispose.push(this.serialModel.onChange(() => this.update()));
}
clearConsole(): void {
this.clearOutputEmitter.fire(undefined);
this.update();
}
dispose(): void {
super.dispose();
}
protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.serialConnection.openWSToBE();
}
onCloseRequest(msg: Message): void {
this.closing = true;
super.onCloseRequest(msg);
}
protected onUpdateRequest(msg: Message): void {
// TODO: `this.isAttached`
// See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713
if (!this.closing && this.isAttached) {
super.onUpdateRequest(msg);
}
}
protected onResize(msg: Widget.ResizeMessage): void {
super.onResize(msg);
this.widgetHeight = msg.height;
this.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
(this.focusNode || this.node).focus();
}
protected onFocusResolved = (element: HTMLElement | undefined) => {
if (this.closing || !this.isAttached) {
return;
}
this.focusNode = element;
requestAnimationFrame(() =>
MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest)
);
};
protected get lineEndings(): OptionsType<
SerialMonitorOutput.SelectOption<SerialModel.EOL>
> {
return [
{
label: nls.localize('arduino/serial/noLineEndings', 'No Line Ending'),
value: '',
},
{
label: nls.localize('arduino/serial/newLine', 'New Line'),
value: '\n',
},
{
label: nls.localize('arduino/serial/carriageReturn', 'Carriage Return'),
value: '\r',
},
{
label: nls.localize(
'arduino/serial/newLineCarriageReturn',
'Both NL & CR'
),
value: '\r\n',
},
];
}
protected get baudRates(): OptionsType<
SerialMonitorOutput.SelectOption<SerialConfig.BaudRate>
> {
const baudRates: Array<SerialConfig.BaudRate> = [
300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,
];
return baudRates.map((baudRate) => ({
label: baudRate + ' baud',
value: baudRate,
}));
}
protected render(): React.ReactNode {
const { baudRates, lineEndings } = this;
const lineEnding =
lineEndings.find((item) => item.value === this.serialModel.lineEnding) ||
lineEndings[1]; // Defaults to `\n`.
const baudRate =
baudRates.find((item) => item.value === this.serialModel.baudRate) ||
baudRates[4]; // Defaults to `9600`.
return (
<div className="serial-monitor">
<div className="head">
<div className="send">
<SerialMonitorSendInput
serialConnection={this.serialConnection}
resolveFocus={this.onFocusResolved}
onSend={this.onSend}
/>
</div>
<div className="config">
<div className="select">
<ArduinoSelect
maxMenuHeight={this.widgetHeight - 40}
options={lineEndings}
value={lineEnding}
onChange={this.onChangeLineEnding}
/>
</div>
<div className="select">
<ArduinoSelect
className="select"
maxMenuHeight={this.widgetHeight - 40}
options={baudRates}
value={baudRate}
onChange={this.onChangeBaudRate}
/>
</div>
</div>
</div>
<div className="body">
<SerialMonitorOutput
serialModel={this.serialModel}
serialConnection={this.serialConnection}
clearConsoleEvent={this.clearOutputEmitter.event}
height={Math.floor(this.widgetHeight - 50)}
/>
</div>
</div>
);
}
protected readonly onSend = (value: string) => this.doSend(value);
protected async doSend(value: string): Promise<void> {
this.serialConnection.send(value);
}
protected readonly onChangeLineEnding = (
option: SerialMonitorOutput.SelectOption<SerialModel.EOL>
) => {
this.serialModel.lineEnding = option.value;
};
protected readonly onChangeBaudRate = (
option: SerialMonitorOutput.SelectOption<SerialConfig.BaudRate>
) => {
this.serialModel.baudRate = option.value;
};
}

View File

@@ -0,0 +1,118 @@
import * as React from 'react';
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
import { Board, Port } from '../../../common/protocol/boards-service';
import { isOSX } from '@theia/core/lib/common/os';
import { DisposableCollection, nls } from '@theia/core/lib/common';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialPlotter } from '../plotter/protocol';
export namespace SerialMonitorSendInput {
export interface Props {
readonly serialConnection: SerialConnectionManager;
readonly onSend: (text: string) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
}
export interface State {
text: string;
connected: boolean;
}
}
export class SerialMonitorSendInput extends React.Component<
SerialMonitorSendInput.Props,
SerialMonitorSendInput.State
> {
protected toDisposeBeforeUnmount = new DisposableCollection();
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
super(props);
this.state = { text: '', connected: false };
this.onChange = this.onChange.bind(this);
this.onSend = this.onSend.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
componentDidMount(): void {
this.props.serialConnection.isBESerialConnected().then((connected) => {
this.setState({ connected });
});
this.toDisposeBeforeUnmount.pushAll([
this.props.serialConnection.onRead(({ messages }) => {
if (
messages.command ===
SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED &&
'connected' in messages.data
) {
this.setState({ connected: messages.data.connected });
}
}),
]);
}
componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose();
}
render(): React.ReactNode {
return (
<input
ref={this.setRef}
type="text"
className={`theia-input ${this.state.connected ? '' : 'warning'}`}
placeholder={this.placeholder}
value={this.state.text}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
);
}
protected get placeholder(): string {
const serialConfig = this.props.serialConnection.getConfig();
if (!this.state.connected || !serialConfig) {
return nls.localize(
'arduino/serial/notConnected',
'Not connected. Select a board and a port to connect automatically.'
);
}
const { board, port } = serialConfig;
return nls.localize(
'arduino/serial/message',
"Message ({0} + Enter to send message to '{1}' on '{2}'",
isOSX ? '⌘' : nls.localize('vscode/keybindingLabels/ctrlKey', 'Ctrl'),
board
? Board.toString(board, {
useFqbn: false,
})
: 'unknown',
port ? Port.toString(port) : 'unknown'
);
}
protected setRef = (element: HTMLElement | null) => {
if (this.props.resolveFocus) {
this.props.resolveFocus(element || undefined);
}
};
protected onChange(event: React.ChangeEvent<HTMLInputElement>): void {
this.setState({ text: event.target.value });
}
protected onSend(): void {
this.props.onSend(this.state.text);
this.setState({ text: '' });
}
protected onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
const keyCode = KeyCode.createKeyCode(event.nativeEvent);
if (keyCode) {
const { key, meta, ctrl } = keyCode;
if (key === Key.ENTER && ((isOSX && meta) || (!isOSX && ctrl))) {
this.onSend();
}
}
}
}

View File

@@ -0,0 +1,147 @@
import * as React from 'react';
import { Event } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { areEqual, FixedSizeList as List } from 'react-window';
import { SerialModel } from '../serial-model';
import { SerialConnectionManager } from '../serial-connection-manager';
import dateFormat = require('dateformat');
import { messagesToLines, truncateLines } from './monitor-utils';
export type Line = { message: string; timestamp?: Date; lineLen: number };
export class SerialMonitorOutput extends React.Component<
SerialMonitorOutput.Props,
SerialMonitorOutput.State
> {
/**
* Do not touch it. It is used to be able to "follow" the serial monitor log.
*/
protected toDisposeBeforeUnmount = new DisposableCollection();
private listRef: React.RefObject<any>;
constructor(props: Readonly<SerialMonitorOutput.Props>) {
super(props);
this.listRef = React.createRef();
this.state = {
lines: [],
timestamp: this.props.serialModel.timestamp,
charCount: 0,
};
}
render(): React.ReactNode {
return (
<List
className="serial-monitor-messages"
height={this.props.height}
itemData={
{
lines: this.state.lines,
timestamp: this.state.timestamp,
} as any
}
itemCount={this.state.lines.length}
itemSize={18}
width={'100%'}
style={{ whiteSpace: 'nowrap' }}
ref={this.listRef}
>
{Row}
</List>
);
}
shouldComponentUpdate(): boolean {
return true;
}
componentDidMount(): void {
this.scrollToBottom();
this.toDisposeBeforeUnmount.pushAll([
this.props.serialConnection.onRead(({ messages }) => {
const [newLines, totalCharCount] = messagesToLines(
messages,
this.state.lines,
this.state.charCount
);
const [lines, charCount] = truncateLines(newLines, totalCharCount);
this.setState({
lines,
charCount,
});
this.scrollToBottom();
}),
this.props.clearConsoleEvent(() =>
this.setState({ lines: [], charCount: 0 })
),
this.props.serialModel.onChange(({ property }) => {
if (property === 'timestamp') {
const { timestamp } = this.props.serialModel;
this.setState({ timestamp });
}
if (property === 'autoscroll') {
this.scrollToBottom();
}
}),
]);
}
componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose();
}
scrollToBottom = ((): void => {
if (this.listRef.current && this.props.serialModel.autoscroll) {
this.listRef.current.scrollToItem(this.state.lines.length, 'end');
}
}).bind(this);
}
const _Row = ({
index,
style,
data,
}: {
index: number;
style: any;
data: { lines: Line[]; timestamp: boolean };
}) => {
const timestamp =
(data.timestamp &&
`${dateFormat(data.lines[index].timestamp, 'H:M:ss.l')} -> `) ||
'';
return (
(data.lines[index].lineLen && (
<div style={style}>
{timestamp}
{data.lines[index].message}
</div>
)) ||
null
);
};
const Row = React.memo(_Row, areEqual);
export namespace SerialMonitorOutput {
export interface Props {
readonly serialModel: SerialModel;
readonly serialConnection: SerialConnectionManager;
readonly clearConsoleEvent: Event<void>;
readonly height: number;
}
export interface State {
lines: Line[];
timestamp: boolean;
charCount: number;
}
export interface SelectOption<T> {
readonly label: string;
readonly value: T;
}
export const MAX_CHARACTERS = 1_000_000;
}

View File

@@ -0,0 +1,106 @@
import { ThemeService } from '@theia/core/lib/browser/theming';
import { injectable, inject } from 'inversify';
import {
Command,
CommandRegistry,
MaybePromise,
MenuModelRegistry,
} from '@theia/core';
import { SerialModel } from '../serial-model';
import { ArduinoMenus } from '../../menu/arduino-menus';
import { Contribution } from '../../contributions/contribution';
import { Endpoint, FrontendApplication } from '@theia/core/lib/browser';
import { ipcRenderer } from '@theia/core/shared/electron';
import { SerialConfig } from '../../../common/protocol';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialPlotter } from './protocol';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
const queryString = require('query-string');
export namespace SerialPlotterContribution {
export namespace Commands {
export const OPEN: Command = {
id: 'serial-plotter-open',
label: 'Serial Plotter',
category: 'Arduino',
};
}
}
@injectable()
export class PlotterFrontendContribution extends Contribution {
protected window: Window | null;
protected url: string;
protected wsPort: number;
@inject(SerialModel)
protected readonly model: SerialModel;
@inject(ThemeService)
protected readonly themeService: ThemeService;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
onStart(app: FrontendApplication): MaybePromise<void> {
this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString();
ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => {
if (!!this.window) {
this.window = null;
}
});
return super.onStart(app);
}
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(SerialPlotterContribution.Commands.OPEN, {
execute: this.connect.bind(this),
});
}
registerMenus(menus: MenuModelRegistry): void {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: SerialPlotterContribution.Commands.OPEN.id,
label: SerialPlotterContribution.Commands.OPEN.label,
order: '7',
});
}
async connect(): Promise<void> {
if (!!this.window) {
this.window.focus();
return;
}
const wsPort = this.serialConnection.getWsPort();
if (wsPort) {
this.open(wsPort);
} else {
this.messageService.error(`Couldn't open serial plotter`);
}
}
protected async open(wsPort: number): Promise<void> {
const initConfig: Partial<SerialPlotter.Config> = {
baudrates: SerialConfig.BaudRates.map((b) => b),
currentBaudrate: this.model.baudRate,
currentLineEnding: this.model.lineEnding,
darkTheme: this.themeService.getCurrentTheme().type === 'dark',
wsPort,
interpolate: this.model.interpolate,
connected: await this.serialConnection.isBESerialConnected(),
serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address,
};
const urlWithParams = queryString.stringifyUrl(
{
url: this.url,
query: initConfig,
},
{ arrayFormat: 'comma' }
);
this.window = window.open(urlWithParams, 'serialPlotter');
}
}

View File

@@ -0,0 +1,26 @@
export namespace SerialPlotter {
export type Config = {
currentBaudrate: number;
baudrates: number[];
currentLineEnding: string;
darkTheme: boolean;
wsPort: number;
interpolate: boolean;
serialPort: string;
connected: boolean;
generate?: boolean;
};
export namespace Protocol {
export enum Command {
PLOTTER_SET_BAUDRATE = 'PLOTTER_SET_BAUDRATE',
PLOTTER_SET_LINE_ENDING = 'PLOTTER_SET_LINE_ENDING',
PLOTTER_SET_INTERPOLATE = 'PLOTTER_SET_INTERPOLATE',
PLOTTER_SEND_MESSAGE = 'PLOTTER_SEND_MESSAGE',
MIDDLEWARE_CONFIG_CHANGED = 'MIDDLEWARE_CONFIG_CHANGED',
}
export type Message = {
command: SerialPlotter.Protocol.Command;
data?: any;
};
}
}

View File

@@ -0,0 +1,361 @@
import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import {
SerialService,
SerialConfig,
SerialError,
Status,
SerialServiceClient,
} from '../../common/protocol/serial-service';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import {
Port,
Board,
BoardsService,
} from '../../common/protocol/boards-service';
import { BoardsConfig } from '../boards/boards-config';
import { SerialModel } from './serial-model';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { CoreService } from '../../common/protocol';
import { nls } from '@theia/core/lib/common/nls';
@injectable()
export class SerialConnectionManager {
protected config: Partial<SerialConfig> = {
board: undefined,
port: undefined,
baudRate: undefined,
};
protected readonly onConnectionChangedEmitter = new Emitter<boolean>();
/**
* This emitter forwards all read events **if** the connection is established.
*/
protected readonly onReadEmitter = new Emitter<{ messages: string[] }>();
/**
* Array for storing previous serial errors received from the server, and based on the number of elements in this array,
* we adjust the reconnection delay.
* Super naive way: we wait `array.length * 1000` ms. Once we hit 10 errors, we do not try to reconnect and clean the array.
*/
protected serialErrors: SerialError[] = [];
protected reconnectTimeout?: number;
/**
* When the websocket server is up on the backend, we save the port here, so that the client knows how to connect to it
* */
protected wsPort?: number;
protected webSocket?: WebSocket;
constructor(
@inject(SerialModel) protected readonly serialModel: SerialModel,
@inject(SerialService) protected readonly serialService: SerialService,
@inject(SerialServiceClient)
protected readonly serialServiceClient: SerialServiceClient,
@inject(BoardsService) protected readonly boardsService: BoardsService,
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider,
@inject(MessageService) protected messageService: MessageService,
@inject(ThemeService) protected readonly themeService: ThemeService,
@inject(CoreService) protected readonly core: CoreService,
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider
) {
this.serialServiceClient.onWebSocketChanged(
this.handleWebSocketChanged.bind(this)
);
this.serialServiceClient.onBaudRateChanged((baudRate) => {
if (this.serialModel.baudRate !== baudRate) {
this.serialModel.baudRate = baudRate;
}
});
this.serialServiceClient.onLineEndingChanged((lineending) => {
if (this.serialModel.lineEnding !== lineending) {
this.serialModel.lineEnding = lineending;
}
});
this.serialServiceClient.onInterpolateChanged((interpolate) => {
if (this.serialModel.interpolate !== interpolate) {
this.serialModel.interpolate = interpolate;
}
});
this.serialServiceClient.onError(this.handleError.bind(this));
this.boardsServiceProvider.onBoardsConfigChanged(
this.handleBoardConfigChange.bind(this)
);
// Handles the `baudRate` changes by reconnecting if required.
this.serialModel.onChange(async ({ property }) => {
if (
property === 'baudRate' &&
(await this.serialService.isSerialPortOpen())
) {
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
}
// update the current values in the backend and propagate to websocket clients
this.serialService.updateWsConfigParam({
...(property === 'lineEnding' && {
currentLineEnding: this.serialModel.lineEnding,
}),
...(property === 'interpolate' && {
interpolate: this.serialModel.interpolate,
}),
});
});
this.themeService.onDidColorThemeChange((theme) => {
this.serialService.updateWsConfigParam({
darkTheme: theme.newTheme.type === 'dark',
});
});
}
/**
* Updated the config in the BE passing only the properties that has changed.
* BE will create a new connection if needed.
*
* @param newConfig the porperties of the config that has changed
*/
async setConfig(newConfig: Partial<SerialConfig>): Promise<void> {
let configHasChanged = false;
Object.keys(this.config).forEach((key: keyof SerialConfig) => {
if (newConfig[key] !== this.config[key]) {
configHasChanged = true;
this.config = { ...this.config, [key]: newConfig[key] };
}
});
if (configHasChanged) {
this.serialService.updateWsConfigParam({
currentBaudrate: this.config.baudRate,
serialPort: this.config.port?.address,
});
if (isSerialConfig(this.config)) {
this.serialService.setSerialConfig(this.config);
}
}
}
getConfig(): Partial<SerialConfig> {
return this.config;
}
getWsPort(): number | undefined {
return this.wsPort;
}
protected handleWebSocketChanged(wsPort: number): void {
this.wsPort = wsPort;
}
get serialConfig(): SerialConfig | undefined {
return isSerialConfig(this.config)
? (this.config as SerialConfig)
: undefined;
}
async isBESerialConnected(): Promise<boolean> {
return await this.serialService.isSerialPortOpen();
}
openWSToBE(): void {
if (!isSerialConfig(this.config)) {
this.messageService.error(
`Please select a board and a port to open the serial connection.`
);
}
if (!this.webSocket && this.wsPort) {
try {
this.webSocket = new WebSocket(`ws://localhost:${this.wsPort}`);
this.webSocket.onmessage = (res) => {
const messages = JSON.parse(res.data);
this.onReadEmitter.fire({ messages });
};
} catch {
this.messageService.error(`Unable to connect to websocket`);
}
}
}
closeWStoBE(): void {
if (this.webSocket) {
try {
this.webSocket.close();
this.webSocket = undefined;
} catch {
this.messageService.error(`Unable to close websocket`);
}
}
}
/**
* Handles error on the SerialServiceClient and try to reconnect, eventually
*/
async handleError(error: SerialError): Promise<void> {
if (!(await this.serialService.isSerialPortOpen())) return;
const { code, config } = error;
const { board, port } = config;
const options = { timeout: 3000 };
switch (code) {
case SerialError.ErrorCodes.CLIENT_CANCEL: {
console.debug(
`Serial connection was canceled by client: ${Serial.Config.toString(
this.config
)}.`
);
break;
}
case SerialError.ErrorCodes.DEVICE_BUSY: {
this.messageService.warn(
nls.localize(
'arduino/serial/connectionBusy',
'Connection failed. Serial port is busy: {0}',
Port.toString(port)
),
options
);
this.serialErrors.push(error);
break;
}
case SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
this.messageService.info(
nls.localize(
'arduino/serial/disconnected',
'Disconnected {0} from {1}.',
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port)
),
options
);
break;
}
case undefined: {
this.messageService.error(
nls.localize(
'arduino/serial/unexpectedError',
'Unexpected error. Reconnecting {0} on port {1}.',
Board.toString(board),
Port.toString(port)
),
options
);
console.error(JSON.stringify(error));
break;
}
}
if ((await this.serialService.clientsAttached()) > 0) {
if (this.serialErrors.length >= 10) {
this.messageService.warn(
nls.localize(
'arduino/serial/failedReconnect',
'Failed to reconnect {0} to serial port after 10 consecutive attempts. The {1} serial port is busy.',
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port)
)
);
this.serialErrors.length = 0;
} else {
const attempts = this.serialErrors.length || 1;
if (this.reconnectTimeout !== undefined) {
// Clear the previous timer.
window.clearTimeout(this.reconnectTimeout);
}
const timeout = attempts * 1000;
this.messageService.warn(
nls.localize(
'arduino/serial/reconnect',
'Reconnecting {0} to {1} in {2} seconds...',
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port),
attempts.toString()
)
);
this.reconnectTimeout = window.setTimeout(
() => this.reconnectAfterUpload(),
timeout
);
}
}
}
async reconnectAfterUpload(): Promise<void> {
try {
if (isSerialConfig(this.config)) {
await this.boardsServiceClientImpl.waitUntilAvailable(
Object.assign(this.config.board, { port: this.config.port }),
10_000
);
this.serialService.connectSerialIfRequired();
}
} catch (waitError) {
this.messageService.error(
nls.localize(
'arduino/sketch/couldNotConnectToSerial',
'Could not reconnect to serial port. {0}',
waitError.toString()
)
);
}
}
/**
* Sends the data to the connected serial port.
* The desired EOL is appended to `data`, you do not have to add it.
* It is a NOOP if connected.
*/
async send(data: string): Promise<Status> {
if (!(await this.serialService.isSerialPortOpen())) {
return Status.NOT_CONNECTED;
}
return new Promise<Status>((resolve) => {
this.serialService
.sendMessageToSerial(data + this.serialModel.lineEnding)
.then(() => resolve(Status.OK));
});
}
get onConnectionChanged(): Event<boolean> {
return this.onConnectionChangedEmitter.event;
}
get onRead(): Event<{ messages: any }> {
return this.onReadEmitter.event;
}
protected async handleBoardConfigChange(
boardsConfig: BoardsConfig.Config
): Promise<void> {
const { selectedBoard: board, selectedPort: port } = boardsConfig;
const { baudRate } = this.serialModel;
const newConfig: Partial<SerialConfig> = { board, port, baudRate };
this.setConfig(newConfig);
}
}
export namespace Serial {
export namespace Config {
export function toString(config: Partial<SerialConfig>): string {
if (!isSerialConfig(config)) return '';
const { board, port } = config;
return `${Board.toString(board)} ${Port.toString(port)}`;
}
}
}
function isSerialConfig(config: Partial<SerialConfig>): config is SerialConfig {
return !!config.board && !!config.baudRate && !!config.port;
}

View File

@@ -1,6 +1,6 @@
import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MonitorConfig } from '../../common/protocol/monitor-service';
import { SerialConfig } from '../../common/protocol';
import {
FrontendApplicationContribution,
LocalStorageService,
@@ -8,8 +8,8 @@ import {
import { BoardsServiceProvider } from '../boards/boards-service-provider';
@injectable()
export class MonitorModel implements FrontendApplicationContribution {
protected static STORAGE_ID = 'arduino-monitor-model';
export class SerialModel implements FrontendApplicationContribution {
protected static STORAGE_ID = 'arduino-serial-model';
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@@ -18,26 +18,28 @@ export class MonitorModel implements FrontendApplicationContribution {
protected readonly boardsServiceClient: BoardsServiceProvider;
protected readonly onChangeEmitter: Emitter<
MonitorModel.State.Change<keyof MonitorModel.State>
SerialModel.State.Change<keyof SerialModel.State>
>;
protected _autoscroll: boolean;
protected _timestamp: boolean;
protected _baudRate: MonitorConfig.BaudRate;
protected _lineEnding: MonitorModel.EOL;
protected _baudRate: SerialConfig.BaudRate;
protected _lineEnding: SerialModel.EOL;
protected _interpolate: boolean;
constructor() {
this._autoscroll = true;
this._timestamp = false;
this._baudRate = MonitorConfig.BaudRate.DEFAULT;
this._lineEnding = MonitorModel.EOL.DEFAULT;
this._baudRate = SerialConfig.BaudRate.DEFAULT;
this._lineEnding = SerialModel.EOL.DEFAULT;
this._interpolate = false;
this.onChangeEmitter = new Emitter<
MonitorModel.State.Change<keyof MonitorModel.State>
SerialModel.State.Change<keyof SerialModel.State>
>();
}
onStart(): void {
this.localStorageService
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID)
.getData<SerialModel.State>(SerialModel.STORAGE_ID)
.then((state) => {
if (state) {
this.restoreState(state);
@@ -45,7 +47,7 @@ export class MonitorModel implements FrontendApplicationContribution {
});
}
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
get onChange(): Event<SerialModel.State.Change<keyof SerialModel.State>> {
return this.onChangeEmitter.event;
}
@@ -78,11 +80,11 @@ export class MonitorModel implements FrontendApplicationContribution {
);
}
get baudRate(): MonitorConfig.BaudRate {
get baudRate(): SerialConfig.BaudRate {
return this._baudRate;
}
set baudRate(baudRate: MonitorConfig.BaudRate) {
set baudRate(baudRate: SerialConfig.BaudRate) {
this._baudRate = baudRate;
this.storeState().then(() =>
this.onChangeEmitter.fire({
@@ -92,11 +94,11 @@ export class MonitorModel implements FrontendApplicationContribution {
);
}
get lineEnding(): MonitorModel.EOL {
get lineEnding(): SerialModel.EOL {
return this._lineEnding;
}
set lineEnding(lineEnding: MonitorModel.EOL) {
set lineEnding(lineEnding: SerialModel.EOL) {
this._lineEnding = lineEnding;
this.storeState().then(() =>
this.onChangeEmitter.fire({
@@ -106,29 +108,46 @@ export class MonitorModel implements FrontendApplicationContribution {
);
}
protected restoreState(state: MonitorModel.State): void {
get interpolate(): boolean {
return this._interpolate;
}
set interpolate(i: boolean) {
this._interpolate = i;
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'interpolate',
value: this._interpolate,
})
);
}
protected restoreState(state: SerialModel.State): void {
this._autoscroll = state.autoscroll;
this._timestamp = state.timestamp;
this._baudRate = state.baudRate;
this._lineEnding = state.lineEnding;
this._interpolate = state.interpolate;
}
protected async storeState(): Promise<void> {
return this.localStorageService.setData(MonitorModel.STORAGE_ID, {
return this.localStorageService.setData(SerialModel.STORAGE_ID, {
autoscroll: this._autoscroll,
timestamp: this._timestamp,
baudRate: this._baudRate,
lineEnding: this._lineEnding,
interpolate: this._interpolate,
});
}
}
export namespace MonitorModel {
export namespace SerialModel {
export interface State {
autoscroll: boolean;
timestamp: boolean;
baudRate: MonitorConfig.BaudRate;
baudRate: SerialConfig.BaudRate;
lineEnding: EOL;
interpolate: boolean;
}
export namespace State {
export interface Change<K extends keyof State> {

View File

@@ -0,0 +1,48 @@
import { injectable } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import {
SerialServiceClient,
SerialError,
SerialConfig,
} from '../../common/protocol/serial-service';
import { SerialModel } from './serial-model';
@injectable()
export class SerialServiceClientImpl implements SerialServiceClient {
protected readonly onErrorEmitter = new Emitter<SerialError>();
readonly onError = this.onErrorEmitter.event;
protected readonly onWebSocketChangedEmitter = new Emitter<number>();
readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event;
protected readonly onBaudRateChangedEmitter =
new Emitter<SerialConfig.BaudRate>();
readonly onBaudRateChanged = this.onBaudRateChangedEmitter.event;
protected readonly onLineEndingChangedEmitter =
new Emitter<SerialModel.EOL>();
readonly onLineEndingChanged = this.onLineEndingChangedEmitter.event;
protected readonly onInterpolateChangedEmitter = new Emitter<boolean>();
readonly onInterpolateChanged = this.onInterpolateChangedEmitter.event;
notifyError(error: SerialError): void {
this.onErrorEmitter.fire(error);
}
notifyWebSocketChanged(message: number): void {
this.onWebSocketChangedEmitter.fire(message);
}
notifyBaudRateChanged(message: SerialConfig.BaudRate): void {
this.onBaudRateChangedEmitter.fire(message);
}
notifyLineEndingChanged(message: SerialModel.EOL): void {
this.onLineEndingChangedEmitter.fire(message);
}
notifyInterpolateChanged(message: boolean): void {
this.onInterpolateChangedEmitter.fire(message);
}
}

View File

@@ -1,224 +1,237 @@
div#select-board-dialog {
margin: 5px;
margin: 5px;
}
div#select-board-dialog .selectBoardContainer .body {
display: flex;
overflow: hidden;
display: flex;
overflow: hidden;
}
.select-board-dialog .head {
margin: 5px;
margin: 5px;
}
div.dialogContent.select-board-dialog > div.head .title {
font-weight: 400;
letter-spacing: .02em;
font-size: 1.2em;
color: var(--theia-arduino-branding-primary);
margin-bottom: 10px;
font-weight: 400;
letter-spacing: 0.02em;
font-size: 1.2em;
color: var(--theia-arduino-branding-primary);
margin-bottom: 10px;
}
div#select-board-dialog .selectBoardContainer .body .list .item.selected {
background: var(--theia-secondaryButton-hoverBackground);
background: var(--theia-secondaryButton-hoverBackground);
}
div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
color: var(--theia-arduino-branding-primary);
color: var(--theia-arduino-branding-primary);
}
#select-board-dialog .selectBoardContainer .search,
#select-board-dialog .selectBoardContainer .search input,
#select-board-dialog .selectBoardContainer .list,
#select-board-dialog .selectBoardContainer .list {
background: var(--theia-editor-background);
background: var(--theia-editor-background);
}
#select-board-dialog .selectBoardContainer .body .search input {
border: none;
width: 100%;
height: auto;
max-height: 37px;
padding: 10px 5px 10px 10px;
margin: 0;
vertical-align: top;
display: flex;
color: var(--theia-editor-foreground);
border: none;
width: 100%;
height: auto;
max-height: 37px;
padding: 10px 5px 10px 10px;
margin: 0;
vertical-align: top;
display: flex;
color: var(--theia-editor-foreground);
}
#select-board-dialog .selectBoardContainer .body .search input:focus {
box-shadow: none;
box-shadow: none;
}
#select-board-dialog .selectBoardContainer .body .container {
flex: 1;
padding: 0px 10px 0px 0px;
min-width: 240px;
max-width: 240px;
flex: 1;
padding: 0px 10px 0px 0px;
min-width: 240px;
max-width: 240px;
}
#select-board-dialog .selectBoardContainer .body .left.container .content {
margin: 0 5px 0 0;
margin: 0 5px 0 0;
}
#select-board-dialog .selectBoardContainer .body .right.container .content {
margin: 0 0 0 5px;
margin: 0 0 0 5px;
}
#select-board-dialog .selectBoardContainer .body .container .content .title {
color: #7f8c8d;
padding: 0px 0px 10px 0px;
text-transform: uppercase;
color: #7f8c8d;
padding: 0px 0px 10px 0px;
text-transform: uppercase;
}
#select-board-dialog .selectBoardContainer .body .container .content .footer {
padding: 10px 5px 10px 0px;
padding: 10px 5px 10px 0px;
}
#select-board-dialog .selectBoardContainer .body .container .content .loading {
font-size: var(--theia-ui-font-size1);
color: var(--theia-arduino-branding-secondary);
padding: 10px 5px 10px 10px;
text-transform: uppercase;
/* The max, min-height comes from `.body .list` 200px + 47px top padding - 2 * 10px top padding */
max-height: 227px;
min-height: 227px;
font-size: var(--theia-ui-font-size1);
color: var(--theia-arduino-branding-secondary);
padding: 10px 5px 10px 10px;
text-transform: uppercase;
/* The max, min-height comes from `.body .list` 200px + 47px top padding - 2 * 10px top padding */
max-height: 227px;
min-height: 227px;
}
#select-board-dialog .selectBoardContainer .body .list .item {
padding: 10px 5px 10px 10px;
display: flex;
justify-content: end;
white-space: nowrap;
overflow-x: hidden;
padding: 10px 5px 10px 10px;
display: flex;
justify-content: end;
white-space: nowrap;
overflow-x: hidden;
}
#select-board-dialog .selectBoardContainer .body .list .item .selected-icon {
margin-left: auto;
margin-left: auto;
}
#select-board-dialog .selectBoardContainer .body .list .item .details {
font-size: var(--theia-ui-font-size1);
opacity: var(--theia-mod-disabled-opacity);
width: 155px; /* used heuristics for the calculation */
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--theia-ui-font-size1);
opacity: var(--theia-mod-disabled-opacity);
width: 155px; /* used heuristics for the calculation */
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
}
#select-board-dialog .selectBoardContainer .body .list .item.missing {
opacity: var(--theia-mod-disabled-opacity);
opacity: var(--theia-mod-disabled-opacity);
}
#select-board-dialog .selectBoardContainer .body .list .item:hover {
background: var(--theia-secondaryButton-hoverBackground);
background: var(--theia-secondaryButton-hoverBackground);
}
#select-board-dialog .selectBoardContainer .body .list .label {
max-width: 215px;
width: 215px;
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
max-width: 215px;
width: 215px;
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
}
#select-board-dialog .selectBoardContainer .body .list {
max-height: 200px;
min-height: 200px;
overflow-y: auto;
max-height: 200px;
min-height: 200px;
overflow-y: auto;
}
#select-board-dialog .selectBoardContainer .body .ports.list {
margin: 47px 0px 0px 0px /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
margin: 47px 0px 0px 0px; /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
}
#select-board-dialog .selectBoardContainer .body .search {
margin-bottom: 10px;
display: flex;
align-items: center;
padding-right: 5px;
margin-bottom: 10px;
display: flex;
align-items: center;
padding-right: 5px;
}
.p-Widget.dialogOverlay .dialogContent.select-board-dialog {
width: 500px;
width: 500px;
}
.arduino-boards-toolbar-item-container {
margin-left: 3px;
margin-left: 3px;
}
.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container {
display: flex;
align-items: baseline;
width: 100%;
.arduino-boards-toolbar-item-container
.arduino-boards-toolbar-item
.inner-container {
display: flex;
align-items: baseline;
width: 100%;
}
.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container .notAttached {
width: 10px;
height: 10px;
color: red;
margin: 0 5px;
.arduino-boards-toolbar-item-container
.arduino-boards-toolbar-item
.inner-container
.notAttached {
width: 10px;
height: 10px;
color: red;
margin: 0 5px;
}
.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container .guessed {
width: 10px;
height: 10px;
color: var(--theia-warningBackground);
margin: 0 5px;
.arduino-boards-toolbar-item-container
.arduino-boards-toolbar-item
.inner-container
.guessed {
width: 10px;
height: 10px;
color: var(--theia-warningBackground);
margin: 0 5px;
}
.arduino-boards-toolbar-item-container {
display: flex;
align-items: center;
width: 220px;
display: flex;
align-items: center;
width: 220px;
}
.arduino-boards-toolbar-item .label {
height: 100%;
display: flex;
align-items: center;
margin: 0 5px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
margin: 0 5px;
width: 100%;
}
.arduino-boards-toolbar-item .caret {
width: 10px;
margin-right: 5px;
width: 10px;
margin-right: 5px;
}
.arduino-boards-toolbar-item {
background: var(--theia-tab-unfocusedActiveBackground);
color: var(--theia-foreground);
height: 22px;
display: flex;
width: 100%;
overflow: hidden;
margin: 0px 3px 0px 3px;
border: 1px solid var(--theia-dropdown-border);
background: var(--theia-tab-unfocusedActiveBackground);
color: var(--theia-foreground);
height: 22px;
display: flex;
width: 100%;
overflow: hidden;
margin: 0px 3px 0px 3px;
border: 1px solid var(--theia-dropdown-border);
}
.arduino-boards-dropdown-list {
border: 3px solid var(--theia-activityBar-background);
margin: -1px;
z-index: 1;
border: 1px solid var(--theia-dropdown-border);
border: 3px solid var(--theia-activityBar-background);
margin: -1px;
z-index: 1;
border: 1px solid var(--theia-dropdown-border);
}
.arduino-boards-dropdown-item {
font-size: var(--theia-ui-font-size1);
display: flex;
padding: 10px;
cursor: pointer;
color: var(--theia-foreground);
background: var(--theia-tab-unfocusedActiveBackground);
border: 1px solid var(--theia-tab-unfocusedActiveBackground);
font-size: var(--theia-ui-font-size1);
display: flex;
padding: 10px;
cursor: pointer;
color: var(--theia-foreground);
background: var(--theia-tab-unfocusedActiveBackground);
border: 1px solid var(--theia-tab-unfocusedActiveBackground);
}
.arduino-boards-dropdown-item .fa-check {
color: var(--theia-arduino-branding-primary);
align-self: center;
color: var(--theia-arduino-branding-primary);
align-self: center;
}
.arduino-boards-dropdown-item.selected,
.arduino-boards-dropdown-item:hover {
border: 1px solid var(--theia-focusBorder);
border: 1px solid var(--theia-focusBorder);
}
.arduino-board-dropdown-footer {
color: var(--theia-arduino-branding-primary);
border-top: 1px solid var(--theia-dropdown-border);
}

View File

@@ -0,0 +1,74 @@
.certificate-uploader-dialog {
width: 600px;
}
.certificate-uploader-dialog .theia-select {
border: none !important;
}
.certificate-uploader-dialog .arduino-select__control {
height: 31px;
background: var(--theia-menubar-selectionBackground) !important;
}
.certificate-uploader-dialog .dialogRow > button{
margin-right: 3px;
}
.certificate-uploader-dialog .certificate-list {
border: 1px solid #BDC7C7;
border-radius: 2px;;
background: var(--theia-menubar-selectionBackground) !important;
overflow: auto;
height: 120px;
flex: 1;
}
.certificate-uploader-dialog .certificate-list .certificate-row {
display: flex;
padding: 6px 10px 5px 10px
}
.certificate-uploader-dialog .certificate-list .certificate-row:hover {
background-color: var(--theia-list-activeSelectionBackground);
}
.certificate-uploader-dialog .upload-status {
display: flex;
align-items: center;
flex: 1;
}
.certificate-uploader-dialog .success {
display: flex;
align-items: center;
color: #1DA086;
}
.certificate-uploader-dialog .warn {
color: #C11F09;
}
.certificate-uploader-dialog .status-icon {
margin-right: 10px;
}
.certificate-uploader-dialog .add-cert-btn {
display: flex;
align-items: center;
justify-content: space-between;
}
.certificate-uploader-dialog .add-cert-btn .caret {
margin-left: 6px;
}
.certificate-add {
padding: 16px;
background-color: var(--theia-list-hoverBackground);
border-radius: 3px;
border: 1px solid #BDC7C7;
}
.certificate-add input {
margin-top: 12px;
padding: 0 12px;
width: 100%;
box-sizing: border-box;
}

View File

@@ -0,0 +1,4 @@
.codicon-debug-alt:before {
font-family: 'FontAwesome' !important;
content: "\e905" !important
}

View File

@@ -0,0 +1,65 @@
.p-Widget.dialogOverlay .dialogBlock {
border-radius: 3px;
padding: 0 28px;
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.25);
min-height: 0px;
}
.p-Widget.dialogOverlay .dialogBlock .dialogTitle {
padding: 36px 0 28px;
font-weight: 500;
background-color: transparent;
font-size: var(--theia-ui-font-size2);
color: var(--theia-list-inactiveSelectionForeground);
min-height: 0;
}
.p-Widget.dialogOverlay .dialogBlock .dialogControl {
padding: 0 0 36px;
min-height: 0;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent {
padding: 0;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent > div {
padding: 0 0 12px;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection {
margin-top: 28px;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection:first-child {
margin-top: 0;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow {
margin-top: 16px;
display: flex;
align-items: center;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow .spinner {
background: var(--theia-icon-loading) center center no-repeat;
animation: theia-spin 1.25s linear infinite;
width: 30px;
height: 30px;
}
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow:first-child {
margin-top: 0px;
}
.fl1{
flex: 1;
}
.status-icon {
margin-right: 6px;
font-size: 17px;
}
.fa.disabled {
opacity: .4;
}

View File

@@ -4,3 +4,7 @@
background-size: 13px;
background-image: var(--theia-icon-circle);
}
.monaco-list-row.show-file-icons.focused {
background-color: #d6ebff;
}

View File

@@ -0,0 +1,32 @@
.firmware-uploader-dialog {
width: 600px;
}
.firmware-uploader-dialog .theia-select {
border: none !important;
}
.firmware-uploader-dialog .arduino-select__control {
height: 31px;
background: var(--theia-menubar-selectionBackground) !important;
}
.firmware-uploader-dialog .dialogRow > button{
width: 33%;
margin-right: 3px;
}
.firmware-uploader-dialog #firmware-select {
flex: unset;
}
.firmware-uploader-dialog .success {
color: #1DA086;
}
.firmware-uploader-dialog .warn {
color: #C11F09;
}
.firmware-uploader-dialog .status-icon {
margin-right: 10px;
}

View File

@@ -0,0 +1,711 @@
@font-face {
font-family: 'FontAwesome';
src:
url('fonts/FontAwesome.ttf?nuchcq') format('truetype'),
url('fonts/FontAwesome.woff?nuchcq') format('woff'),
url('fonts/FontAwesome.svg?nuchcq#FontAwesome') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
.fa {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'FontAwesome' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.fa-arduino-debugger:before {
content: "\e905";
}
.fa-arduino-search:before {
content: "\e901";
}
.fa-arduino-boards:before {
content: "\e902";
}
.fa-arduino-library:before {
content: "\e903";
}
.fa-arduino-folder:before {
content: "\e904";
}
.fa-reload:before {
content: "\e900";
}
.fa-asterisk:before {
content: "\f069";
}
.fa-plus:before {
content: "\f067";
}
.fa-question:before {
content: "\f128";
}
.fa-minus:before {
content: "\f068";
}
.fa-music:before {
content: "\f001";
}
.fa-search:before {
content: "\f002";
}
.fa-envelope-o:before {
content: "\f003";
}
.fa-heart:before {
content: "\f004";
}
.fa-star:before {
content: "\f005";
}
.fa-star-o:before {
content: "\f006";
}
.fa-user:before {
content: "\f007";
}
.fa-film:before {
content: "\f008";
}
.fa-th-large:before {
content: "\f009";
}
.fa-th:before {
content: "\f00a";
}
.fa-th-list:before {
content: "\f00b";
}
.fa-check:before {
content: "\f00c";
}
.fa-close:before {
content: "\f00d";
}
.fa-remove:before {
content: "\f00d";
}
.fa-times:before {
content: "\f00d";
}
.fa-search-plus:before {
content: "\f00e";
}
.fa-search-minus:before {
content: "\f010";
}
.fa-power-off:before {
content: "\f011";
}
.fa-signal:before {
content: "\f012";
}
.fa-cog:before {
content: "\f013";
}
.fa-gear:before {
content: "\f013";
}
.fa-trash-o:before {
content: "\f014";
}
.fa-home:before {
content: "\f015";
}
.fa-file-o:before {
content: "\f016";
}
.fa-clock-o:before {
content: "\f017";
}
.fa-road:before {
content: "\f018";
}
.fa-download:before {
content: "\f019";
}
.fa-arrow-circle-o-down:before {
content: "\f01a";
}
.fa-arrow-circle-o-up:before {
content: "\f01b";
}
.fa-inbox:before {
content: "\f01c";
}
.fa-play-circle-o:before {
content: "\f01d";
}
.fa-repeat:before {
content: "\f01e";
}
.fa-rotate-right:before {
content: "\f01e";
}
.fa-refresh:before {
content: "\f021";
}
.fa-list-alt:before {
content: "\f022";
}
.fa-lock:before {
content: "\f023";
}
.fa-flag:before {
content: "\f024";
}
.fa-volume-off:before {
content: "\f026";
}
.fa-volume-down:before {
content: "\f027";
}
.fa-volume-up:before {
content: "\f028";
}
.fa-qrcode:before {
content: "\f029";
}
.fa-tag:before {
content: "\f02b";
}
.fa-tags:before {
content: "\f02c";
}
.fa-book:before {
content: "\f02d";
}
.fa-bookmark:before {
content: "\f02e";
}
.fa-print:before {
content: "\f02f";
}
.fa-text-height:before {
content: "\f034";
}
.fa-text-width:before {
content: "\f035";
}
.fa-align-left:before {
content: "\f036";
}
.fa-align-center:before {
content: "\f037";
}
.fa-align-right:before {
content: "\f038";
}
.fa-align-justify:before {
content: "\f039";
}
.fa-list:before {
content: "\f03a";
}
.fa-dedent:before {
content: "\f03b";
}
.fa-outdent:before {
content: "\f03b";
}
.fa-indent:before {
content: "\f03c";
}
.fa-video-camera:before {
content: "\f03d";
}
.fa-pencil:before {
content: "\f040";
}
.fa-map-marker:before {
content: "\f041";
}
.fa-adjust:before {
content: "\f042";
}
.fa-tint:before {
content: "\f043";
}
.fa-edit:before {
content: "\f044";
}
.fa-pencil-square-o:before {
content: "\f044";
}
.fa-share-square-o:before {
content: "\f045";
}
.fa-check-square-o:before {
content: "\f046";
}
.fa-arrows:before {
content: "\f047";
}
.fa-step-backward:before {
content: "\f048";
}
.fa-fast-backward:before {
content: "\f049";
}
.fa-backward:before {
content: "\f04a";
}
.fa-play:before {
content: "\f04b";
}
.fa-pause:before {
content: "\f04c";
}
.fa-stop:before {
content: "\f04d";
}
.fa-forward:before {
content: "\f04e";
}
.fa-fast-forward:before {
content: "\f050";
}
.fa-step-forward:before {
content: "\f051";
}
.fa-eject:before {
content: "\f052";
}
.fa-chevron-left:before {
content: "\f053";
}
.fa-chevron-right:before {
content: "\f054";
}
.fa-plus-circle:before {
content: "\f055";
}
.fa-minus-circle:before {
content: "\f056";
}
.fa-times-circle:before {
content: "\f057";
}
.fa-check-circle:before {
content: "\f058";
}
.fa-question-circle:before {
content: "\f059";
}
.fa-info-circle:before {
content: "\f05a";
}
.fa-crosshairs:before {
content: "\f05b";
}
.fa-times-circle-o:before {
content: "\f05c";
}
.fa-check-circle-o:before {
content: "\f05d";
}
.fa-ban:before {
content: "\f05e";
}
.fa-arrow-left:before {
content: "\f060";
}
.fa-arrow-right:before {
content: "\f061";
}
.fa-arrow-up:before {
content: "\f062";
}
.fa-arrow-down:before {
content: "\f063";
}
.fa-mail-forward:before {
content: "\f064";
}
.fa-share:before {
content: "\f064";
}
.fa-expand:before {
content: "\f065";
}
.fa-compress:before {
content: "\f066";
}
.fa-exclamation-circle:before {
content: "\f06a";
}
.fa-eye:before {
content: "\f06e";
}
.fa-eye-slash:before {
content: "\f070";
}
.fa-exclamation-triangle:before {
content: "\f071";
}
.fa-warning:before {
content: "\f071";
}
.fa-calendar:before {
content: "\f073";
}
.fa-random:before {
content: "\f074";
}
.fa-comment:before {
content: "\f075";
}
.fa-chevron-up:before {
content: "\f077";
}
.fa-chevron-down:before {
content: "\f078";
}
.fa-retweet:before {
content: "\f079";
}
.fa-folder:before {
content: "\f07b";
}
.fa-folder-open:before {
content: "\f07c";
}
.fa-arrows-v:before {
content: "\f07d";
}
.fa-arrows-h:before {
content: "\f07e";
}
.fa-bar-chart:before {
content: "\f080";
}
.fa-bar-chart-o:before {
content: "\f080";
}
.fa-cogs:before {
content: "\f085";
}
.fa-gears:before {
content: "\f085";
}
.fa-thumbs-o-up:before {
content: "\f087";
}
.fa-thumbs-o-down:before {
content: "\f088";
}
.fa-star-half:before {
content: "\f089";
}
.fa-heart-o:before {
content: "\f08a";
}
.fa-sign-out:before {
content: "\f08b";
}
.fa-thumb-tack:before {
content: "\f08d";
}
.fa-external-link:before {
content: "\f08e";
}
.fa-sign-in:before {
content: "\f090";
}
.fa-upload:before {
content: "\f093";
}
.fa-square-o:before {
content: "\f096";
}
.fa-bookmark-o:before {
content: "\f097";
}
.fa-hdd-o:before {
content: "\f0a0";
}
.fa-bell-o:before {
content: "\f0a2";
}
.fa-certificate:before {
content: "\f0a3";
}
.fa-arrow-circle-left:before {
content: "\f0a8";
}
.fa-arrow-circle-right:before {
content: "\f0a9";
}
.fa-arrow-circle-up:before {
content: "\f0aa";
}
.fa-arrow-circle-down:before {
content: "\f0ab";
}
.fa-globe:before {
content: "\f0ac";
}
.fa-wrench:before {
content: "\f0ad";
}
.fa-tasks:before {
content: "\f0ae";
}
.fa-filter:before {
content: "\f0b0";
}
.fa-briefcase:before {
content: "\f0b1";
}
.fa-arrows-alt:before {
content: "\f0b2";
}
.fa-cloud:before {
content: "\f0c2";
}
.fa-copy:before {
content: "\f0c5";
}
.fa-files-o:before {
content: "\f0c5";
}
.fa-floppy-o:before {
content: "\f0c7";
}
.fa-save:before {
content: "\f0c7";
}
.fa-square:before {
content: "\f0c8";
}
.fa-bars:before {
content: "\f0c9";
}
.fa-navicon:before {
content: "\f0c9";
}
.fa-reorder:before {
content: "\f0c9";
}
.fa-list-ul:before {
content: "\f0ca";
}
.fa-list-ol:before {
content: "\f0cb";
}
.fa-table:before {
content: "\f0ce";
}
.fa-caret-down:before {
content: "\f0d7";
}
.fa-caret-up:before {
content: "\f0d8";
}
.fa-caret-left:before {
content: "\f0d9";
}
.fa-caret-right:before {
content: "\f0da";
}
.fa-columns:before {
content: "\f0db";
}
.fa-sort:before {
content: "\f0dc";
}
.fa-unsorted:before {
content: "\f0dc";
}
.fa-sort-desc:before {
content: "\f0dd";
}
.fa-sort-down:before {
content: "\f0dd";
}
.fa-sort-asc:before {
content: "\f0de";
}
.fa-sort-up:before {
content: "\f0de";
}
.fa-rotate-left:before {
content: "\f0e2";
}
.fa-undo:before {
content: "\f0e2";
}
.fa-file-text-o:before {
content: "\f0f6";
}
.fa-plus-square:before {
content: "\f0fe";
}
.fa-angle-double-left:before {
content: "\f100";
}
.fa-angle-double-right:before {
content: "\f101";
}
.fa-angle-double-up:before {
content: "\f102";
}
.fa-angle-double-down:before {
content: "\f103";
}
.fa-angle-left:before {
content: "\f104";
}
.fa-angle-right:before {
content: "\f105";
}
.fa-angle-up:before {
content: "\f106";
}
.fa-angle-down:before {
content: "\f107";
}
.fa-desktop:before {
content: "\f108";
}
.fa-laptop:before {
content: "\f109";
}
.fa-tablet:before {
content: "\f10a";
}
.fa-mobile:before {
content: "\f10b";
}
.fa-mobile-phone:before {
content: "\f10b";
}
.fa-circle-o:before {
content: "\f10c";
}
.fa-quote-left:before {
content: "\f10d";
}
.fa-quote-right:before {
content: "\f10e";
}
.fa-spinner:before {
content: "\f110";
}
.fa-circle:before {
content: "\f111";
}
.fa-mail-reply:before {
content: "\f112";
}
.fa-reply:before {
content: "\f112";
}
.fa-github-alt:before {
content: "\f113";
}
.fa-folder-o:before {
content: "\f114";
}
.fa-folder-open-o:before {
content: "\f115";
}
.fa-gamepad:before {
content: "\f11b";
}
.fa-keyboard-o:before {
content: "\f11c";
}
.fa-flag-o:before {
content: "\f11d";
}
.fa-flag-checkered:before {
content: "\f11e";
}
.fa-terminal:before {
content: "\f120";
}
.fa-code:before {
content: "\f121";
}
.fa-mail-reply-all:before {
content: "\f122";
}
.fa-reply-all:before {
content: "\f122";
}
.fa-star-half-empty:before {
content: "\f123";
}
.fa-star-half-full:before {
content: "\f123";
}
.fa-star-half-o:before {
content: "\f123";
}
.fa-crop:before {
content: "\f125";
}
.fa-code-fork:before {
content: "\f126";
}
.fa-chain-broken:before {
content: "\f127";
}
.fa-unlink:before {
content: "\f127";
}
.fa-info:before {
content: "\f129";
}
.fa-exclamation:before {
content: "\f12a";
}
.fa-rocket:before {
content: "\f135";
}
.fa-maxcdn:before {
content: "\f136";
}
.fa-chevron-circle-left:before {
content: "\f137";
}
.fa-chevron-circle-right:before {
content: "\f138";
}
.fa-chevron-circle-up:before {
content: "\f139";
}
.fa-chevron-circle-down:before {
content: "\f13a";
}
.fa-ellipsis-h:before {
content: "\f141";
}
.fa-long-arrow-down:before {
content: "\f175";
}
.fa-long-arrow-up:before {
content: "\f176";
}
.fa-long-arrow-left:before {
content: "\f177";
}
.fa-long-arrow-right:before {
content: "\f178";
}
.fa-microchip:before {
content: "\f2db";
}

View File

@@ -0,0 +1,194 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="FontAwesome" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="reload" d="M512.083-9.079c118.817 0 232.769 47.2 316.781 131.213 84.019 84.019 131.219 197.969 131.219 316.785 0 8.487-3.373 16.627-9.376 22.628-5.997 6.001-14.138 9.373-22.624 9.373s-16.627-3.372-22.63-9.373c-5.997-6.001-9.37-14.141-9.37-22.627-0.019-87.81-30.131-172.959-85.318-241.26s-132.115-115.622-217.962-134.086c-85.848-18.458-175.428-6.931-253.811 32.646-78.383 39.584-140.833 104.832-176.941 184.87-36.108 80.045-43.693 170.045-21.49 255.001s72.852 159.737 143.505 211.878c70.653 52.141 157.042 78.492 244.768 74.662s171.487-37.612 237.33-95.712h-158.081c-8.487 0-16.626-3.372-22.627-9.373s-9.373-14.141-9.373-22.627c0-8.487 3.372-16.627 9.373-22.628s14.14-9.372 22.627-9.372h224.001c8.486 0 16.627 3.371 22.624 9.372 6.003 6.001 9.376 14.141 9.376 22.628v224c0 8.487-3.373 16.626-9.376 22.627-5.997 6.001-14.138 9.373-22.624 9.373s-16.627-3.371-22.63-9.373c-5.997-6.001-9.37-14.14-9.37-22.627v-136.96c-55.162 46.332-120.678 78.68-191.002 94.301s-143.375 14.052-212.963-4.571c-69.588-18.623-133.659-53.753-186.78-102.41s-93.725-109.405-118.369-177.096c-24.644-67.69-32.602-140.324-23.199-211.745 9.404-71.419 35.891-139.521 77.216-198.523 41.325-59.008 96.27-107.174 160.174-140.422s134.885-50.598 206.922-50.573v0z" />
<glyph unicode="&#xe901;" glyph-name="arduino-search" d="M984.883 21.297l-272.696 272.696c63.208 77.401 94.257 176.128 86.728 275.773-7.533 99.645-53.065 192.585-127.19 259.604s-171.168 102.992-271.064 100.479c-99.897-2.512-195.009-43.317-265.67-113.978s-111.466-165.773-113.978-265.67c-2.512-99.898 33.46-196.942 100.479-271.062 67.019-74.124 159.959-119.661 259.604-127.19 99.643-7.533 198.375 23.516 275.771 86.724l272.696-272.696c3.624-3.651 7.929-6.549 12.68-8.526 4.746-1.977 9.835-2.996 14.982-2.996 5.142 0 10.231 1.020 14.982 2.996 4.746 1.977 9.056 4.875 12.675 8.526 3.651 3.624 6.549 7.934 8.526 12.68 1.981 4.746 2.996 9.839 2.996 14.982s-1.015 10.236-2.996 14.982c-1.977 4.746-4.875 9.056-8.526 12.675zM411.833 227.379c-61.639 0-121.894 18.276-173.145 52.522s-91.197 82.917-114.785 139.865c-23.588 56.948-29.76 119.61-17.735 180.065s41.707 115.986 85.293 159.571c43.585 43.585 99.117 73.268 159.571 85.293s123.12 5.854 180.063-17.735c56.948-23.588 105.623-63.533 139.865-114.784 34.246-51.251 52.527-111.506 52.527-173.145 0-82.655-32.835-161.926-91.283-220.374-58.448-58.444-137.715-91.278-220.371-91.278z" />
<glyph unicode="&#xe902;" glyph-name="arduino-boards" horiz-adv-x="1178" d="M1107.2 931.657h-896c-11.881 0-23.277-4.72-31.678-13.122s-13.122-19.797-13.122-31.678v-268.8h-89.6c-11.881 0-23.277-4.72-31.678-13.122s-13.122-19.797-13.122-31.678v-268.8c0-11.884 4.72-23.276 13.122-31.677s19.797-13.123 31.678-13.123h89.6v-268.8c0-11.884 4.72-23.276 13.122-31.677s19.797-13.123 31.678-13.123h896c11.884 0 23.276 4.721 31.677 13.123s13.123 19.794 13.123 31.677v896c0 11.881-4.721 23.277-13.123 31.678s-19.794 13.122-31.677 13.122zM121.6 349.257v179.2h134.4v-179.2h-134.4zM1062.4 35.657h-89.6v44.8c0 11.884-4.721 23.276-13.123 31.677s-19.794 13.123-31.677 13.123c-11.884 0-23.276-4.721-31.677-13.123s-13.123-19.794-13.123-31.677v-44.8h-89.6v44.8c0 11.884-4.721 23.276-13.123 31.677s-19.794 13.123-31.677 13.123c-11.884 0-23.276-4.721-31.677-13.123s-13.123-19.794-13.123-31.677v-44.8h-89.6v44.8c0 11.884-4.721 23.276-13.123 31.677s-19.794 13.123-31.677 13.123c-11.884 0-23.276-4.721-31.677-13.123s-13.123-19.794-13.123-31.677v-44.8h-89.6v44.8c0 11.884-4.72 23.276-13.122 31.677s-19.797 13.123-31.678 13.123c-11.881 0-23.277-4.721-31.678-13.123s-13.122-19.794-13.122-31.677v-44.8h-89.6v224h44.8c11.881 0 23.277 4.721 31.678 13.123s13.122 19.794 13.122 31.677v268.8c0 11.881-4.72 23.277-13.122 31.678s-19.797 13.122-31.678 13.122h-44.8v224h89.6v-44.8c0-11.881 4.72-23.277 13.122-31.678s19.797-13.122 31.678-13.122c11.881 0 23.277 4.72 31.678 13.122s13.122 19.797 13.122 31.678v44.8h89.6v-44.8c0-11.881 4.721-23.277 13.123-31.678s19.794-13.122 31.677-13.122c11.884 0 23.276 4.72 31.677 13.122s13.123 19.797 13.123 31.678v44.8h89.6v-44.8c0-11.881 4.721-23.277 13.123-31.678s19.794-13.122 31.677-13.122c11.884 0 23.276 4.72 31.677 13.122s13.123 19.797 13.123 31.678v44.8h89.6v-44.8c0-11.881 4.721-23.277 13.123-31.678s19.794-13.122 31.677-13.122c11.884 0 23.276 4.72 31.677 13.122s13.123 19.797 13.123 31.678v44.8h89.6v-806.4zM928 618.057h-268.8c-11.884 0-23.276-4.72-31.677-13.122s-13.123-19.797-13.123-31.678v-268.8c0-11.884 4.721-23.276 13.123-31.677s19.794-13.123 31.677-13.123h268.8c11.884 0 23.276 4.721 31.677 13.123s13.123 19.794 13.123 31.677v268.8c0 11.881-4.721 23.277-13.123 31.678s-19.794 13.122-31.677 13.122zM883.2 349.257h-179.2v179.2h179.2v-179.2z" />
<glyph unicode="&#xe903;" glyph-name="arduino-library" d="M1006.669 49.097l-164.271 625.707c-2.778 9.338-9.097 17.219-17.604 21.963-8.512 4.743-18.539 5.972-27.942 3.424l-135.518-33.973v220.64c0 9.901-3.934 19.397-10.935 26.399s-16.495 10.935-26.398 10.935h-149.333c-9.903 0-19.396-3.933-26.398-10.935s-10.935-16.497-10.935-26.399v-112h-149.333v74.667c0 9.901-3.933 19.397-10.935 26.399s-16.497 10.935-26.399 10.935h-186.667c-9.901 0-19.397-3.933-26.399-10.935s-10.935-16.497-10.935-26.399v-858.667c0-9.903 3.933-19.396 10.935-26.398s16.497-10.935 26.399-10.935h560c9.903 0 19.396 3.934 26.398 10.935s10.935 16.495 10.935 26.398v435.682l114.987-437.922c2.116-8.213 6.967-15.462 13.751-20.553 6.788-5.090 15.104-7.714 23.582-7.445 3.226-0.363 6.481-0.363 9.707 0l157.918 41.438c9.566 2.483 17.758 8.661 22.775 17.173 2.475 4.527 3.985 9.519 4.437 14.66 0.448 5.141-0.171 10.317-1.822 15.206zM213.333 28.19h-112v784h112v-784zM437.333 28.19h-149.333v672h149.333v-672zM586.667 28.19h-74.667v821.333h74.667v-821.333zM839.040 43.87l-145.225 553.28 85.867 22.773 145.225-554.4-85.867-21.653z" />
<glyph unicode="&#xe904;" glyph-name="arduino-folder" horiz-adv-x="1252" d="M1173.333 737.523h-527.15l-184.175 184.675c-4.651 4.614-10.167 8.263-16.232 10.741s-12.559 3.733-19.11 3.695h-348.444c-13.202 0-25.863-5.244-35.198-14.58s-14.58-21.996-14.58-35.198v-895.999c0-13.204 5.244-25.862 14.58-35.197s21.997-14.581 35.198-14.581h1095.111c13.204 0 25.862 5.245 35.197 14.581s14.581 21.993 14.581 35.197v696.888c0 13.202-5.245 25.863-14.581 35.198s-21.993 14.58-35.197 14.58zM128 837.079h278.258l99.556-99.556h-377.813v99.556zM1123.556 40.635h-995.556v597.333h995.556v-597.333z" />
<glyph unicode="&#xe905;" glyph-name="arduino-debugger" horiz-adv-x="1071" d="M634.224 287.761c-8.983-0.009-17.72 2.956-24.841 8.425-7.126 5.474-12.241 13.149-14.55 21.83s-1.685 17.883 1.778 26.168c3.463 8.29 9.57 15.202 17.366 19.661l304.621 173.966-488.052 278.888v-296.387c0-10.801-4.291-21.16-11.929-28.799s-17.997-11.929-28.799-11.929c-10.801 0-21.16 4.291-28.799 11.929s-11.929 17.997-11.929 28.799v366.545c-0.004 7.135 1.867 14.146 5.426 20.33s8.681 11.324 14.852 14.905c6.171 3.581 13.175 5.477 20.31 5.499s14.15-1.832 20.343-5.376l610.91-349.045c6.232-3.561 11.413-8.707 15.020-14.917 3.603-6.209 5.502-13.261 5.502-20.441s-1.899-14.232-5.502-20.441c-3.607-6.21-8.788-11.356-15.020-14.917l-366.545-209.326c-6.135-3.523-13.089-5.376-20.163-5.367zM512 153.766c10.803 0 21.16 4.291 28.798 11.93s11.93 17.994 11.93 28.798c0 10.803-4.291 21.16-11.93 28.798s-17.994 11.93-28.798 11.93h-43.176c-1.987 17.389-5.532 34.56-10.587 51.316l41.949 41.951c3.798 3.793 6.81 8.304 8.867 13.265 2.053 4.962 3.109 10.277 3.109 15.649s-1.057 10.687-3.109 15.649c-2.057 4.962-5.069 9.467-8.867 13.265s-8.304 6.81-13.265 8.867c-4.962 2.053-10.277 3.114-15.649 3.114s-10.688-1.061-15.65-3.114c-4.961-2.057-9.47-5.069-13.267-8.867l-21.585-21.583c-14.553 22.109-34.216 40.387-57.327 53.285-23.11 12.902-48.988 20.052-75.444 20.838-26.456-0.787-52.334-7.936-75.444-20.838s-42.774-31.181-57.327-53.29l-21.585 21.588c-7.669 7.671-18.070 11.976-28.915 11.976s-21.247-4.305-28.916-11.976c-7.669-7.666-11.978-18.069-11.978-28.914s4.308-21.248 11.977-28.914l41.949-41.951c-5.055-16.756-8.599-33.927-10.589-51.316h-43.171c-10.801 0-21.161-4.291-28.799-11.93s-11.929-17.994-11.929-28.798c0-10.803 4.291-21.16 11.929-28.798s17.997-11.93 28.799-11.93h43.173c1.991-17.389 5.534-34.56 10.589-51.316l-1.222-1.224-40.727-40.727c-7.631-7.685-11.913-18.078-11.913-28.914 0-10.831 4.282-21.225 11.913-28.914 7.72-7.568 18.102-11.813 28.915-11.813s21.194 4.245 28.915 11.813l21.585 21.588c14.553-22.109 34.216-40.387 57.327-53.29s48.989-20.052 75.445-20.838c26.456 0.787 52.334 7.936 75.444 20.838s42.774 31.181 57.327 53.29l21.585-21.588c7.72-7.573 18.102-11.813 28.916-11.813 10.813 0 21.192 4.24 28.914 11.813 7.629 7.689 11.911 18.083 11.911 28.914 0 10.836-4.282 21.229-11.911 28.914l-41.95 41.951c5.055 16.756 8.6 33.927 10.588 51.316h43.176zM288 357.402c37.058 0 69.644-32.991 87.564-81.455h-175.127c17.92 48.463 50.506 81.455 87.564 81.455zM288 31.584c-55.389 0-101.818 74.533-101.818 162.909h203.636c0-88.376-46.429-162.909-101.818-162.909z" />
<glyph unicode="&#xf001;" glyph-name="music" horiz-adv-x="878" d="M877.714 822.857v-640c0-80.571-120.571-109.714-182.857-109.714s-182.857 29.143-182.857 109.714 120.571 109.714 182.857 109.714c37.714 0 75.429-6.857 109.714-22.286v306.857l-438.857-135.429v-405.143c0-80.571-120.571-109.714-182.857-109.714s-182.857 29.143-182.857 109.714 120.571 109.714 182.857 109.714c37.714 0 75.429-6.857 109.714-22.286v552.571c0 24 16 45.143 38.857 52.571l475.429 146.286c5.143 1.714 10.286 2.286 16 2.286 30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf002;" glyph-name="search" horiz-adv-x="951" d="M658.286 475.428c0 141.143-114.857 256-256 256s-256-114.857-256-256 114.857-256 256-256 256 114.857 256 256zM950.857 0c0-40-33.143-73.143-73.143-73.143-19.429 0-38.286 8-51.429 21.714l-196 195.429c-66.857-46.286-146.857-70.857-228-70.857-222.286 0-402.286 180-402.286 402.286s180 402.286 402.286 402.286 402.286-180 402.286-402.286c0-81.143-24.571-161.143-70.857-228l196-196c13.143-13.143 21.143-32 21.143-51.429z" />
<glyph unicode="&#xf003;" glyph-name="envelope-o" d="M950.857 91.428v438.857c-12-13.714-25.143-26.286-39.429-37.714-81.714-62.857-164-126.857-243.429-193.143-42.857-36-96-80-155.429-80h-1.143c-59.429 0-112.571 44-155.429 80-79.429 66.286-161.714 130.286-243.429 193.143-14.286 11.429-27.429 24-39.429 37.714v-438.857c0-9.714 8.571-18.286 18.286-18.286h841.143c9.714 0 18.286 8.571 18.286 18.286zM950.857 692c0 14.286 3.429 39.429-18.286 39.429h-841.143c-9.714 0-18.286-8.571-18.286-18.286 0-65.143 32.571-121.714 84-162.286 76.571-60 153.143-120.571 229.143-181.143 30.286-24.571 85.143-77.143 125.143-77.143h1.143c40 0 94.857 52.571 125.143 77.143 76 60.571 152.571 121.143 229.143 181.143 37.143 29.143 84 92.571 84 141.143zM1024 713.143v-621.714c0-50.286-41.143-91.429-91.429-91.429h-841.143c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h841.143c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf004;" glyph-name="heart" d="M512 0c-9.143 0-18.286 3.429-25.143 10.286l-356.571 344c-4.571 4-130.286 118.857-130.286 256 0 167.429 102.286 267.429 273.143 267.429 100 0 193.714-78.857 238.857-123.429 45.143 44.571 138.857 123.429 238.857 123.429 170.857 0 273.143-100 273.143-267.429 0-137.143-125.714-252-130.857-257.143l-356-342.857c-6.857-6.857-16-10.286-25.143-10.286z" />
<glyph unicode="&#xf005;" glyph-name="star" horiz-adv-x="951" d="M950.857 581.143c0-10.286-7.429-20-14.857-27.429l-207.429-202.286 49.143-285.714c0.571-4 0.571-7.429 0.571-11.429 0-14.857-6.857-28.571-23.429-28.571-8 0-16 2.857-22.857 6.857l-256.571 134.857-256.571-134.857c-7.429-4-14.857-6.857-22.857-6.857-16.571 0-24 13.714-24 28.571 0 4 0.571 7.429 1.143 11.429l49.143 285.714-208 202.286c-6.857 7.429-14.286 17.143-14.286 27.429 0 17.143 17.714 24 32 26.286l286.857 41.714 128.571 260c5.143 10.857 14.857 23.429 28 23.429s22.857-12.571 28-23.429l128.571-260 286.857-41.714c13.714-2.286 32-9.143 32-26.286z" />
<glyph unicode="&#xf006;" glyph-name="star-o" horiz-adv-x="951" d="M649.714 377.143l174.857 169.714-241.143 35.429-108 218.286-108-218.286-241.143-35.429 174.857-169.714-41.714-240.571 216 113.714 215.429-113.714zM950.857 581.143c0-10.286-7.429-20-14.857-27.429l-207.429-202.286 49.143-285.714c0.571-4 0.571-7.429 0.571-11.429 0-15.429-6.857-28.571-23.429-28.571-8 0-16 2.857-22.857 6.857l-256.571 134.857-256.571-134.857c-7.429-4-14.857-6.857-22.857-6.857-16.571 0-24 13.714-24 28.571 0 4 0.571 7.429 1.143 11.429l49.143 285.714-208 202.286c-6.857 7.429-14.286 17.143-14.286 27.429 0 17.143 17.714 24 32 26.286l286.857 41.714 128.571 260c5.143 10.857 14.857 23.429 28 23.429s22.857-12.571 28-23.429l128.571-260 286.857-41.714c13.714-2.286 32-9.143 32-26.286z" />
<glyph unicode="&#xf007;" glyph-name="user" horiz-adv-x="731" d="M731.429 151.428c0-83.429-54.857-151.429-121.714-151.429h-488c-66.857 0-121.714 68-121.714 151.429 0 150.286 37.143 324 186.857 324 46.286-45.143 109.143-73.143 178.857-73.143s132.571 28 178.857 73.143c149.714 0 186.857-173.714 186.857-324zM585.143 658.286c0-121.143-98.286-219.429-219.429-219.429s-219.429 98.286-219.429 219.429 98.286 219.429 219.429 219.429 219.429-98.286 219.429-219.429z" />
<glyph unicode="&#xf008;" glyph-name="film" horiz-adv-x="1097" d="M219.429 36.571v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM219.429 256v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM219.429 475.428v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM804.571 36.571v292.571c0 20-16.571 36.571-36.571 36.571h-438.857c-20 0-36.571-16.571-36.571-36.571v-292.571c0-20 16.571-36.571 36.571-36.571h438.857c20 0 36.571 16.571 36.571 36.571zM219.429 694.857v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM1024 36.571v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM804.571 475.428v292.571c0 20-16.571 36.571-36.571 36.571h-438.857c-20 0-36.571-16.571-36.571-36.571v-292.571c0-20 16.571-36.571 36.571-36.571h438.857c20 0 36.571 16.571 36.571 36.571zM1024 256v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM1024 475.428v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM1024 694.857v73.143c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571zM1097.143 786.286v-768c0-50.286-41.143-91.429-91.429-91.429h-914.286c-50.286 0-91.429 41.143-91.429 91.429v768c0 50.286 41.143 91.429 91.429 91.429h914.286c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf009;" glyph-name="th-large" horiz-adv-x="951" d="M438.857 365.714v-219.429c0-40-33.143-73.143-73.143-73.143h-292.571c-40 0-73.143 33.143-73.143 73.143v219.429c0 40 33.143 73.143 73.143 73.143h292.571c40 0 73.143-33.143 73.143-73.143zM438.857 804.571v-219.429c0-40-33.143-73.143-73.143-73.143h-292.571c-40 0-73.143 33.143-73.143 73.143v219.429c0 40 33.143 73.143 73.143 73.143h292.571c40 0 73.143-33.143 73.143-73.143zM950.857 365.714v-219.429c0-40-33.143-73.143-73.143-73.143h-292.571c-40 0-73.143 33.143-73.143 73.143v219.429c0 40 33.143 73.143 73.143 73.143h292.571c40 0 73.143-33.143 73.143-73.143zM950.857 804.571v-219.429c0-40-33.143-73.143-73.143-73.143h-292.571c-40 0-73.143 33.143-73.143 73.143v219.429c0 40 33.143 73.143 73.143 73.143h292.571c40 0 73.143-33.143 73.143-73.143z" />
<glyph unicode="&#xf00a;" glyph-name="th" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf00b;" glyph-name="th-list" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-548.571c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h548.571c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-548.571c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h548.571c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-548.571c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h548.571c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf00c;" glyph-name="check" d="M954.857 627.428c0-14.286-5.714-28.571-16-38.857l-491.429-491.429c-10.286-10.286-24.571-16-38.857-16s-28.571 5.714-38.857 16l-284.571 284.571c-10.286 10.286-16 24.571-16 38.857s5.714 28.571 16 38.857l77.714 77.714c10.286 10.286 24.571 16 38.857 16s28.571-5.714 38.857-16l168-168.571 374.857 375.429c10.286 10.286 24.571 16 38.857 16s28.571-5.714 38.857-16l77.714-77.714c10.286-10.286 16-24.571 16-38.857z" />
<glyph unicode="&#xf00d;" glyph-name="close, remove, times" horiz-adv-x="805" d="M725.322 168.514c3.477-3.448 6.237-7.547 8.118-12.069 1.885-4.515 2.853-9.364 2.853-14.259s-0.967-9.744-2.853-14.266c-1.881-4.515-4.641-8.623-8.118-12.069-3.448-3.472-7.547-6.232-12.069-8.118-4.515-1.881-9.364-2.848-14.259-2.848s-9.744 0.967-14.266 2.848c-4.522 1.885-8.623 4.646-12.069 8.118l-270.371 270.375-270.372-270.375c-3.448-3.472-7.549-6.232-12.069-8.118-4.519-1.881-9.366-2.848-14.263-2.848s-9.744 0.967-14.263 2.848c-4.519 1.885-8.622 4.646-12.069 8.118-3.474 3.448-6.235 7.555-8.118 12.069-1.884 4.522-2.853 9.37-2.853 14.266s0.97 9.744 2.853 14.259c1.884 4.522 4.643 8.623 8.118 12.069l270.372 270.375-270.372 270.372c-3.456 3.456-6.201 7.565-8.072 12.082s-2.835 9.36-2.835 14.25c0 4.891 0.964 9.732 2.835 14.25s4.617 8.626 8.072 12.081c3.456 3.456 7.564 6.201 12.081 8.072s9.361 2.835 14.25 2.835c4.891 0 9.732-0.964 14.25-2.835s8.626-4.617 12.081-8.072l270.372-270.372 270.371 270.372c6.984 6.983 16.455 10.909 26.335 10.909 9.875 0 19.347-3.923 26.33-10.909s10.909-16.456 10.909-26.333c0-9.877-3.923-19.348-10.909-26.333l-270.371-270.372 270.371-270.375z" />
<glyph unicode="&#xf00e;" glyph-name="search-plus" horiz-adv-x="951" d="M585.143 493.714v-36.571c0-9.714-8.571-18.286-18.286-18.286h-128v-128c0-9.714-8.571-18.286-18.286-18.286h-36.571c-9.714 0-18.286 8.571-18.286 18.286v128h-128c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h128v128c0 9.714 8.571 18.286 18.286 18.286h36.571c9.714 0 18.286-8.571 18.286-18.286v-128h128c9.714 0 18.286-8.571 18.286-18.286zM658.286 475.428c0 141.143-114.857 256-256 256s-256-114.857-256-256 114.857-256 256-256 256 114.857 256 256zM950.857 0c0-40.571-32.571-73.143-73.143-73.143-19.429 0-38.286 8-51.429 21.714l-196 195.429c-66.857-46.286-146.857-70.857-228-70.857-222.286 0-402.286 180-402.286 402.286s180 402.286 402.286 402.286 402.286-180 402.286-402.286c0-81.143-24.571-161.143-70.857-228l196-196c13.143-13.143 21.143-32 21.143-51.429z" />
<glyph unicode="&#xf010;" glyph-name="search-minus" horiz-adv-x="951" d="M585.143 493.714v-36.571c0-9.714-8.571-18.286-18.286-18.286h-329.143c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h329.143c9.714 0 18.286-8.571 18.286-18.286zM658.286 475.428c0 141.143-114.857 256-256 256s-256-114.857-256-256 114.857-256 256-256 256 114.857 256 256zM950.857 0c0-40.571-32.571-73.143-73.143-73.143-19.429 0-38.286 8-51.429 21.714l-196 195.429c-66.857-46.286-146.857-70.857-228-70.857-222.286 0-402.286 180-402.286 402.286s180 402.286 402.286 402.286 402.286-180 402.286-402.286c0-81.143-24.571-161.143-70.857-228l196-196c13.143-13.143 21.143-32 21.143-51.429z" />
<glyph unicode="&#xf011;" glyph-name="power-off" horiz-adv-x="878" d="M877.714 438.857c0-241.714-197.143-438.857-438.857-438.857s-438.857 197.143-438.857 438.857c0 138.857 64 266.857 175.429 350.286 32.571 24.571 78.286 18.286 102.286-14.286 24.571-32 17.714-78.286-14.286-102.286-74.286-56-117.143-141.143-117.143-233.714 0-161.143 131.429-292.571 292.571-292.571s292.571 131.429 292.571 292.571c0 92.571-42.857 177.714-117.143 233.714-32 24-38.857 70.286-14.286 102.286 24 32.571 70.286 38.857 102.286 14.286 111.429-83.429 175.429-211.429 175.429-350.286zM512 877.714v-365.714c0-40-33.143-73.143-73.143-73.143s-73.143 33.143-73.143 73.143v365.714c0 40 33.143 73.143 73.143 73.143s73.143-33.143 73.143-73.143z" />
<glyph unicode="&#xf012;" glyph-name="signal" d="M146.286 128v-109.714c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286zM365.714 201.143v-182.857c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v182.857c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286zM585.143 347.428v-329.143c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v329.143c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286zM804.571 566.857v-548.571c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v548.571c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286zM1024 859.428v-841.143c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v841.143c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286z" />
<glyph unicode="&#xf013;" glyph-name="cog, gear" horiz-adv-x="878" d="M585.143 438.857c0 80.571-65.714 146.286-146.286 146.286s-146.286-65.714-146.286-146.286 65.714-146.286 146.286-146.286 146.286 65.714 146.286 146.286zM877.714 501.143v-126.857c0-8.571-6.857-18.857-16-20.571l-105.714-16c-6.286-18.286-13.143-35.429-22.286-52 19.429-28 40-53.143 61.143-78.857 3.429-4 5.714-9.143 5.714-14.286s-1.714-9.143-5.143-13.143c-13.714-18.286-90.857-102.286-110.286-102.286-5.143 0-10.286 2.286-14.857 5.143l-78.857 61.714c-16.571-8.571-34.286-16-52-21.714-4-34.857-7.429-72-16.571-106.286-2.286-9.143-10.286-16-20.571-16h-126.857c-10.286 0-19.429 7.429-20.571 17.143l-16 105.143c-17.714 5.714-34.857 12.571-51.429 21.143l-80.571-61.143c-4-3.429-9.143-5.143-14.286-5.143s-10.286 2.286-14.286 6.286c-30.286 27.429-70.286 62.857-94.286 96-2.857 4-4 8.571-4 13.143 0 5.143 1.714 9.143 4.571 13.143 19.429 26.286 40.571 51.429 60 78.286-9.714 18.286-17.714 37.143-23.429 56.571l-104.571 15.429c-9.714 1.714-16.571 10.857-16.571 20.571v126.857c0 8.571 6.857 18.857 15.429 20.571l106.286 16c5.714 18.286 13.143 35.429 22.286 52.571-19.429 27.429-40 53.143-61.143 78.857-3.429 4-5.714 8.571-5.714 13.714s2.286 9.143 5.143 13.143c13.714 18.857 90.857 102.286 110.286 102.286 5.143 0 10.286-2.286 14.857-5.714l78.857-61.143c16.571 8.571 34.286 16 52 21.714 4 34.857 7.429 72 16.571 106.286 2.286 9.143 10.286 16 20.571 16h126.857c10.286 0 19.429-7.429 20.571-17.143l16-105.143c17.714-5.714 34.857-12.571 51.429-21.143l81.143 61.143c3.429 3.429 8.571 5.143 13.714 5.143s10.286-2.286 14.286-5.714c30.286-28 70.286-63.429 94.286-97.143 2.857-3.429 4-8 4-12.571 0-5.143-1.714-9.143-4.571-13.143-19.429-26.286-40.571-51.429-60-78.286 9.714-18.286 17.714-37.143 23.429-56l104.571-16c9.714-1.714 16.571-10.857 16.571-20.571z" />
<glyph unicode="&#xf014;" glyph-name="trash-o" horiz-adv-x="805" d="M292.571 530.286v-329.143c0-10.286-8-18.286-18.286-18.286h-36.571c-10.286 0-18.286 8-18.286 18.286v329.143c0 10.286 8 18.286 18.286 18.286h36.571c10.286 0 18.286-8 18.286-18.286zM438.857 530.286v-329.143c0-10.286-8-18.286-18.286-18.286h-36.571c-10.286 0-18.286 8-18.286 18.286v329.143c0 10.286 8 18.286 18.286 18.286h36.571c10.286 0 18.286-8 18.286-18.286zM585.143 530.286v-329.143c0-10.286-8-18.286-18.286-18.286h-36.571c-10.286 0-18.286 8-18.286 18.286v329.143c0 10.286 8 18.286 18.286 18.286h36.571c10.286 0 18.286-8 18.286-18.286zM658.286 116.571v541.714h-512v-541.714c0-27.429 15.429-43.429 18.286-43.429h475.429c2.857 0 18.286 16 18.286 43.429zM274.286 731.428h256l-27.429 66.857c-1.714 2.286-6.857 5.714-9.714 6.286h-181.143c-3.429-0.571-8-4-9.714-6.286zM804.571 713.143v-36.571c0-10.286-8-18.286-18.286-18.286h-54.857v-541.714c0-62.857-41.143-116.571-91.429-116.571h-475.429c-50.286 0-91.429 51.429-91.429 114.286v544h-54.857c-10.286 0-18.286 8-18.286 18.286v36.571c0 10.286 8 18.286 18.286 18.286h176.571l40 95.429c11.429 28 45.714 50.857 76 50.857h182.857c30.286 0 64.571-22.857 76-50.857l40-95.429h176.571c10.286 0 18.286-8 18.286-18.286z" />
<glyph unicode="&#xf015;" glyph-name="home" horiz-adv-x="951" d="M804.571 384v-274.286c0-20-16.571-36.571-36.571-36.571h-219.429v219.429h-146.286v-219.429h-219.429c-20 0-36.571 16.571-36.571 36.571v274.286c0 1.143 0.571 2.286 0.571 3.429l328.571 270.857 328.571-270.857c0.571-1.143 0.571-2.286 0.571-3.429zM932 423.428l-35.429-42.286c-2.857-3.429-7.429-5.714-12-6.286h-1.714c-4.571 0-8.571 1.143-12 4l-395.429 329.714-395.429-329.714c-4-2.857-8.571-4.571-13.714-4-4.571 0.571-9.143 2.857-12 6.286l-35.429 42.286c-6.286 7.429-5.143 19.429 2.286 25.714l410.857 342.286c24 20 62.857 20 86.857 0l139.429-116.571v111.429c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286v-233.143l125.143-104c7.429-6.286 8.571-18.286 2.286-25.714z" />
<glyph unicode="&#xf016;" glyph-name="file-o" horiz-adv-x="878" d="M838.857 733.714c21.143-21.143 38.857-63.429 38.857-93.714v-658.286c0-30.286-24.571-54.857-54.857-54.857h-768c-30.286 0-54.857 24.571-54.857 54.857v914.286c0 30.286 24.571 54.857 54.857 54.857h512c30.286 0 72.571-17.714 93.714-38.857zM585.143 873.143v-214.857h214.857c-3.429 9.714-8.571 19.429-12.571 23.429l-178.857 178.857c-4 4-13.714 9.143-23.429 12.571zM804.571 0v585.143h-237.714c-30.286 0-54.857 24.571-54.857 54.857v237.714h-438.857v-877.714h731.429z" />
<glyph unicode="&#xf017;" glyph-name="clock-o" horiz-adv-x="878" d="M512 640v-256c0-10.286-8-18.286-18.286-18.286h-182.857c-10.286 0-18.286 8-18.286 18.286v36.571c0 10.286 8 18.286 18.286 18.286h128v201.143c0 10.286 8 18.286 18.286 18.286h36.571c10.286 0 18.286-8 18.286-18.286zM749.714 438.857c0 171.429-139.429 310.857-310.857 310.857s-310.857-139.429-310.857-310.857 139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf019;" glyph-name="download" horiz-adv-x="951" d="M731.429 182.857c0 20-16.571 36.571-36.571 36.571s-36.571-16.571-36.571-36.571 16.571-36.571 36.571-36.571 36.571 16.571 36.571 36.571zM877.714 182.857c0 20-16.571 36.571-36.571 36.571s-36.571-16.571-36.571-36.571 16.571-36.571 36.571-36.571 36.571 16.571 36.571 36.571zM950.857 310.857v-182.857c0-30.286-24.571-54.857-54.857-54.857h-841.143c-30.286 0-54.857 24.571-54.857 54.857v182.857c0 30.286 24.571 54.857 54.857 54.857h265.714l77.143-77.714c21.143-20.571 48.571-32 77.714-32s56.571 11.429 77.714 32l77.714 77.714h265.143c30.286 0 54.857-24.571 54.857-54.857zM765.143 636c5.714-13.714 2.857-29.714-8-40l-256-256c-6.857-7.429-16.571-10.857-25.714-10.857s-18.857 3.429-25.714 10.857l-256 256c-10.857 10.286-13.714 26.286-8 40 5.714 13.143 18.857 22.286 33.714 22.286h146.286v256c0 20 16.571 36.571 36.571 36.571h146.286c20 0 36.571-16.571 36.571-36.571v-256h146.286c14.857 0 28-9.143 33.714-22.286z" />
<glyph unicode="&#xf01a;" glyph-name="arrow-circle-o-down" horiz-adv-x="878" d="M640 420.571c0-5.143-2.286-9.714-5.714-13.714l-182.286-182.286c-4-3.429-8.571-5.143-13.143-5.143s-9.143 1.714-13.143 5.143l-182.857 182.857c-5.143 5.714-6.857 13.143-4 20s9.714 11.429 17.143 11.429h109.714v201.143c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286v-201.143h109.714c10.286 0 18.286-8 18.286-18.286zM438.857 749.714c-171.429 0-310.857-139.429-310.857-310.857s139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857-139.429 310.857-310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857v0c242.286 0 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf01b;" glyph-name="arrow-circle-o-up" horiz-adv-x="878" d="M638.857 450.286c-2.857-6.857-9.714-11.429-17.143-11.429h-109.714v-201.143c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v201.143h-109.714c-10.286 0-18.286 8-18.286 18.286 0 5.143 2.286 9.714 5.714 13.714l182.286 182.286c4 3.429 8.571 5.143 13.143 5.143s9.143-1.714 13.143-5.143l182.857-182.857c5.143-5.714 6.857-13.143 4-20zM438.857 749.714c-171.429 0-310.857-139.429-310.857-310.857s139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857-139.429 310.857-310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857v0c242.286 0 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf01c;" glyph-name="inbox" horiz-adv-x="878" d="M584.571 402.286h180.571c-1.143 2.857-1.714 6.286-2.857 9.143l-121.143 283.429h-404.571l-121.143-283.429c-1.143-2.857-1.714-6.286-2.857-9.143h180.571l54.286-109.714h182.857zM877.714 385.143v-275.429c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v275.429c0 20.571 6.286 50.857 14.286 70.286l136 315.429c8 18.857 30.857 33.714 50.857 33.714h475.429c20 0 42.857-14.857 50.857-33.714l136-315.429c8-19.429 14.286-49.714 14.286-70.286z" />
<glyph unicode="&#xf01d;" glyph-name="play-circle-o" horiz-adv-x="878" d="M676.571 438.857c0-13.143-6.857-25.143-18.286-31.429l-310.857-182.857c-5.714-3.429-12-5.143-18.286-5.143s-12.571 1.714-18.286 4.571c-11.429 6.857-18.286 18.857-18.286 32v365.714c0 13.143 6.857 25.143 18.286 32 11.429 6.286 25.714 6.286 36.571-0.571l310.857-182.857c11.429-6.286 18.286-18.286 18.286-31.429zM749.714 438.857c0 171.429-139.429 310.857-310.857 310.857s-310.857-139.429-310.857-310.857 139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf01e;" glyph-name="repeat, rotate-right" horiz-adv-x="878" d="M877.714 804.571v-256c0-20-16.571-36.571-36.571-36.571h-256c-14.857 0-28 9.143-33.714 22.857-5.714 13.143-2.857 29.143 8 39.429l78.857 78.857c-53.714 49.714-124.571 78.286-199.429 78.286-161.143 0-292.571-131.429-292.571-292.571s131.429-292.571 292.571-292.571c90.857 0 174.857 41.143 230.857 113.714 2.857 4 8 6.286 13.143 6.857 5.143 0 10.286-1.714 14.286-5.143l78.286-78.857c6.857-6.286 6.857-17.143 1.143-24.571-83.429-100.571-206.857-158.286-337.714-158.286-241.714 0-438.857 197.143-438.857 438.857s197.143 438.857 438.857 438.857c112.571 0 221.714-45.143 302.286-121.143l74.286 73.714c10.286 10.857 26.286 13.714 40 8 13.143-5.714 22.286-18.857 22.286-33.714z" />
<glyph unicode="&#xf021;" glyph-name="refresh" horiz-adv-x="878" d="M863.429 347.428c0-1.143 0-2.857-0.571-4-48.571-202.286-215.429-343.429-426.286-343.429-111.429 0-219.429 44-300.571 121.143l-73.714-73.714c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v256c0 20 16.571 36.571 36.571 36.571h256c20 0 36.571-16.571 36.571-36.571 0-9.714-4-18.857-10.857-25.714l-78.286-78.286c53.714-50.286 125.143-78.857 198.857-78.857 101.714 0 196 52.571 249.143 139.429 13.714 22.286 20.571 44 30.286 66.857 2.857 8 8.571 13.143 17.143 13.143h109.714c10.286 0 18.286-8.571 18.286-18.286zM877.714 804.571v-256c0-20-16.571-36.571-36.571-36.571h-256c-20 0-36.571 16.571-36.571 36.571 0 9.714 4 18.857 10.857 25.714l78.857 78.857c-54.286 50.286-125.714 78.286-199.429 78.286-101.714 0-196-52.571-249.143-139.429-13.714-22.286-20.571-44-30.286-66.857-2.857-8-8.571-13.143-17.143-13.143h-113.714c-10.286 0-18.286 8.571-18.286 18.286v4c49.143 202.857 217.714 343.429 428.571 343.429 112 0 221.143-44.571 302.286-121.143l74.286 73.714c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf022;" glyph-name="list-alt" d="M219.429 274.286v-36.571c0-9.714-8.571-18.286-18.286-18.286h-36.571c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h36.571c9.714 0 18.286-8.571 18.286-18.286zM219.429 420.571v-36.571c0-9.714-8.571-18.286-18.286-18.286h-36.571c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h36.571c9.714 0 18.286-8.571 18.286-18.286zM219.429 566.857v-36.571c0-9.714-8.571-18.286-18.286-18.286h-36.571c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h36.571c9.714 0 18.286-8.571 18.286-18.286zM877.714 274.286v-36.571c0-9.714-8.571-18.286-18.286-18.286h-548.571c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h548.571c9.714 0 18.286-8.571 18.286-18.286zM877.714 420.571v-36.571c0-9.714-8.571-18.286-18.286-18.286h-548.571c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h548.571c9.714 0 18.286-8.571 18.286-18.286zM877.714 566.857v-36.571c0-9.714-8.571-18.286-18.286-18.286h-548.571c-9.714 0-18.286 8.571-18.286 18.286v36.571c0 9.714 8.571 18.286 18.286 18.286h548.571c9.714 0 18.286-8.571 18.286-18.286zM950.857 164.571v475.429c0 9.714-8.571 18.286-18.286 18.286h-841.143c-9.714 0-18.286-8.571-18.286-18.286v-475.429c0-9.714 8.571-18.286 18.286-18.286h841.143c9.714 0 18.286 8.571 18.286 18.286zM1024 786.286v-621.714c0-50.286-41.143-91.429-91.429-91.429h-841.143c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h841.143c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf023;" glyph-name="lock" horiz-adv-x="658" d="M182.857 512h292.571v109.714c0 80.571-65.714 146.286-146.286 146.286s-146.286-65.714-146.286-146.286v-109.714zM658.286 457.143v-329.143c0-30.286-24.571-54.857-54.857-54.857h-548.571c-30.286 0-54.857 24.571-54.857 54.857v329.143c0 30.286 24.571 54.857 54.857 54.857h18.286v109.714c0 140.571 115.429 256 256 256s256-115.429 256-256v-109.714h18.286c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf026;" glyph-name="volume-off" horiz-adv-x="439" d="M438.857 749.714v-621.714c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-190.286 190.286h-149.714c-20 0-36.571 16.571-36.571 36.571v219.429c0 20 16.571 36.571 36.571 36.571h149.714l190.286 190.286c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf027;" glyph-name="volume-down" horiz-adv-x="658" d="M438.857 749.714v-621.714c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-190.286 190.286h-149.714c-20 0-36.571 16.571-36.571 36.571v219.429c0 20 16.571 36.571 36.571 36.571h149.714l190.286 190.286c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571zM658.286 438.857c0-57.143-34.857-112.571-88.571-134.286-4.571-2.286-9.714-2.857-14.286-2.857-20 0-36.571 16-36.571 36.571 0 43.429 66.286 31.429 66.286 100.571s-66.286 57.143-66.286 100.571c0 20.571 16.571 36.571 36.571 36.571 4.571 0 9.714-0.571 14.286-2.857 53.714-21.143 88.571-77.143 88.571-134.286z" />
<glyph unicode="&#xf028;" glyph-name="volume-up" horiz-adv-x="951" d="M438.857 749.714v-621.714c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-190.286 190.286h-149.714c-20 0-36.571 16.571-36.571 36.571v219.429c0 20 16.571 36.571 36.571 36.571h149.714l190.286 190.286c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571zM658.286 438.857c0-57.143-34.857-112.571-88.571-134.286-4.571-2.286-9.714-2.857-14.286-2.857-20 0-36.571 16-36.571 36.571 0 43.429 66.286 31.429 66.286 100.571s-66.286 57.143-66.286 100.571c0 20.571 16.571 36.571 36.571 36.571 4.571 0 9.714-0.571 14.286-2.857 53.714-21.143 88.571-77.143 88.571-134.286zM804.571 438.857c0-116-69.714-224-177.143-269.143-4.571-1.714-9.714-2.857-14.286-2.857-20.571 0-37.143 16.571-37.143 36.571 0 16 9.143 26.857 22.286 33.714 15.429 8 29.714 14.857 43.429 25.143 56.571 41.143 89.714 106.857 89.714 176.571s-33.143 135.429-89.714 176.571c-13.714 10.286-28 17.143-43.429 25.143-13.143 6.857-22.286 17.714-22.286 33.714 0 20 16.571 36.571 36.571 36.571 5.143 0 10.286-1.143 14.857-2.857 107.429-45.143 177.143-153.143 177.143-269.143zM950.857 438.857c0-175.429-104.571-334.286-265.714-403.429-4.571-1.714-9.714-2.857-14.857-2.857-20 0-36.571 16.571-36.571 36.571 0 16.571 8.571 25.714 22.286 33.714 8 4.571 17.143 7.429 25.714 12 16 8.571 32 18.286 46.857 29.143 93.714 69.143 149.143 178.286 149.143 294.857s-55.429 225.714-149.143 294.857c-14.857 10.857-30.857 20.571-46.857 29.143-8.571 4.571-17.714 7.429-25.714 12-13.714 8-22.286 17.143-22.286 33.714 0 20 16.571 36.571 36.571 36.571 5.143 0 10.286-1.143 14.857-2.857 161.143-69.143 265.714-228 265.714-403.429z" />
<glyph unicode="&#xf029;" glyph-name="qrcode" horiz-adv-x="805" d="M219.429 292.571v-73.143h-73.143v73.143h73.143zM219.429 731.428v-73.143h-73.143v73.143h73.143zM658.286 731.428v-73.143h-73.143v73.143h73.143zM73.143 146.857h219.429v218.857h-219.429v-218.857zM73.143 585.143h219.429v219.429h-219.429v-219.429zM512 585.143h219.429v219.429h-219.429v-219.429zM365.714 438.857v-365.714h-365.714v365.714h365.714zM658.286 146.286v-73.143h-73.143v73.143h73.143zM804.571 146.286v-73.143h-73.143v73.143h73.143zM804.571 438.857v-219.429h-219.429v73.143h-73.143v-219.429h-73.143v365.714h219.429v-73.143h73.143v73.143h73.143zM365.714 877.714v-365.714h-365.714v365.714h365.714zM804.571 877.714v-365.714h-365.714v365.714h365.714z" />
<glyph unicode="&#xf02b;" glyph-name="tag" horiz-adv-x="866" d="M256 694.857c0 40.571-32.571 73.143-73.143 73.143s-73.143-32.571-73.143-73.143 32.571-73.143 73.143-73.143 73.143 32.571 73.143 73.143zM865.714 365.714c0-19.429-8-38.286-21.143-51.429l-280.571-281.143c-13.714-13.143-32.571-21.143-52-21.143s-38.286 8-51.429 21.143l-408.571 409.143c-29.143 28.571-52 84-52 124.571v237.714c0 40 33.143 73.143 73.143 73.143h237.714c40.571 0 96-22.857 125.143-52l408.571-408c13.143-13.714 21.143-32.571 21.143-52z" />
<glyph unicode="&#xf02c;" glyph-name="tags" horiz-adv-x="1085" d="M256 694.857c0 40.571-32.571 73.143-73.143 73.143s-73.143-32.571-73.143-73.143 32.571-73.143 73.143-73.143 73.143 32.571 73.143 73.143zM865.714 365.714c0-19.429-8-38.286-21.143-51.429l-280.571-281.143c-13.714-13.143-32.571-21.143-52-21.143s-38.286 8-51.429 21.143l-408.571 409.143c-29.143 28.571-52 84-52 124.571v237.714c0 40 33.143 73.143 73.143 73.143h237.714c40.571 0 96-22.857 125.143-52l408.571-408c13.143-13.714 21.143-32.571 21.143-52zM1085.143 365.714c0-19.429-8-38.286-21.143-51.429l-280.571-281.143c-13.714-13.143-32.571-21.143-52-21.143-29.714 0-44.571 13.714-64 33.714l268.571 268.571c13.143 13.143 21.143 32 21.143 51.429s-8 38.286-21.143 52l-408.571 408c-29.143 29.143-84.571 52-125.143 52h128c40.571 0 96-22.857 125.143-52l408.571-408c13.143-13.714 21.143-32.571 21.143-52z" />
<glyph unicode="&#xf02d;" glyph-name="book" horiz-adv-x="953" d="M936.571 677.714c14.286-20.571 18.286-47.429 10.286-73.714l-157.143-517.714c-14.286-48.571-64.571-86.286-113.714-86.286h-527.429c-58.286 0-120.571 46.286-141.714 105.714-9.143 25.714-9.143 50.857-1.143 72.571 1.143 11.429 3.429 22.857 4 36.571 0.571 9.143-4.571 16.571-3.429 23.429 2.286 13.714 14.286 23.429 23.429 38.857 17.143 28.571 36.571 74.857 42.857 104.571 2.857 10.857-2.857 23.429 0 33.143 2.857 10.857 13.714 18.857 19.429 29.143 15.429 26.286 35.429 77.143 38.286 104 1.143 12-4.571 25.143-1.143 34.286 4 13.143 16.571 18.857 25.143 30.286 13.714 18.857 36.571 73.143 40 103.429 1.143 9.714-4.571 19.429-2.857 29.714 2.286 10.857 16 22.286 25.143 35.429 24 35.429 28.571 113.714 101.143 93.143l-0.571-1.714c9.714 2.286 19.429 5.143 29.143 5.143h434.857c26.857 0 50.857-12 65.143-32 14.857-20.571 18.286-47.429 10.286-74.286l-156.571-517.714c-26.857-88-41.714-107.429-114.286-107.429h-496.571c-7.429 0-16.571-1.714-21.714-8.571-4.571-6.857-5.143-12-0.571-24.571 11.429-33.143 50.857-40 82.286-40h527.429c21.143 0 45.714 12 52 32.571l171.429 564c3.429 10.857 3.429 22.286 2.857 32.571 13.143-5.143 25.143-13.143 33.714-24.571zM328.571 676.571c-3.429-10.286 2.286-18.286 12.571-18.286h347.429c9.714 0 20.571 8 24 18.286l12 36.571c3.429 10.286-2.286 18.286-12.571 18.286h-347.429c-9.714 0-20.571-8-24-18.286zM281.143 530.286c-3.429-10.286 2.286-18.286 12.571-18.286h347.429c9.714 0 20.571 8 24 18.286l12 36.571c3.429 10.286-2.286 18.286-12.571 18.286h-347.429c-9.714 0-20.571-8-24-18.286z" />
<glyph unicode="&#xf02f;" glyph-name="print" horiz-adv-x="951" d="M219.429 73.143h512v146.286h-512v-146.286zM219.429 438.857h512v219.429h-91.429c-30.286 0-54.857 24.571-54.857 54.857v91.429h-365.714v-365.714zM877.714 402.286c0 20-16.571 36.571-36.571 36.571s-36.571-16.571-36.571-36.571 16.571-36.571 36.571-36.571 36.571 16.571 36.571 36.571zM950.857 402.286v-237.714c0-9.714-8.571-18.286-18.286-18.286h-128v-91.429c0-30.286-24.571-54.857-54.857-54.857h-548.571c-30.286 0-54.857 24.571-54.857 54.857v91.429h-128c-9.714 0-18.286 8.571-18.286 18.286v237.714c0 60 49.714 109.714 109.714 109.714h36.571v310.857c0 30.286 24.571 54.857 54.857 54.857h384c30.286 0 72-17.143 93.714-38.857l86.857-86.857c21.714-21.714 38.857-63.429 38.857-93.714v-146.286h36.571c60 0 109.714-49.714 109.714-109.714z" />
<glyph unicode="&#xf034;" glyph-name="text-height" horiz-adv-x="1030" d="M996.571 146.286c25.143 0 33.143-16 17.714-36l-72-92.571c-15.429-20-40.571-20-56 0l-72 92.571c-15.429 20-7.429 36 17.714 36h45.714v585.143h-45.714c-25.143 0-33.143 16-17.714 36l72 92.571c15.429 20 40.571 20 56 0l72-92.571c15.429-20 7.429-36-17.714-36h-45.714v-585.143h45.714zM46.286 877.143l30.857-15.429c4-1.714 108.571-2.857 120.571-2.857 50.286 0 100.571 2.286 150.857 2.286 41.143 0 81.714-0.571 122.857-0.571h167.429c22.857 0 36-5.143 51.429 16.571l24 0.571c5.143 0 10.857-0.571 16-0.571 1.143-64 1.143-128 1.143-192 0-20 0.571-42.286-2.857-62.286-12.571-4.571-25.714-8.571-38.857-10.286-13.143 22.857-22.286 48-30.857 73.143-4 11.429-17.714 88.571-18.857 89.714-12 14.857-25.143 12-42.857 12-52 0-106.286 2.286-157.714-4-2.857-25.143-5.143-52-4.571-77.714 0.571-160.571 2.286-321.143 2.286-481.714 0-44-6.857-90.286 5.714-132.571 43.429-22.286 94.857-25.714 139.429-45.714 1.143-9.143 2.857-18.857 2.857-28.571 0-5.143-0.571-10.857-1.714-16.571l-19.429-0.571c-81.143-2.286-161.143 10.286-242.857 10.286-57.714 0-115.429-10.286-173.143-10.286-0.571 9.714-1.714 20-1.714 29.714v5.143c21.714 34.857 100 35.429 136 56.571 12.571 28 10.857 182.857 10.857 218.857 0 115.429-3.429 230.857-3.429 346.286v66.857c0 10.286 2.286 51.429-4.571 59.429-8 8.571-82.857 6.857-92.571 6.857-21.143 0-82.286-9.714-98.857-21.714-27.429-18.857-27.429-133.143-61.714-135.429-10.286 6.286-24.571 15.429-32 25.143v218.857z" />
<glyph unicode="&#xf035;" glyph-name="text-width" horiz-adv-x="878" d="M46.286 877.143l30.857-15.429c4-1.714 108.571-2.857 120.571-2.857 50.286 0 100.571 2.286 150.857 2.286 151.429 0 304.571 3.429 456-1.714 12.571-0.571 24.571 7.429 32 17.714l24 0.571c5.143 0 10.857-0.571 16-0.571 1.143-64 1.143-128 1.143-192 0-20.571 0.571-42.286-2.857-62.286-12.571-4.571-25.714-8.571-38.857-10.286-13.143 22.857-22.286 48-30.857 73.143-4 11.429-18.286 88.571-18.857 89.714-4 5.143-9.143 8.571-15.429 10.857-4.571 1.714-32 1.143-37.714 1.143-70.286 0-151.429 4-220.571-4-2.857-25.143-5.143-52-4.571-77.714l0.571-86.857v29.714c0.571-93.143 1.714-185.714 1.714-278.286 0-44-6.857-90.286 5.714-132.571 43.429-22.286 94.857-25.714 139.429-45.714 1.143-9.143 2.857-18.857 2.857-28.571 0-5.143-0.571-10.857-1.714-16.571l-19.429-0.571c-81.143-2.286-161.143 10.286-242.857 10.286-57.714 0-115.429-10.286-173.143-10.286-0.571 9.714-1.714 20-1.714 29.714v5.143c21.714 34.857 100 35.429 136 56.571 14.286 32 10.286 302.286 10.286 352.571 0 8-2.857 16.571-2.857 25.143 0 23.429 4 157.714-4.571 167.429-8 8.571-82.857 6.857-92.571 6.857-24 0-158.286-12.571-172-21.714-26.857-17.714-27.429-132.571-61.714-135.429-10.286 6.286-24.571 15.429-32 25.143v218.857zM748.571 144.571c20 0 96-68 111.429-80 8.571-6.857 14.857-16.571 14.857-28s-6.286-21.143-14.857-28c-15.429-12-91.429-80-111.429-80-26.286 0-17.143 61.143-17.143 71.429h-585.143c0-10.286 9.143-71.429-17.143-71.429-20 0-96 68-111.429 80-8.571 6.857-14.857 16.571-14.857 28s6.286 21.143 14.857 28c15.429 12 91.429 80 111.429 80 26.286 0 17.143-61.143 17.143-71.429h585.143c0 10.286-9.143 71.429 17.143 71.429z" />
<glyph unicode="&#xf036;" glyph-name="align-left" d="M1024 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM804.571 402.286v-73.143c0-20-16.571-36.571-36.571-36.571h-731.429c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h731.429c20 0 36.571-16.571 36.571-36.571zM950.857 621.714v-73.143c0-20-16.571-36.571-36.571-36.571h-877.714c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h877.714c20 0 36.571-16.571 36.571-36.571zM731.429 841.143v-73.143c0-20-16.571-36.571-36.571-36.571h-658.286c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h658.286c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf037;" glyph-name="align-center" d="M1024 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM804.571 402.286v-73.143c0-20-16.571-36.571-36.571-36.571h-512c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h512c20 0 36.571-16.571 36.571-36.571zM950.857 621.714v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM731.429 841.143v-73.143c0-20-16.571-36.571-36.571-36.571h-365.714c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h365.714c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf038;" glyph-name="align-right" d="M1024 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM1024 402.286v-73.143c0-20-16.571-36.571-36.571-36.571h-731.429c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h731.429c20 0 36.571-16.571 36.571-36.571zM1024 621.714v-73.143c0-20-16.571-36.571-36.571-36.571h-877.714c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h877.714c20 0 36.571-16.571 36.571-36.571zM1024 841.143v-73.143c0-20-16.571-36.571-36.571-36.571h-658.286c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h658.286c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf039;" glyph-name="align-justify" d="M1024 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM1024 402.286v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM1024 621.714v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM1024 841.143v-73.143c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf03a;" glyph-name="list" d="M146.286 201.143v-109.714c0-9.714-8.571-18.286-18.286-18.286h-109.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h109.714c9.714 0 18.286-8.571 18.286-18.286zM146.286 420.571v-109.714c0-9.714-8.571-18.286-18.286-18.286h-109.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h109.714c9.714 0 18.286-8.571 18.286-18.286zM146.286 640v-109.714c0-9.714-8.571-18.286-18.286-18.286h-109.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h109.714c9.714 0 18.286-8.571 18.286-18.286zM1024 201.143v-109.714c0-9.714-8.571-18.286-18.286-18.286h-768c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h768c9.714 0 18.286-8.571 18.286-18.286zM146.286 859.428v-109.714c0-9.714-8.571-18.286-18.286-18.286h-109.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h109.714c9.714 0 18.286-8.571 18.286-18.286zM1024 420.571v-109.714c0-9.714-8.571-18.286-18.286-18.286h-768c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h768c9.714 0 18.286-8.571 18.286-18.286zM1024 640v-109.714c0-9.714-8.571-18.286-18.286-18.286h-768c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h768c9.714 0 18.286-8.571 18.286-18.286zM1024 859.428v-109.714c0-9.714-8.571-18.286-18.286-18.286h-768c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h768c9.714 0 18.286-8.571 18.286-18.286z" />
<glyph unicode="&#xf03b;" glyph-name="dedent, outdent" d="M219.429 640v-329.143c0-9.714-8.571-18.286-18.286-18.286-4.571 0-9.714 1.714-13.143 5.143l-164.571 164.571c-3.429 3.429-5.143 8.571-5.143 13.143s1.714 9.714 5.143 13.143l164.571 164.571c3.429 3.429 8.571 5.143 13.143 5.143 9.714 0 18.286-8.571 18.286-18.286zM1024 201.143v-109.714c0-9.714-8.571-18.286-18.286-18.286h-987.429c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h987.429c9.714 0 18.286-8.571 18.286-18.286zM1024 420.571v-109.714c0-9.714-8.571-18.286-18.286-18.286h-621.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h621.714c9.714 0 18.286-8.571 18.286-18.286zM1024 640v-109.714c0-9.714-8.571-18.286-18.286-18.286h-621.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h621.714c9.714 0 18.286-8.571 18.286-18.286zM1024 859.428v-109.714c0-9.714-8.571-18.286-18.286-18.286h-987.429c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h987.429c9.714 0 18.286-8.571 18.286-18.286z" />
<glyph unicode="&#xf03c;" glyph-name="indent" d="M201.143 475.428c0-4.571-1.714-9.714-5.143-13.143l-164.571-164.571c-3.429-3.429-8.571-5.143-13.143-5.143-9.714 0-18.286 8.571-18.286 18.286v329.143c0 9.714 8.571 18.286 18.286 18.286 4.571 0 9.714-1.714 13.143-5.143l164.571-164.571c3.429-3.429 5.143-8.571 5.143-13.143zM1024 201.143v-109.714c0-9.714-8.571-18.286-18.286-18.286h-987.429c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h987.429c9.714 0 18.286-8.571 18.286-18.286zM1024 420.571v-109.714c0-9.714-8.571-18.286-18.286-18.286h-621.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h621.714c9.714 0 18.286-8.571 18.286-18.286zM1024 640v-109.714c0-9.714-8.571-18.286-18.286-18.286h-621.714c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h621.714c9.714 0 18.286-8.571 18.286-18.286zM1024 859.428v-109.714c0-9.714-8.571-18.286-18.286-18.286h-987.429c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h987.429c9.714 0 18.286-8.571 18.286-18.286z" />
<glyph unicode="&#xf040;" glyph-name="pencil" horiz-adv-x="866" d="M207.429 73.143l52 52-134.286 134.286-52-52v-61.143h73.143v-73.143h61.143zM506.286 603.428c0 7.429-5.143 12.571-12.571 12.571-3.429 0-6.857-1.143-9.714-4l-309.714-309.714c-2.857-2.857-4-6.286-4-9.714 0-7.429 5.143-12.571 12.571-12.571 3.429 0 6.857 1.143 9.714 4l309.714 309.714c2.857 2.857 4 6.286 4 9.714zM475.429 713.143l237.714-237.714-475.429-475.429h-237.714v237.714zM865.714 658.286c0-19.429-8-38.286-21.143-51.429l-94.857-94.857-237.714 237.714 94.857 94.286c13.143 13.714 32 21.714 51.429 21.714s38.286-8 52-21.714l134.286-133.714c13.143-13.714 21.143-32.571 21.143-52z" />
<glyph unicode="&#xf042;" glyph-name="adjust" horiz-adv-x="878" d="M438.857 128v621.714c-171.429 0-310.857-139.429-310.857-310.857s139.429-310.857 310.857-310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf044;" glyph-name="edit, pencil-square-o" horiz-adv-x="1025" d="M507.429 274.286l66.286 66.286-86.857 86.857-66.286-66.286v-32h54.857v-54.857h32zM758.857 685.714c-5.143 5.143-13.714 4.571-18.857-0.571l-200-200c-5.143-5.143-5.714-13.714-0.571-18.857s13.714-4.571 18.857 0.571l200 200c5.143 5.143 5.714 13.714 0.571 18.857zM804.571 346.286v-108.571c0-90.857-73.714-164.571-164.571-164.571h-475.429c-90.857 0-164.571 73.714-164.571 164.571v475.429c0 90.857 73.714 164.571 164.571 164.571h475.429c22.857 0 45.714-4.571 66.857-14.286 5.143-2.286 9.143-7.429 10.286-13.143 1.143-6.286-0.571-12-5.143-16.571l-28-28c-5.143-5.143-12-6.857-18.286-4.571-8.571 2.286-17.143 3.429-25.714 3.429h-475.429c-50.286 0-91.429-41.143-91.429-91.429v-475.429c0-50.286 41.143-91.429 91.429-91.429h475.429c50.286 0 91.429 41.143 91.429 91.429v72c0 4.571 1.714 9.143 5.143 12.571l36.571 36.571c5.714 5.714 13.143 6.857 20 4s11.429-9.143 11.429-16.571zM749.714 768l164.571-164.571-384-384h-164.571v164.571zM1003.429 692.571l-52.571-52.571-164.571 164.571 52.571 52.571c21.143 21.143 56.571 21.143 77.714 0l86.857-86.857c21.143-21.143 21.143-56.571 0-77.714z" />
<glyph unicode="&#xf045;" glyph-name="share-square-o" horiz-adv-x="954" d="M804.571 385.714v-148c0-90.857-73.714-164.571-164.571-164.571h-475.429c-90.857 0-164.571 73.714-164.571 164.571v475.429c0 90.857 73.714 164.571 164.571 164.571h145.714c9.714 0 18.286-8 18.286-18.286 0-9.143-6.286-16.571-14.857-18.286-28.571-9.714-54.286-21.143-76-34.286-2.857-1.143-5.714-2.286-9.143-2.286h-64c-50.286 0-91.429-41.143-91.429-91.429v-475.429c0-50.286 41.143-91.429 91.429-91.429h475.429c50.286 0 91.429 41.143 91.429 91.429v122.286c0 6.857 4 13.143 10.286 16.571 11.429 5.143 21.714 12.571 30.857 21.143 5.143 5.143 13.143 7.429 20 4.571s12-9.143 12-16.571zM940 669.143l-219.429-219.429c-6.857-7.429-16-10.857-25.714-10.857-4.571 0-9.714 1.143-14.286 2.857-13.143 5.714-22.286 18.857-22.286 33.714v109.714h-91.429c-125.714 0-205.714-24-250.286-74.857-46.286-53.143-60-138.857-42.286-270.286 1.143-8-4-16-11.429-19.429-2.286-0.571-4.571-1.143-6.857-1.143-5.714 0-11.429 2.857-14.857 7.429-4 5.714-94.857 134.286-94.857 248.571 0 153.143 48 329.143 420.571 329.143h91.429v109.714c0 14.857 9.143 28 22.286 33.714 4.571 1.714 9.714 2.857 14.286 2.857 9.714 0 18.857-4 25.714-10.857l219.429-219.429c14.286-14.286 14.286-37.143 0-51.429z" />
<glyph unicode="&#xf046;" glyph-name="check-square-o" horiz-adv-x="955" d="M804.571 419.428v-181.714c0-90.857-73.714-164.571-164.571-164.571h-475.429c-90.857 0-164.571 73.714-164.571 164.571v475.429c0 90.857 73.714 164.571 164.571 164.571h475.429c22.857 0 45.714-4.571 66.857-14.286 5.143-2.286 9.143-7.429 10.286-13.143 1.143-6.286-0.571-12-5.143-16.571l-28-28c-3.429-3.429-8.571-5.714-13.143-5.714-1.714 0-3.429 0.571-5.143 1.143-8.571 2.286-17.143 3.429-25.714 3.429h-475.429c-50.286 0-91.429-41.143-91.429-91.429v-475.429c0-50.286 41.143-91.429 91.429-91.429h475.429c50.286 0 91.429 41.143 91.429 91.429v145.143c0 4.571 1.714 9.143 5.143 12.571l36.571 36.571c4 4 8.571 5.714 13.143 5.714 2.286 0 4.571-0.571 6.857-1.714 6.857-2.857 11.429-9.143 11.429-16.571zM936.571 698.857l-465.143-465.143c-18.286-18.286-46.857-18.286-65.143 0l-245.714 245.714c-18.286 18.286-18.286 46.857 0 65.143l62.857 62.857c18.286 18.286 46.857 18.286 65.143 0l150.286-150.286 369.714 369.714c18.286 18.286 46.857 18.286 65.143 0l62.857-62.857c18.286-18.286 18.286-46.857 0-65.143z" />
<glyph unicode="&#xf047;" glyph-name="arrows" d="M1024 438.857c0-9.714-4-18.857-10.857-25.714l-146.286-146.286c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v73.143h-219.429v-219.429h73.143c20 0 36.571-16.571 36.571-36.571 0-9.714-4-18.857-10.857-25.714l-146.286-146.286c-6.857-6.857-16-10.857-25.714-10.857s-18.857 4-25.714 10.857l-146.286 146.286c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h73.143v219.429h-219.429v-73.143c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-146.286 146.286c-6.857 6.857-10.857 16-10.857 25.714s4 18.857 10.857 25.714l146.286 146.286c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571v-73.143h219.429v219.429h-73.143c-20 0-36.571 16.571-36.571 36.571 0 9.714 4 18.857 10.857 25.714l146.286 146.286c6.857 6.857 16 10.857 25.714 10.857s18.857-4 25.714-10.857l146.286-146.286c6.857-6.857 10.857-16 10.857-25.714 0-20-16.571-36.571-36.571-36.571h-73.143v-219.429h219.429v73.143c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l146.286-146.286c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf048;" glyph-name="step-backward" horiz-adv-x="585" d="M559.429 870.286c14.286 14.286 25.714 9.143 25.714-10.857v-841.143c0-20-11.429-25.143-25.714-10.857l-405.714 405.714c-3.429 3.429-5.714 6.857-7.429 10.857v-387.429c0-20-16.571-36.571-36.571-36.571h-73.143c-20 0-36.571 16.571-36.571 36.571v804.571c0 20 16.571 36.571 36.571 36.571h73.143c20 0 36.571-16.571 36.571-36.571v-387.429c1.714 4 4 7.429 7.429 10.857z" />
<glyph unicode="&#xf049;" glyph-name="fast-backward" d="M998.286 870.286c14.286 14.286 25.714 9.143 25.714-10.857v-841.143c0-20-11.429-25.143-25.714-10.857l-405.714 405.714c-3.429 3.429-5.714 6.857-7.429 10.857v-405.714c0-20-11.429-25.143-25.714-10.857l-405.714 405.714c-3.429 3.429-5.714 6.857-7.429 10.857v-387.429c0-20-16.571-36.571-36.571-36.571h-73.143c-20 0-36.571 16.571-36.571 36.571v804.571c0 20 16.571 36.571 36.571 36.571h73.143c20 0 36.571-16.571 36.571-36.571v-387.429c1.714 4 4 7.429 7.429 10.857l405.714 405.714c14.286 14.286 25.714 9.143 25.714-10.857v-405.714c1.714 4 4 7.429 7.429 10.857z" />
<glyph unicode="&#xf04a;" glyph-name="backward" horiz-adv-x="1017" d="M925.143 870.286c14.286 14.286 25.714 9.143 25.714-10.857v-841.143c0-20-11.429-25.143-25.714-10.857l-405.714 405.714c-3.429 3.429-5.714 6.857-7.429 10.857v-405.714c0-20-11.429-25.143-25.714-10.857l-405.714 405.714c-14.286 14.286-14.286 37.143 0 51.429l405.714 405.714c14.286 14.286 25.714 9.143 25.714-10.857v-405.714c1.714 4 4 7.429 7.429 10.857z" />
<glyph unicode="&#xf04b;" glyph-name="play" horiz-adv-x="809" d="M790.857 421.143l-758.857-421.714c-17.714-9.714-32-1.143-32 18.857v841.143c0 20 14.286 28.571 32 18.857l758.857-421.714c17.714-9.714 17.714-25.714 0-35.429z" />
<glyph unicode="&#xf04c;" glyph-name="pause" horiz-adv-x="878" d="M877.714 841.143v-804.571c0-20-16.571-36.571-36.571-36.571h-292.571c-20 0-36.571 16.571-36.571 36.571v804.571c0 20 16.571 36.571 36.571 36.571h292.571c20 0 36.571-16.571 36.571-36.571zM365.714 841.143v-804.571c0-20-16.571-36.571-36.571-36.571h-292.571c-20 0-36.571 16.571-36.571 36.571v804.571c0 20 16.571 36.571 36.571 36.571h292.571c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf04d;" glyph-name="stop" horiz-adv-x="878" d="M877.714 841.143v-804.571c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v804.571c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf04e;" glyph-name="forward" horiz-adv-x="885" d="M25.714 7.428c-14.286-14.286-25.714-9.143-25.714 10.857v841.143c0 20 11.429 25.143 25.714 10.857l405.714-405.714c3.429-3.429 5.714-6.857 7.429-10.857v405.714c0 20 11.429 25.143 25.714 10.857l405.714-405.714c14.286-14.286 14.286-37.143 0-51.429l-405.714-405.714c-14.286-14.286-25.714-9.143-25.714 10.857v405.714c-1.714-4-4-7.429-7.429-10.857z" />
<glyph unicode="&#xf050;" glyph-name="fast-forward" d="M25.714 7.428c-14.286-14.286-25.714-9.143-25.714 10.857v841.143c0 20 11.429 25.143 25.714 10.857l405.714-405.714c3.429-3.429 5.714-6.857 7.429-10.857v405.714c0 20 11.429 25.143 25.714 10.857l405.714-405.714c3.429-3.429 5.714-6.857 7.429-10.857v387.429c0 20 16.571 36.571 36.571 36.571h73.143c20 0 36.571-16.571 36.571-36.571v-804.571c0-20-16.571-36.571-36.571-36.571h-73.143c-20 0-36.571 16.571-36.571 36.571v387.429c-1.714-4-4-7.429-7.429-10.857l-405.714-405.714c-14.286-14.286-25.714-9.143-25.714 10.857v405.714c-1.714-4-4-7.429-7.429-10.857z" />
<glyph unicode="&#xf051;" glyph-name="step-forward" horiz-adv-x="585" d="M25.714 7.428c-14.286-14.286-25.714-9.143-25.714 10.857v841.143c0 20 11.429 25.143 25.714 10.857l405.714-405.714c3.429-3.429 5.714-6.857 7.429-10.857v387.429c0 20 16.571 36.571 36.571 36.571h73.143c20 0 36.571-16.571 36.571-36.571v-804.571c0-20-16.571-36.571-36.571-36.571h-73.143c-20 0-36.571 16.571-36.571 36.571v387.429c-1.714-4-4-7.429-7.429-10.857z" />
<glyph unicode="&#xf052;" glyph-name="eject" horiz-adv-x="879" d="M8 391.428l405.714 405.714c14.286 14.286 37.143 14.286 51.429 0l405.714-405.714c14.286-14.286 9.143-25.714-10.857-25.714h-841.143c-20 0-25.143 11.429-10.857 25.714zM841.714 73.143h-804.571c-20 0-36.571 16.571-36.571 36.571v146.286c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571v-146.286c0-20-16.571-36.571-36.571-36.571z" />
<glyph unicode="&#xf053;" glyph-name="chevron-left" horiz-adv-x="768" d="M669.143 778.857l-303.429-303.429 303.429-303.429c14.286-14.286 14.286-37.143 0-51.429l-94.857-94.857c-14.286-14.286-37.143-14.286-51.429 0l-424 424c-14.286 14.286-14.286 37.143 0 51.429l424 424c14.286 14.286 37.143 14.286 51.429 0l94.857-94.857c14.286-14.286 14.286-37.143 0-51.429z" />
<glyph unicode="&#xf054;" glyph-name="chevron-right" horiz-adv-x="695" d="M632.571 449.714l-424-424c-14.286-14.286-37.143-14.286-51.429 0l-94.857 94.857c-14.286 14.286-14.286 37.143 0 51.429l303.429 303.429-303.429 303.429c-14.286 14.286-14.286 37.143 0 51.429l94.857 94.857c14.286 14.286 37.143 14.286 51.429 0l424-424c14.286-14.286 14.286-37.143 0-51.429z" />
<glyph unicode="&#xf055;" glyph-name="plus-circle" horiz-adv-x="878" d="M694.857 402.286v73.143c0 20-16.571 36.571-36.571 36.571h-146.286v146.286c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-146.286h-146.286c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h146.286v-146.286c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571v146.286h146.286c20 0 36.571 16.571 36.571 36.571zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf056;" glyph-name="minus-circle" horiz-adv-x="878" d="M694.857 402.286v73.143c0 20-16.571 36.571-36.571 36.571h-438.857c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h438.857c20 0 36.571 16.571 36.571 36.571zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf057;" glyph-name="times-circle" horiz-adv-x="878" d="M656.571 309.714c0 9.714-4 18.857-10.857 25.714l-103.429 103.429 103.429 103.429c6.857 6.857 10.857 16 10.857 25.714s-4 19.429-10.857 26.286l-51.429 51.429c-6.857 6.857-16.571 10.857-26.286 10.857s-18.857-4-25.714-10.857l-103.429-103.429-103.429 103.429c-6.857 6.857-16 10.857-25.714 10.857s-19.429-4-26.286-10.857l-51.429-51.429c-6.857-6.857-10.857-16.571-10.857-26.286s4-18.857 10.857-25.714l103.429-103.429-103.429-103.429c-6.857-6.857-10.857-16-10.857-25.714s4-19.429 10.857-26.286l51.429-51.429c6.857-6.857 16.571-10.857 26.286-10.857s18.857 4 25.714 10.857l103.429 103.429 103.429-103.429c6.857-6.857 16-10.857 25.714-10.857s19.429 4 26.286 10.857l51.429 51.429c6.857 6.857 10.857 16.571 10.857 26.286zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf058;" glyph-name="check-circle" horiz-adv-x="878" d="M733.714 531.428c0 9.714-3.429 19.429-10.286 26.286l-52 51.429c-6.857 6.857-16 10.857-25.714 10.857s-18.857-4-25.714-10.857l-233.143-232.571-129.143 129.143c-6.857 6.857-16 10.857-25.714 10.857s-18.857-4-25.714-10.857l-52-51.429c-6.857-6.857-10.286-16.571-10.286-26.286s3.429-18.857 10.286-25.714l206.857-206.857c6.857-6.857 16.571-10.857 25.714-10.857 9.714 0 19.429 4 26.286 10.857l310.286 310.286c6.857 6.857 10.286 16 10.286 25.714zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf059;" glyph-name="question-circle" horiz-adv-x="878" d="M512 164.571v109.714c0 10.286-8 18.286-18.286 18.286h-109.714c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h109.714c10.286 0 18.286 8 18.286 18.286zM658.286 548.571c0 104.571-109.714 182.857-208 182.857-93.143 0-162.857-40-212-121.714-5.143-8-2.857-18.286 4.571-24l75.429-57.143c2.857-2.286 6.857-3.429 10.857-3.429 5.143 0 10.857 2.286 14.286 6.857 26.857 34.286 38.286 44.571 49.143 52.571 9.714 6.857 28.571 13.714 49.143 13.714 36.571 0 70.286-23.429 70.286-48.571 0-29.714-15.429-44.571-50.286-60.571-40.571-18.286-96-65.714-96-121.143v-20.571c0-10.286 8-18.286 18.286-18.286h109.714c10.286 0 18.286 8 18.286 18.286v0c0 13.143 16.571 41.143 43.429 56.571 43.429 24.571 102.857 57.714 102.857 144.571zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf05a;" glyph-name="info-circle" horiz-adv-x="878" d="M585.143 164.571v91.429c0 10.286-8 18.286-18.286 18.286h-54.857v292.571c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-91.429c0-10.286 8-18.286 18.286-18.286h54.857v-182.857h-54.857c-10.286 0-18.286-8-18.286-18.286v-91.429c0-10.286 8-18.286 18.286-18.286h256c10.286 0 18.286 8 18.286 18.286zM512 676.571v91.429c0 10.286-8 18.286-18.286 18.286h-109.714c-10.286 0-18.286-8-18.286-18.286v-91.429c0-10.286 8-18.286 18.286-18.286h109.714c10.286 0 18.286 8 18.286 18.286zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf05b;" glyph-name="crosshairs" horiz-adv-x="878" d="M684 365.714h-62.286c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h62.286c-24.571 82.286-89.714 147.429-172 172v-62.286c0-20-16.571-36.571-36.571-36.571h-73.143c-20 0-36.571 16.571-36.571 36.571v62.286c-82.286-24.571-147.429-89.714-172-172h62.286c20 0 36.571-16.571 36.571-36.571v-73.143c0-20-16.571-36.571-36.571-36.571h-62.286c24.571-82.286 89.714-147.429 172-172v62.286c0 20 16.571 36.571 36.571 36.571h73.143c20 0 36.571-16.571 36.571-36.571v-62.286c82.286 24.571 147.429 89.714 172 172zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-81.714c-28-122.857-124.571-219.429-247.429-247.429v-81.714c0-20-16.571-36.571-36.571-36.571h-73.143c-20 0-36.571 16.571-36.571 36.571v81.714c-122.857 28-219.429 124.571-247.429 247.429h-81.714c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h81.714c28 122.857 124.571 219.429 247.429 247.429v81.714c0 20 16.571 36.571 36.571 36.571h73.143c20 0 36.571-16.571 36.571-36.571v-81.714c122.857-28 219.429-124.571 247.429-247.429h81.714c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf05c;" glyph-name="times-circle-o" horiz-adv-x="878" d="M626.857 334.286l-83.429-83.429c-7.429-7.429-18.857-7.429-26.286 0l-78.286 78.286-78.286-78.286c-7.429-7.429-18.857-7.429-26.286 0l-83.429 83.429c-7.429 7.429-7.429 18.857 0 26.286l78.286 78.286-78.286 78.286c-7.429 7.429-7.429 18.857 0 26.286l83.429 83.429c7.429 7.429 18.857 7.429 26.286 0l78.286-78.286 78.286 78.286c7.429 7.429 18.857 7.429 26.286 0l83.429-83.429c7.429-7.429 7.429-18.857 0-26.286l-78.286-78.286 78.286-78.286c7.429-7.429 7.429-18.857 0-26.286zM749.714 438.857c0 171.429-139.429 310.857-310.857 310.857s-310.857-139.429-310.857-310.857 139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf05d;" glyph-name="check-circle-o" horiz-adv-x="878" d="M669.143 486.286l-241.143-241.143c-14.286-14.286-37.143-14.286-51.429 0l-168 168c-14.286 14.286-14.286 37.143 0 51.429l58.286 58.286c14.286 14.286 37.143 14.286 51.429 0l84-84 157.143 157.143c14.286 14.286 37.143 14.286 51.429 0l58.286-58.286c14.286-14.286 14.286-37.143 0-51.429zM749.714 438.857c0 171.429-139.429 310.857-310.857 310.857s-310.857-139.429-310.857-310.857 139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf05e;" glyph-name="ban" horiz-adv-x="878" d="M749.714 440.571c0 62.286-18.286 120-49.714 168.571l-430.857-430.286c49.143-32 107.429-50.857 169.714-50.857 171.429 0 310.857 140 310.857 312.571zM178.857 269.714l431.429 430.857c-49.143 33.143-108 52-171.429 52-171.429 0-310.857-140-310.857-312 0-63.429 18.857-121.714 50.857-170.857zM877.714 440.571c0-243.429-196.571-440.571-438.857-440.571s-438.857 197.143-438.857 440.571c0 242.857 196.571 440 438.857 440s438.857-197.143 438.857-440z" />
<glyph unicode="&#xf060;" glyph-name="arrow-left" horiz-adv-x="914" d="M877.714 438.857v-73.143c0-38.857-25.714-73.143-66.857-73.143h-402.286l167.429-168c13.714-13.143 21.714-32 21.714-51.429s-8-38.286-21.714-51.429l-42.857-43.429c-13.143-13.143-32-21.143-51.429-21.143s-38.286 8-52 21.143l-372 372.571c-13.143 13.143-21.143 32-21.143 51.429s8 38.286 21.143 52l372 371.429c13.714 13.714 32.571 21.714 52 21.714s37.714-8 51.429-21.714l42.857-42.286c13.714-13.714 21.714-32.571 21.714-52s-8-38.286-21.714-52l-167.429-167.429h402.286c41.143 0 66.857-34.286 66.857-73.143z" />
<glyph unicode="&#xf061;" glyph-name="arrow-right" horiz-adv-x="841" d="M841.143 402.286c0-19.429-7.429-38.286-21.143-52l-372-372c-13.714-13.143-32.571-21.143-52-21.143s-37.714 8-51.429 21.143l-42.857 42.857c-13.714 13.714-21.714 32.571-21.714 52s8 38.286 21.714 52l167.429 167.429h-402.286c-41.143 0-66.857 34.286-66.857 73.143v73.143c0 38.857 25.714 73.143 66.857 73.143h402.286l-167.429 168c-13.714 13.143-21.714 32-21.714 51.429s8 38.286 21.714 51.429l42.857 42.857c13.714 13.714 32 21.714 51.429 21.714s38.286-8 52-21.714l372-372c13.714-13.143 21.143-32 21.143-51.429z" />
<glyph unicode="&#xf062;" glyph-name="arrow-up" horiz-adv-x="951" d="M920.571 396c0-19.429-8-37.714-21.143-51.429l-42.857-42.857c-13.714-13.714-32.571-21.714-52-21.714s-38.286 8-51.429 21.714l-168 167.429v-402.286c0-41.143-34.286-66.857-73.143-66.857h-73.143c-38.857 0-73.143 25.714-73.143 66.857v402.286l-168-167.429c-13.143-13.714-32-21.714-51.429-21.714s-38.286 8-51.429 21.714l-42.857 42.857c-13.714 13.714-21.714 32-21.714 51.429s8 38.286 21.714 52l372 372c13.143 13.714 32 21.143 51.429 21.143s38.286-7.429 52-21.143l372-372c13.143-13.714 21.143-32.571 21.143-52z" />
<glyph unicode="&#xf063;" glyph-name="arrow-down" horiz-adv-x="951" d="M920.571 475.428c0-19.429-8-38.286-21.143-51.429l-372-372.571c-13.714-13.143-32.571-21.143-52-21.143s-38.286 8-51.429 21.143l-372 372.571c-13.714 13.143-21.714 32-21.714 51.429s8 38.286 21.714 52l42.286 42.857c13.714 13.143 32.571 21.143 52 21.143s38.286-8 51.429-21.143l168-168v402.286c0 40 33.143 73.143 73.143 73.143h73.143c40 0 73.143-33.143 73.143-73.143v-402.286l168 168c13.143 13.143 32 21.143 51.429 21.143s38.286-8 52-21.143l42.857-42.857c13.143-13.714 21.143-32.571 21.143-52z" />
<glyph unicode="&#xf064;" glyph-name="mail-forward, share" d="M1024 585.143c0-9.714-4-18.857-10.857-25.714l-292.571-292.571c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v146.286h-128c-246.286 0-408-47.429-408-320 0-23.429 1.143-46.857 2.857-70.286 0.571-9.143 2.857-19.429 2.857-28.571 0-10.857-6.857-20-18.286-20-8 0-12 4-16 9.714-8.571 12-14.857 30.286-21.143 43.429-32.571 73.143-72.571 177.714-72.571 257.714 0 64 6.286 129.714 30.286 190.286 79.429 197.143 312.571 230.286 500 230.286h128v146.286c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l292.571-292.571c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf065;" glyph-name="expand" horiz-adv-x="878" d="M431.429 347.428c0-4.571-2.286-9.714-5.714-13.143l-189.714-189.714 82.286-82.286c6.857-6.857 10.857-16 10.857-25.714 0-20-16.571-36.571-36.571-36.571h-256c-20 0-36.571 16.571-36.571 36.571v256c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l82.286-82.286 189.714 189.714c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l65.143-65.143c3.429-3.429 5.714-8.571 5.714-13.143zM877.714 841.143v-256c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-82.286 82.286-189.714-189.714c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-65.143 65.143c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l189.714 189.714-82.286 82.286c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h256c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf066;" glyph-name="compress" horiz-adv-x="878" d="M438.857 402.286v-256c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-82.286 82.286-189.714-189.714c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-65.143 65.143c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l189.714 189.714-82.286 82.286c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h256c20 0 36.571-16.571 36.571-36.571zM870.286 786.286c0-4.571-2.286-9.714-5.714-13.143l-189.714-189.714 82.286-82.286c6.857-6.857 10.857-16 10.857-25.714 0-20-16.571-36.571-36.571-36.571h-256c-20 0-36.571 16.571-36.571 36.571v256c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l82.286-82.286 189.714 189.714c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l65.143-65.143c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf067;" glyph-name="plus" horiz-adv-x="805" d="M804.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-237.714v-237.714c0-30.286-24.571-54.857-54.857-54.857h-109.714c-30.286 0-54.857 24.571-54.857 54.857v237.714h-237.714c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h237.714v237.714c0 30.286 24.571 54.857 54.857 54.857h109.714c30.286 0 54.857-24.571 54.857-54.857v-237.714h237.714c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf068;" glyph-name="minus" horiz-adv-x="805" d="M804.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-694.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h694.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf069;" glyph-name="asterisk" horiz-adv-x="951" d="M846.857 350.857c34.857-20 46.857-65.143 26.857-100l-36.571-62.857c-20-34.857-65.143-46.857-100-26.857l-152 87.429v-175.429c0-40-33.143-73.143-73.143-73.143h-73.143c-40 0-73.143 33.143-73.143 73.143v175.429l-152-87.429c-34.857-20-80-8-100 26.857l-36.571 62.857c-20 34.857-8 80 26.857 100l152 88-152 88c-34.857 20-46.857 65.143-26.857 100l36.571 62.857c20 34.857 65.143 46.857 100 26.857l152-87.429v175.429c0 40 33.143 73.143 73.143 73.143h73.143c40 0 73.143-33.143 73.143-73.143v-175.429l152 87.429c34.857 20 80 8 100-26.857l36.571-62.857c20-34.857 8-80-26.857-100l-152-88z" />
<glyph unicode="&#xf06a;" glyph-name="exclamation-circle" horiz-adv-x="878" d="M438.857 877.714c242.286 0 438.857-196.571 438.857-438.857s-196.571-438.857-438.857-438.857-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857zM512 165.143v108.571c0 10.286-8 18.857-17.714 18.857h-109.714c-10.286 0-18.857-8.571-18.857-18.857v-108.571c0-10.286 8.571-18.857 18.857-18.857h109.714c9.714 0 17.714 8.571 17.714 18.857zM510.857 361.714l10.286 354.857c0 4-1.714 8-5.714 10.286-3.429 2.857-8.571 4.571-13.714 4.571h-125.714c-5.143 0-10.286-1.714-13.714-4.571-4-2.286-5.714-6.286-5.714-10.286l9.714-354.857c0-8 8.571-14.286 19.429-14.286h105.714c10.286 0 18.857 6.286 19.429 14.286z" />
<glyph unicode="&#xf06e;" glyph-name="eye" d="M950.857 402.286c-54.286 84-128.571 156-217.714 201.714 22.857-38.857 34.857-83.429 34.857-128.571 0-141.143-114.857-256-256-256s-256 114.857-256 256c0 45.143 12 89.714 34.857 128.571-89.143-45.714-163.429-117.714-217.714-201.714 97.714-150.857 255.429-256 438.857-256s341.143 105.143 438.857 256zM539.429 621.714c0 14.857-12.571 27.429-27.429 27.429-95.429 0-173.714-78.286-173.714-173.714 0-14.857 12.571-27.429 27.429-27.429s27.429 12.571 27.429 27.429c0 65.143 53.714 118.857 118.857 118.857 14.857 0 27.429 12.571 27.429 27.429zM1024 402.286c0-14.286-4.571-27.429-11.429-39.429-105.143-173.143-297.714-289.714-500.571-289.714s-395.429 117.143-500.571 289.714c-6.857 12-11.429 25.143-11.429 39.429s4.571 27.429 11.429 39.429c105.143 172.571 297.714 289.714 500.571 289.714s395.429-117.143 500.571-289.714c6.857-12 11.429-25.143 11.429-39.429z" />
<glyph unicode="&#xf070;" glyph-name="eye-slash" d="M317.143 188l44.571 80.571c-66.286 48-105.714 125.143-105.714 206.857 0 45.143 12 89.714 34.857 128.571-89.143-45.714-163.429-117.714-217.714-201.714 59.429-92 143.429-169.143 244-214.286zM539.429 621.714c0 14.857-12.571 27.429-27.429 27.429-95.429 0-173.714-78.286-173.714-173.714 0-14.857 12.571-27.429 27.429-27.429s27.429 12.571 27.429 27.429c0 65.714 53.714 118.857 118.857 118.857 14.857 0 27.429 12.571 27.429 27.429zM746.857 730.857c0-1.143 0-4-0.571-5.143-120.571-215.429-240-432-360.571-647.429l-28-50.857c-3.429-5.714-9.714-9.143-16-9.143-10.286 0-64.571 33.143-76.571 40-5.714 3.429-9.143 9.143-9.143 16 0 9.143 19.429 40 25.143 49.714-110.857 50.286-204 136-269.714 238.857-7.429 11.429-11.429 25.143-11.429 39.429 0 13.714 4 28 11.429 39.429 113.143 173.714 289.714 289.714 500.571 289.714 34.286 0 69.143-3.429 102.857-9.714l30.857 55.429c3.429 5.714 9.143 9.143 16 9.143 10.286 0 64-33.143 76-40 5.714-3.429 9.143-9.143 9.143-15.429zM768 475.428c0-106.286-65.714-201.143-164.571-238.857l160 286.857c2.857-16 4.571-32 4.571-48zM1024 402.286c0-14.857-4-26.857-11.429-39.429-17.714-29.143-40-57.143-62.286-82.857-112-128.571-266.286-206.857-438.286-206.857l42.286 75.429c166.286 14.286 307.429 115.429 396.571 253.714-42.286 65.714-96.571 123.429-161.143 168l36 64c70.857-47.429 142.286-118.857 186.857-192.571 7.429-12.571 11.429-24.571 11.429-39.429z" />
<glyph unicode="&#xf071;" glyph-name="exclamation-triangle, warning" d="M585.143 165.143v108.571c0 10.286-8 18.857-18.286 18.857h-109.714c-10.286 0-18.286-8.571-18.286-18.857v-108.571c0-10.286 8-18.857 18.286-18.857h109.714c10.286 0 18.286 8.571 18.286 18.857zM584 378.857l10.286 262.286c0 3.429-1.714 8-5.714 10.857-3.429 2.857-8.571 6.286-13.714 6.286h-125.714c-5.143 0-10.286-3.429-13.714-6.286-4-2.857-5.714-8.571-5.714-12l9.714-261.143c0-7.429 8.571-13.143 19.429-13.143h105.714c10.286 0 18.857 5.714 19.429 13.143zM576 912.571l438.857-804.571c12.571-22.286 12-49.714-1.143-72s-37.143-36-62.857-36h-877.714c-25.714 0-49.714 13.714-62.857 36s-13.714 49.714-1.143 72l438.857 804.571c12.571 23.429 37.143 38.286 64 38.286s51.429-14.857 64-38.286z" />
<glyph unicode="&#xf073;" glyph-name="calendar" horiz-adv-x="951" d="M73.143 0h164.571v164.571h-164.571v-164.571zM274.286 0h182.857v164.571h-182.857v-164.571zM73.143 201.143h164.571v182.857h-164.571v-182.857zM274.286 201.143h182.857v182.857h-182.857v-182.857zM73.143 420.571h164.571v164.571h-164.571v-164.571zM493.714 0h182.857v164.571h-182.857v-164.571zM274.286 420.571h182.857v164.571h-182.857v-164.571zM713.143 0h164.571v164.571h-164.571v-164.571zM493.714 201.143h182.857v182.857h-182.857v-182.857zM292.571 694.857v164.571c0 9.714-8.571 18.286-18.286 18.286h-36.571c-9.714 0-18.286-8.571-18.286-18.286v-164.571c0-9.714 8.571-18.286 18.286-18.286h36.571c9.714 0 18.286 8.571 18.286 18.286zM713.143 201.143h164.571v182.857h-164.571v-182.857zM493.714 420.571h182.857v164.571h-182.857v-164.571zM713.143 420.571h164.571v164.571h-164.571v-164.571zM731.429 694.857v164.571c0 9.714-8.571 18.286-18.286 18.286h-36.571c-9.714 0-18.286-8.571-18.286-18.286v-164.571c0-9.714 8.571-18.286 18.286-18.286h36.571c9.714 0 18.286 8.571 18.286 18.286zM950.857 731.428v-731.429c0-40-33.143-73.143-73.143-73.143h-804.571c-40 0-73.143 33.143-73.143 73.143v731.429c0 40 33.143 73.143 73.143 73.143h73.143v54.857c0 50.286 41.143 91.429 91.429 91.429h36.571c50.286 0 91.429-41.143 91.429-91.429v-54.857h219.429v54.857c0 50.286 41.143 91.429 91.429 91.429h36.571c50.286 0 91.429-41.143 91.429-91.429v-54.857h73.143c40 0 73.143-33.143 73.143-73.143z" />
<glyph unicode="&#xf074;" glyph-name="random" d="M380.571 676c-32-49.143-55.429-102.286-78.286-156-33.143 69.143-69.714 138.286-156 138.286h-128c-10.286 0-18.286 8-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h128c101.714 0 176.571-47.429 234.286-128.571zM1024 219.428c0-4.571-1.714-9.714-5.143-13.143l-182.857-182.857c-3.429-3.429-8.571-5.143-13.143-5.143-9.714 0-18.286 8.571-18.286 18.286v109.714c-169.714 0-274.286-20-380 128.571 31.429 49.143 54.857 102.286 77.714 156 33.143-69.143 69.714-138.286 156-138.286h146.286v109.714c0 10.286 8 18.286 18.286 18.286 5.143 0 9.714-2.286 13.714-5.714l182.286-182.286c3.429-3.429 5.143-8.571 5.143-13.143zM1024 731.428c0-4.571-1.714-9.714-5.143-13.143l-182.857-182.857c-3.429-3.429-8.571-5.143-13.143-5.143-9.714 0-18.286 8-18.286 18.286v109.714h-146.286c-76 0-112-52-144-113.714-16.571-32-30.857-65.143-44.571-97.714-63.429-147.429-137.714-300.571-323.429-300.571h-128c-10.286 0-18.286 8-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h128c76 0 112 52 144 113.714 16.571 32 30.857 65.143 44.571 97.714 63.429 147.429 137.714 300.571 323.429 300.571h146.286v109.714c0 10.286 8 18.286 18.286 18.286 5.143 0 9.714-2.286 13.714-5.714l182.286-182.286c3.429-3.429 5.143-8.571 5.143-13.143z" />
<glyph unicode="&#xf075;" glyph-name="comment" d="M1024 438.857c0-202.286-229.143-365.714-512-365.714-28 0-56 1.714-82.857 4.571-74.857-66.286-164-113.143-262.857-138.286-20.571-5.714-42.857-9.714-65.143-12.571-12.571-1.143-24.571 8-27.429 21.714v0.571c-2.857 14.286 6.857 22.857 15.429 33.143 36 40.571 77.143 74.857 104 170.286-117.714 66.857-193.143 170.286-193.143 286.286 0 201.714 229.143 365.714 512 365.714s512-163.429 512-365.714z" />
<glyph unicode="&#xf077;" glyph-name="chevron-up" d="M961.714 190.286l-94.857-94.286c-14.286-14.286-37.143-14.286-51.429 0l-303.429 303.429-303.429-303.429c-14.286-14.286-37.143-14.286-51.429 0l-94.857 94.286c-14.286 14.286-14.286 37.714 0 52l424 423.429c14.286 14.286 37.143 14.286 51.429 0l424-423.429c14.286-14.286 14.286-37.714 0-52z" />
<glyph unicode="&#xf078;" glyph-name="chevron-down" d="M961.714 489.143l-424-423.429c-14.286-14.286-37.143-14.286-51.429 0l-424 423.429c-14.286 14.286-14.286 37.714 0 52l94.857 94.286c14.286 14.286 37.143 14.286 51.429 0l303.429-303.429 303.429 303.429c14.286 14.286 37.143 14.286 51.429 0l94.857-94.286c14.286-14.286 14.286-37.714 0-52z" />
<glyph unicode="&#xf079;" glyph-name="retweet" horiz-adv-x="1097" d="M731.429 91.428c0-9.714-8.571-18.286-18.286-18.286h-548.571c-21.143 0-18.286 22.286-18.286 36.571v329.143h-109.714c-20 0-36.571 16.571-36.571 36.571 0 8.571 2.857 17.143 8.571 23.429l182.857 219.429c6.857 8 17.143 12.571 28 12.571s21.143-4.571 28-12.571l182.857-219.429c5.714-6.286 8.571-14.857 8.571-23.429 0-20-16.571-36.571-36.571-36.571h-109.714v-219.429h329.143c5.143 0 10.857-2.286 14.286-6.286l91.429-109.714c2.286-3.429 4-8 4-12zM1097.143 329.143c0-8.571-2.857-17.143-8.571-23.429l-182.857-219.429c-6.857-8-17.143-13.143-28-13.143s-21.143 5.143-28 13.143l-182.857 219.429c-5.714 6.286-8.571 14.857-8.571 23.429 0 20 16.571 36.571 36.571 36.571h109.714v219.429h-329.143c-5.143 0-10.857 2.286-14.286 6.857l-91.429 109.714c-2.286 2.857-4 7.429-4 11.429 0 9.714 8.571 18.286 18.286 18.286h548.571c21.143 0 18.286-22.286 18.286-36.571v-329.143h109.714c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf07b;" glyph-name="folder" horiz-adv-x="951" d="M950.857 603.428v-402.286c0-70.286-57.714-128-128-128h-694.857c-70.286 0-128 57.714-128 128v548.571c0 70.286 57.714 128 128 128h182.857c70.286 0 128-57.714 128-128v-18.286h384c70.286 0 128-57.714 128-128z" />
<glyph unicode="&#xf07c;" glyph-name="folder-open" horiz-adv-x="1074" d="M1073.714 406.857c0-13.714-8.571-27.429-17.714-37.714l-192-226.286c-33.143-38.857-100.571-69.714-150.857-69.714h-621.714c-20.571 0-49.714 6.286-49.714 32 0 13.714 8.571 27.429 17.714 37.714l192 226.286c33.143 38.857 100.571 69.714 150.857 69.714h621.714c20.571 0 49.714-6.286 49.714-32zM877.714 603.428v-91.429h-475.429c-71.429 0-160-40.571-206.286-95.429l-195.429-229.714c0 4.571-0.571 9.714-0.571 14.286v548.571c0 70.286 57.714 128 128 128h182.857c70.286 0 128-57.714 128-128v-18.286h310.857c70.286 0 128-57.714 128-128z" />
<glyph unicode="&#xf07d;" glyph-name="arrows-v" horiz-adv-x="439" d="M402.286 768c0-20-16.571-36.571-36.571-36.571h-73.143v-585.143h73.143c20 0 36.571-16.571 36.571-36.571 0-9.714-4-18.857-10.857-25.714l-146.286-146.286c-6.857-6.857-16-10.857-25.714-10.857s-18.857 4-25.714 10.857l-146.286 146.286c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h73.143v585.143h-73.143c-20 0-36.571 16.571-36.571 36.571 0 9.714 4 18.857 10.857 25.714l146.286 146.286c6.857 6.857 16 10.857 25.714 10.857s18.857-4 25.714-10.857l146.286-146.286c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf07e;" glyph-name="arrows-h" d="M1024 438.857c0-9.714-4-18.857-10.857-25.714l-146.286-146.286c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v73.143h-585.143v-73.143c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-146.286 146.286c-6.857 6.857-10.857 16-10.857 25.714s4 18.857 10.857 25.714l146.286 146.286c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571v-73.143h585.143v73.143c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l146.286-146.286c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf085;" glyph-name="cogs, gears" horiz-adv-x="1097" d="M512 438.857c0 80.571-65.714 146.286-146.286 146.286s-146.286-65.714-146.286-146.286 65.714-146.286 146.286-146.286 146.286 65.714 146.286 146.286zM950.857 146.286c0 40-33.143 73.143-73.143 73.143s-73.143-33.143-73.143-73.143c0-40.571 33.143-73.143 73.143-73.143 40.571 0 73.143 33.143 73.143 73.143zM950.857 731.428c0 40-33.143 73.143-73.143 73.143s-73.143-33.143-73.143-73.143c0-40.571 33.143-73.143 73.143-73.143 40.571 0 73.143 33.143 73.143 73.143zM731.429 490.857v-105.714c0-7.429-5.714-16-13.143-17.143l-88.571-13.714c-4.571-14.857-10.857-29.143-18.286-43.429 16-22.857 33.143-44 51.429-65.714 2.286-3.429 4-6.857 4-11.429 0-4-1.143-8-4-10.857-11.429-15.429-75.429-85.143-92-85.143-4.571 0-8.571 1.714-12 4l-65.714 51.429c-14.286-7.429-28.571-13.143-44-17.714-2.857-29.143-5.714-60.571-13.143-88.571-2.286-8-9.143-13.714-17.143-13.714h-106.286c-8 0-16 6.286-17.143 14.286l-13.143 87.429c-14.857 4.571-29.143 10.857-42.857 17.714l-67.429-50.857c-2.857-2.857-7.429-4-11.429-4-4.571 0-8.571 1.714-12 4.571-14.857 13.714-82.286 74.857-82.286 91.429 0 4 1.714 7.429 4 10.857 16.571 21.714 33.714 42.857 50.286 65.143-8 15.429-14.857 30.857-20 46.857l-86.857 13.714c-8 1.143-13.714 8.571-13.714 16.571v105.714c0 7.429 5.714 16 13.143 17.143l88.571 13.714c4.571 14.857 10.857 29.143 18.286 43.429-16 22.857-33.143 44-51.429 65.714-2.286 3.429-4 7.429-4 11.429s1.143 8 4 11.429c11.429 15.429 75.429 84.571 92 84.571 4.571 0 8.571-1.714 12-4l65.714-51.429c14.286 7.429 28.571 13.143 44 18.286 2.857 28.571 5.714 60 13.143 88 2.286 8 9.143 13.714 17.143 13.714h106.286c8 0 16-6.286 17.143-14.286l13.143-87.429c14.857-4.571 29.143-10.857 42.857-17.714l67.429 50.857c3.429 2.857 7.429 4 11.429 4 4.571 0 8.571-1.714 12-4.571 14.857-13.714 82.286-75.429 82.286-91.429 0-4-1.714-7.429-4-10.857-16.571-22.286-33.714-42.857-49.714-65.143 7.429-15.429 14.286-30.857 19.429-46.857l86.857-13.143c8-1.714 13.714-9.143 13.714-17.143zM1097.143 186.286v-80c0-8.571-73.714-16.571-85.143-17.714-4.571-10.857-10.286-20.571-17.143-29.714 5.143-11.429 29.143-68.571 29.143-78.857 0-1.714-0.571-2.857-2.286-4-6.857-4-68-40.571-70.857-40.571-7.429 0-50.286 57.143-56 65.714-5.714-0.571-11.429-1.143-17.143-1.143s-11.429 0.571-17.143 1.143c-5.714-8.571-48.571-65.714-56-65.714-2.857 0-64 36.571-70.857 40.571-1.714 1.143-2.286 2.857-2.286 4 0 9.714 24 67.429 29.143 78.857-6.857 9.143-12.571 18.857-17.143 29.714-11.429 1.143-85.143 9.143-85.143 17.714v80c0 8.571 73.714 16.571 85.143 17.714 4.571 10.286 10.286 20.571 17.143 29.714-5.143 11.429-29.143 69.143-29.143 78.857 0 1.143 0.571 2.857 2.286 4 6.857 3.429 68 40 70.857 40 7.429 0 50.286-56.571 56-65.143 5.714 0.571 11.429 1.143 17.143 1.143s11.429-0.571 17.143-1.143c16 22.286 33.143 44.571 52.571 64l3.429 1.143c2.857 0 64-36 70.857-40 1.714-1.143 2.286-2.857 2.286-4 0-10.286-24-67.429-29.143-78.857 6.857-9.143 12.571-19.429 17.143-29.714 11.429-1.143 85.143-9.143 85.143-17.714zM1097.143 771.428v-80c0-8.571-73.714-16.571-85.143-17.714-4.571-10.857-10.286-20.571-17.143-29.714 5.143-11.429 29.143-68.571 29.143-78.857 0-1.714-0.571-2.857-2.286-4-6.857-4-68-40.571-70.857-40.571-7.429 0-50.286 57.143-56 65.714-5.714-0.571-11.429-1.143-17.143-1.143s-11.429 0.571-17.143 1.143c-5.714-8.571-48.571-65.714-56-65.714-2.857 0-64 36.571-70.857 40.571-1.714 1.143-2.286 2.857-2.286 4 0 9.714 24 67.429 29.143 78.857-6.857 9.143-12.571 18.857-17.143 29.714-11.429 1.143-85.143 9.143-85.143 17.714v80c0 8.571 73.714 16.571 85.143 17.714 4.571 10.286 10.286 20.571 17.143 29.714-5.143 11.429-29.143 69.143-29.143 78.857 0 1.143 0.571 2.857 2.286 4 6.857 3.429 68 40 70.857 40 7.429 0 50.286-56.571 56-65.143 5.714 0.571 11.429 1.143 17.143 1.143s11.429-0.571 17.143-1.143c16 22.286 33.143 44.571 52.571 64l3.429 1.143c2.857 0 64-36 70.857-40 1.714-1.143 2.286-2.857 2.286-4 0-10.286-24-67.429-29.143-78.857 6.857-9.143 12.571-19.429 17.143-29.714 11.429-1.143 85.143-9.143 85.143-17.714z" />
<glyph unicode="&#xf089;" glyph-name="star-half" horiz-adv-x="475" d="M475.429 932.571v-765.143l-256.571-134.857c-7.429-4-14.857-6.857-22.857-6.857-16.571 0-24 13.714-24 28.571 0 4 0.571 7.429 1.143 11.429l49.143 285.714-208 202.286c-6.857 7.429-14.286 17.143-14.286 27.429 0 17.143 17.714 24 32 26.286l286.857 41.714 128.571 260c5.143 10.857 14.857 23.429 28 23.429v0z" />
<glyph unicode="&#xf08a;" glyph-name="heart-o" d="M950.857 610.286c0 160.571-108.571 194.286-200 194.286-85.143 0-181.143-92-210.857-127.429-13.714-16.571-42.286-16.571-56 0-29.714 35.429-125.714 127.429-210.857 127.429-91.429 0-200-33.714-200-194.286 0-104.571 105.714-201.714 106.857-202.857l332-320 331.429 319.429c1.714 1.714 107.429 98.857 107.429 203.429zM1024 610.286c0-137.143-125.714-252-130.857-257.143l-356-342.857c-6.857-6.857-16-10.286-25.143-10.286s-18.286 3.429-25.143 10.286l-356.571 344c-4.571 4-130.286 118.857-130.286 256 0 167.429 102.286 267.429 273.143 267.429 100 0 193.714-78.857 238.857-123.429 45.143 44.571 138.857 123.429 238.857 123.429 170.857 0 273.143-100 273.143-267.429z" />
<glyph unicode="&#xf08b;" glyph-name="sign-out" horiz-adv-x="896" d="M365.714 128c0-16 7.429-54.857-18.286-54.857h-182.857c-90.857 0-164.571 73.714-164.571 164.571v402.286c0 90.857 73.714 164.571 164.571 164.571h182.857c9.714 0 18.286-8.571 18.286-18.286 0-16 7.429-54.857-18.286-54.857h-182.857c-50.286 0-91.429-41.143-91.429-91.429v-402.286c0-50.286 41.143-91.429 91.429-91.429h164.571c14.286 0 36.571 2.857 36.571-18.286zM896 438.857c0-9.714-4-18.857-10.857-25.714l-310.857-310.857c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v164.571h-256c-20 0-36.571 16.571-36.571 36.571v219.429c0 20 16.571 36.571 36.571 36.571h256v164.571c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l310.857-310.857c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf08d;" glyph-name="thumb-tack" horiz-adv-x="658" d="M274.286 457.143v256c0 10.286-8 18.286-18.286 18.286s-18.286-8-18.286-18.286v-256c0-10.286 8-18.286 18.286-18.286s18.286 8 18.286 18.286zM658.286 256c0-20-16.571-36.571-36.571-36.571h-245.143l-29.143-276c-1.143-9.143-8.571-16.571-17.714-16.571h-0.571c-9.143 0-16.571 6.286-18.286 15.429l-43.429 277.143h-230.857c-20 0-36.571 16.571-36.571 36.571 0 93.714 70.857 182.857 146.286 182.857v292.571c-40 0-73.143 33.143-73.143 73.143s33.143 73.143 73.143 73.143h365.714c40 0 73.143-33.143 73.143-73.143s-33.143-73.143-73.143-73.143v-292.571c75.429 0 146.286-89.143 146.286-182.857z" />
<glyph unicode="&#xf08e;" glyph-name="external-link" d="M804.571 420.571v-182.857c0-90.857-73.714-164.571-164.571-164.571h-475.429c-90.857 0-164.571 73.714-164.571 164.571v475.429c0 90.857 73.714 164.571 164.571 164.571h402.286c10.286 0 18.286-8 18.286-18.286v-36.571c0-10.286-8-18.286-18.286-18.286h-402.286c-50.286 0-91.429-41.143-91.429-91.429v-475.429c0-50.286 41.143-91.429 91.429-91.429h475.429c50.286 0 91.429 41.143 91.429 91.429v182.857c0 10.286 8 18.286 18.286 18.286h36.571c10.286 0 18.286-8 18.286-18.286zM1024 914.286v-292.571c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-100.571 100.571-372.571-372.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-65.143 65.143c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l372.571 372.571-100.571 100.571c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h292.571c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf090;" glyph-name="sign-in" horiz-adv-x="878" d="M676.571 438.857c0-9.714-4-18.857-10.857-25.714l-310.857-310.857c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v164.571h-256c-20 0-36.571 16.571-36.571 36.571v219.429c0 20 16.571 36.571 36.571 36.571h256v164.571c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l310.857-310.857c6.857-6.857 10.857-16 10.857-25.714zM877.714 640v-402.286c0-90.857-73.714-164.571-164.571-164.571h-182.857c-9.714 0-18.286 8.571-18.286 18.286 0 16-7.429 54.857 18.286 54.857h182.857c50.286 0 91.429 41.143 91.429 91.429v402.286c0 50.286-41.143 91.429-91.429 91.429h-164.571c-14.286 0-36.571-2.857-36.571 18.286 0 16-7.429 54.857 18.286 54.857h182.857c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf093;" glyph-name="upload" horiz-adv-x="951" d="M731.429 109.714c0 20-16.571 36.571-36.571 36.571s-36.571-16.571-36.571-36.571 16.571-36.571 36.571-36.571 36.571 16.571 36.571 36.571zM877.714 109.714c0 20-16.571 36.571-36.571 36.571s-36.571-16.571-36.571-36.571 16.571-36.571 36.571-36.571 36.571 16.571 36.571 36.571zM950.857 237.714v-182.857c0-30.286-24.571-54.857-54.857-54.857h-841.143c-30.286 0-54.857 24.571-54.857 54.857v182.857c0 30.286 24.571 54.857 54.857 54.857h244c15.429-42.286 56-73.143 103.429-73.143h146.286c47.429 0 88 30.857 103.429 73.143h244c30.286 0 54.857-24.571 54.857-54.857zM765.143 608c-5.714-13.714-18.857-22.857-33.714-22.857h-146.286v-256c0-20-16.571-36.571-36.571-36.571h-146.286c-20 0-36.571 16.571-36.571 36.571v256h-146.286c-14.857 0-28 9.143-33.714 22.857-5.714 13.143-2.857 29.143 8 39.429l256 256c6.857 7.429 16.571 10.857 25.714 10.857s18.857-3.429 25.714-10.857l256-256c10.857-10.286 13.714-26.286 8-39.429z" />
<glyph unicode="&#xf096;" glyph-name="square-o" horiz-adv-x="805" d="M640 804.571h-475.429c-50.286 0-91.429-41.143-91.429-91.429v-475.429c0-50.286 41.143-91.429 91.429-91.429h475.429c50.286 0 91.429 41.143 91.429 91.429v475.429c0 50.286-41.143 91.429-91.429 91.429zM804.571 713.143v-475.429c0-90.857-73.714-164.571-164.571-164.571h-475.429c-90.857 0-164.571 73.714-164.571 164.571v475.429c0 90.857 73.714 164.571 164.571 164.571h475.429c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf097;" glyph-name="bookmark-o" horiz-adv-x="731" d="M658.286 804.571h-585.143v-709.714l292.571 280.571 50.857-48.571 241.714-232v709.714zM665.143 877.714c8.571 0 17.143-1.714 25.143-5.143 25.143-9.714 41.143-33.143 41.143-58.857v-736.571c0-25.714-16-49.143-41.143-58.857-8-3.429-16.571-4.571-25.143-4.571-17.714 0-34.286 6.286-47.429 18.286l-252 242.286-252-242.286c-13.143-12-29.714-18.857-47.429-18.857-8.571 0-17.143 1.714-25.143 5.143-25.143 9.714-41.143 33.143-41.143 58.857v736.571c0 25.714 16 49.143 41.143 58.857 8 3.429 16.571 5.143 25.143 5.143h598.857z" />
<glyph unicode="&#xf0a0;" glyph-name="hdd-o" horiz-adv-x="878" d="M594.286 256c0-25.143-20.571-45.714-45.714-45.714s-45.714 20.571-45.714 45.714 20.571 45.714 45.714 45.714 45.714-20.571 45.714-45.714zM740.571 256c0-25.143-20.571-45.714-45.714-45.714s-45.714 20.571-45.714 45.714 20.571 45.714 45.714 45.714 45.714-20.571 45.714-45.714zM804.571 164.571v182.857c0 9.714-8.571 18.286-18.286 18.286h-694.857c-9.714 0-18.286-8.571-18.286-18.286v-182.857c0-9.714 8.571-18.286 18.286-18.286h694.857c9.714 0 18.286 8.571 18.286 18.286zM101.714 438.857h674.286l-89.714 275.429c-2.857 9.714-13.714 17.143-24 17.143h-446.857c-10.286 0-21.143-7.429-24-17.143zM877.714 347.428v-182.857c0-50.286-41.143-91.429-91.429-91.429h-694.857c-50.286 0-91.429 41.143-91.429 91.429v182.857c0 15.429 4.571 28.571 9.143 42.857l112.571 346.286c13.143 40 51.429 68 93.714 68h446.857c42.286 0 80.571-28 93.714-68l112.571-346.286c4.571-14.286 9.143-27.429 9.143-42.857z" />
<glyph unicode="&#xf0a2;" glyph-name="bell-o" d="M521.143-18.286c0 5.143-4 9.143-9.143 9.143-45.143 0-82.286 37.143-82.286 82.286 0 5.143-4 9.143-9.143 9.143s-9.143-4-9.143-9.143c0-55.429 45.143-100.571 100.571-100.571 5.143 0 9.143 4 9.143 9.143zM140.571 146.286h742.857c-102.286 115.429-152 272-152 475.429 0 73.714-69.714 182.857-219.429 182.857s-219.429-109.143-219.429-182.857c0-203.429-49.714-360-152-475.429zM987.429 146.286c0-40-33.143-73.143-73.143-73.143h-256c0-80.571-65.714-146.286-146.286-146.286s-146.286 65.714-146.286 146.286h-256c-40 0-73.143 33.143-73.143 73.143 84.571 71.429 182.857 199.429 182.857 475.429 0 109.714 90.857 229.714 242.286 252-2.857 6.857-4.571 14.286-4.571 22.286 0 30.286 24.571 54.857 54.857 54.857s54.857-24.571 54.857-54.857c0-8-1.714-15.429-4.571-22.286 151.429-22.286 242.286-142.286 242.286-252 0-276 98.286-404 182.857-475.429z" />
<glyph unicode="&#xf0a3;" glyph-name="certificate" horiz-adv-x="878" d="M786.286 438.857l78.857-77.143c10.857-10.286 14.857-25.714 11.429-40-4-14.286-15.429-25.714-29.714-29.143l-107.429-27.429 30.286-106.286c4-14.286 0-29.714-10.857-40-10.286-10.857-25.714-14.857-40-10.857l-106.286 30.286-27.429-107.429c-3.429-14.286-14.857-25.714-29.143-29.714-3.429-0.571-7.429-1.143-10.857-1.143-10.857 0-21.714 4.571-29.143 12.571l-77.143 78.857-77.143-78.857c-10.286-10.857-25.714-14.857-40-11.429-14.857 4-25.714 15.429-29.143 29.714l-27.429 107.429-106.286-30.286c-14.286-4-29.714 0-40 10.857-10.857 10.286-14.857 25.714-10.857 40l30.286 106.286-107.429 27.429c-14.286 3.429-25.714 14.857-29.714 29.143-3.429 14.286 0.571 29.714 11.429 40l78.857 77.143-78.857 77.143c-10.857 10.286-14.857 25.714-11.429 40 4 14.286 15.429 25.714 29.714 29.143l107.429 27.429-30.286 106.286c-4 14.286 0 29.714 10.857 40 10.286 10.857 25.714 14.857 40 10.857l106.286-30.286 27.429 107.429c3.429 14.286 14.857 25.714 29.143 29.143 14.286 4 29.714 0 40-10.857l77.143-79.429 77.143 79.429c10.286 10.857 25.143 14.857 40 10.857 14.286-3.429 25.714-14.857 29.143-29.143l27.429-107.429 106.286 30.286c14.286 4 29.714 0 40-10.857 10.857-10.286 14.857-25.714 10.857-40l-30.286-106.286 107.429-27.429c14.286-3.429 25.714-14.857 29.714-29.143 3.429-14.286-0.571-29.714-11.429-40z" />
<glyph unicode="&#xf0a8;" glyph-name="arrow-circle-left" horiz-adv-x="878" d="M731.429 402.286v73.143c0 20-16.571 36.571-36.571 36.571h-286.857l108 108c6.857 6.857 10.857 16 10.857 25.714s-4 18.857-10.857 25.714l-52 52c-6.857 6.857-16 10.286-25.714 10.286s-18.857-3.429-25.714-10.286l-258.857-258.857c-6.857-6.857-10.286-16-10.286-25.714s3.429-18.857 10.286-25.714l258.857-258.857c6.857-6.857 16-10.286 25.714-10.286s18.857 3.429 25.714 10.286l52 52c6.857 6.857 10.286 16 10.286 25.714s-3.429 18.857-10.286 25.714l-108 108h286.857c20 0 36.571 16.571 36.571 36.571zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf0a9;" glyph-name="arrow-circle-right" horiz-adv-x="878" d="M734.286 438.857c0 9.714-3.429 18.857-10.286 25.714l-258.857 258.857c-6.857 6.857-16 10.286-25.714 10.286s-18.857-3.429-25.714-10.286l-52-52c-6.857-6.857-10.286-16-10.286-25.714s3.429-18.857 10.286-25.714l108-108h-286.857c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h286.857l-108-108c-6.857-6.857-10.857-16-10.857-25.714s4-18.857 10.857-25.714l52-52c6.857-6.857 16-10.286 25.714-10.286s18.857 3.429 25.714 10.286l258.857 258.857c6.857 6.857 10.286 16 10.286 25.714zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf0aa;" glyph-name="arrow-circle-up" horiz-adv-x="878" d="M733.714 439.428c0 9.714-3.429 18.857-10.286 25.714l-258.857 258.857c-6.857 6.857-16 10.286-25.714 10.286s-18.857-3.429-25.714-10.286l-258.857-258.857c-6.857-6.857-10.286-16-10.286-25.714s3.429-18.857 10.286-25.714l52-52c6.857-6.857 16-10.286 25.714-10.286s18.857 3.429 25.714 10.286l108 108v-286.857c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571v286.857l108-108c6.857-6.857 16-10.857 25.714-10.857s18.857 4 25.714 10.857l52 52c6.857 6.857 10.286 16 10.286 25.714zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf0ab;" glyph-name="arrow-circle-down" horiz-adv-x="878" d="M733.714 438.286c0 9.714-3.429 18.857-10.286 25.714l-52 52c-6.857 6.857-16 10.286-25.714 10.286s-18.857-3.429-25.714-10.286l-108-108v286.857c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-286.857l-108 108c-6.857 6.857-16 10.857-25.714 10.857s-18.857-4-25.714-10.857l-52-52c-6.857-6.857-10.286-16-10.286-25.714s3.429-18.857 10.286-25.714l258.857-258.857c6.857-6.857 16-10.286 25.714-10.286s18.857 3.429 25.714 10.286l258.857 258.857c6.857 6.857 10.286 16 10.286 25.714zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf0ad;" glyph-name="wrench" horiz-adv-x="962" d="M219.429 109.714c0 20-16.571 36.571-36.571 36.571s-36.571-16.571-36.571-36.571 16.571-36.571 36.571-36.571 36.571 16.571 36.571 36.571zM587.429 349.714l-389.714-389.714c-13.143-13.143-32-21.143-51.429-21.143s-38.286 8-52 21.143l-60.571 61.714c-13.714 13.143-21.714 32-21.714 51.429s8 38.286 21.714 52l389.143 389.143c29.714-74.857 89.714-134.857 164.571-164.571zM949.714 598.286c0-18.857-6.857-42.286-13.143-60.571-36-101.714-133.714-172-241.714-172-141.143 0-256 114.857-256 256s114.857 256 256 256c41.714 0 96-12.571 130.857-36 5.714-4 9.143-9.143 9.143-16 0-6.286-4-12.571-9.143-16l-167.429-96.571v-128l110.286-61.143c18.857 10.857 151.429 94.286 162.857 94.286s18.286-8.571 18.286-20z" />
<glyph unicode="&#xf0ae;" glyph-name="tasks" d="M585.143 146.286h365.714v73.143h-365.714v-73.143zM365.714 438.857h585.143v73.143h-585.143v-73.143zM731.429 731.428h219.429v73.143h-219.429v-73.143zM1024 256v-146.286c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v146.286c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM1024 548.571v-146.286c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v146.286c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571zM1024 841.143v-146.286c0-20-16.571-36.571-36.571-36.571h-950.857c-20 0-36.571 16.571-36.571 36.571v146.286c0 20 16.571 36.571 36.571 36.571h950.857c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf0b0;" glyph-name="filter" horiz-adv-x="805" d="M801.714 782.286c5.714-13.714 2.857-29.714-8-40l-281.714-281.714v-424c0-14.857-9.143-28-22.286-33.714-4.571-1.714-9.714-2.857-14.286-2.857-9.714 0-18.857 3.429-25.714 10.857l-146.286 146.286c-6.857 6.857-10.857 16-10.857 25.714v277.714l-281.714 281.714c-10.857 10.286-13.714 26.286-8 40 5.714 13.143 18.857 22.286 33.714 22.286h731.429c14.857 0 28-9.143 33.714-22.286z" />
<glyph unicode="&#xf0b1;" glyph-name="briefcase" d="M365.714 804.571h292.571v73.143h-292.571v-73.143zM1024 438.857v-274.286c0-50.286-41.143-91.429-91.429-91.429h-841.143c-50.286 0-91.429 41.143-91.429 91.429v274.286h384v-91.429c0-20 16.571-36.571 36.571-36.571h182.857c20 0 36.571 16.571 36.571 36.571v91.429h384zM585.143 438.857v-73.143h-146.286v73.143h146.286zM1024 713.143v-219.429h-1024v219.429c0 50.286 41.143 91.429 91.429 91.429h201.143v91.429c0 30.286 24.571 54.857 54.857 54.857h329.143c30.286 0 54.857-24.571 54.857-54.857v-91.429h201.143c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf0b2;" glyph-name="arrows-alt" horiz-adv-x="878" d="M733.143 641.714l-202.857-202.857 202.857-202.857 82.286 82.286c10.286 10.857 26.286 13.714 40 8 13.143-5.714 22.286-18.857 22.286-33.714v-256c0-20-16.571-36.571-36.571-36.571h-256c-14.857 0-28 9.143-33.714 22.857-5.714 13.143-2.857 29.143 8 39.429l82.286 82.286-202.857 202.857-202.857-202.857 82.286-82.286c10.857-10.286 13.714-26.286 8-39.429-5.714-13.714-18.857-22.857-33.714-22.857h-256c-20 0-36.571 16.571-36.571 36.571v256c0 14.857 9.143 28 22.857 33.714 13.143 5.714 29.143 2.857 39.429-8l82.286-82.286 202.857 202.857-202.857 202.857-82.286-82.286c-6.857-6.857-16-10.857-25.714-10.857-4.571 0-9.714 1.143-13.714 2.857-13.714 5.714-22.857 18.857-22.857 33.714v256c0 20 16.571 36.571 36.571 36.571h256c14.857 0 28-9.143 33.714-22.857 5.714-13.143 2.857-29.143-8-39.429l-82.286-82.286 202.857-202.857 202.857 202.857-82.286 82.286c-10.857 10.286-13.714 26.286-8 39.429 5.714 13.714 18.857 22.857 33.714 22.857h256c20 0 36.571-16.571 36.571-36.571v-256c0-14.857-9.143-28-22.286-33.714-4.571-1.714-9.714-2.857-14.286-2.857-9.714 0-18.857 4-25.714 10.857z" />
<glyph unicode="&#xf0c2;" glyph-name="cloud" horiz-adv-x="1097" d="M1097.143 292.571c0-121.143-98.286-219.429-219.429-219.429h-621.714c-141.143 0-256 114.857-256 256 0 102.286 60.571 190.857 147.429 231.429-0.571 8-1.143 16.571-1.143 24.571 0 161.714 130.857 292.571 292.571 292.571 122.286 0 226.857-74.857 270.857-181.714 25.143 22.286 58.286 35.429 94.857 35.429 80.571 0 146.286-65.714 146.286-146.286 0-29.143-8.571-56-23.429-78.857 97.143-22.857 169.714-109.714 169.714-213.714z" />
<glyph unicode="&#xf0c5;" glyph-name="copy, files-o" d="M969.143 731.428c30.286 0 54.857-24.571 54.857-54.857v-694.857c0-30.286-24.571-54.857-54.857-54.857h-548.571c-30.286 0-54.857 24.571-54.857 54.857v164.571h-310.857c-30.286 0-54.857 24.571-54.857 54.857v384c0 30.286 17.714 72.571 38.857 93.714l233.143 233.143c21.143 21.143 63.429 38.857 93.714 38.857h237.714c30.286 0 54.857-24.571 54.857-54.857v-187.429c22.286 13.143 50.857 22.857 73.143 22.857h237.714zM658.286 609.714l-170.857-170.857h170.857v170.857zM292.571 829.143l-170.857-170.857h170.857v170.857zM404.571 459.428l180.571 180.571v237.714h-219.429v-237.714c0-30.286-24.571-54.857-54.857-54.857h-237.714v-365.714h292.571v146.286c0 30.286 17.714 72.571 38.857 93.714zM950.857 0v658.286h-219.429v-237.714c0-30.286-24.571-54.857-54.857-54.857h-237.714v-365.714h512z" />
<glyph unicode="&#xf0c7;" glyph-name="floppy-o, save" horiz-adv-x="878" d="M219.429 73.143h438.857v219.429h-438.857v-219.429zM731.429 73.143h73.143v512c0 10.857-9.714 34.286-17.143 41.714l-160.571 160.571c-8 8-30.286 17.143-41.714 17.143v-237.714c0-30.286-24.571-54.857-54.857-54.857h-329.143c-30.286 0-54.857 24.571-54.857 54.857v237.714h-73.143v-731.429h73.143v237.714c0 30.286 24.571 54.857 54.857 54.857h475.429c30.286 0 54.857-24.571 54.857-54.857v-237.714zM512 603.428v182.857c0 9.714-8.571 18.286-18.286 18.286h-109.714c-9.714 0-18.286-8.571-18.286-18.286v-182.857c0-9.714 8.571-18.286 18.286-18.286h109.714c9.714 0 18.286 8.571 18.286 18.286zM877.714 585.143v-530.286c0-30.286-24.571-54.857-54.857-54.857h-768c-30.286 0-54.857 24.571-54.857 54.857v768c0 30.286 24.571 54.857 54.857 54.857h530.286c30.286 0 72-17.143 93.714-38.857l160-160c21.714-21.714 38.857-63.429 38.857-93.714z" />
<glyph unicode="&#xf0c8;" glyph-name="square" horiz-adv-x="878" d="M877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf0c9;" glyph-name="bars, navicon, reorder" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf0ca;" glyph-name="list-ul" d="M219.429 146.286c0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714 49.143 109.714 109.714 109.714 109.714-49.143 109.714-109.714zM219.429 438.857c0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714 49.143 109.714 109.714 109.714 109.714-49.143 109.714-109.714zM1024 201.143v-109.714c0-9.714-8.571-18.286-18.286-18.286h-694.857c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h694.857c9.714 0 18.286-8.571 18.286-18.286zM219.429 731.428c0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714 49.143 109.714 109.714 109.714 109.714-49.143 109.714-109.714zM1024 493.714v-109.714c0-9.714-8.571-18.286-18.286-18.286h-694.857c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h694.857c9.714 0 18.286-8.571 18.286-18.286zM1024 786.286v-109.714c0-9.714-8.571-18.286-18.286-18.286h-694.857c-9.714 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8.571 18.286 18.286 18.286h694.857c9.714 0 18.286-8.571 18.286-18.286z" />
<glyph unicode="&#xf0cb;" glyph-name="list-ol" horiz-adv-x="1033" d="M217.714 25.143c0-62.857-49.143-98.286-108.571-98.286-36 0-72.571 12-98.286 37.714l32.571 50.286c15.429-14.286 38.857-25.714 60.571-25.714 20 0 41.143 9.714 41.143 32.571 0 32-36.571 33.714-60 32l-14.857 32c20.571 26.286 39.429 55.429 64 77.714v0.571c-18.286 0-37.143-1.143-55.429-1.143v-30.286h-60.571v86.857h190.286v-50.286l-54.286-65.714c38.286-9.143 63.429-38.857 63.429-78.286zM218.857 383.428v-90.857h-206.857c-1.714 10.286-3.429 20.571-3.429 30.857 0 105.714 129.143 121.714 129.143 169.714 0 19.429-12 29.714-30.857 29.714-20 0-36.571-17.143-46.286-33.143l-48.571 33.714c18.857 39.429 57.714 61.714 101.143 61.714 53.143 0 98.857-31.429 98.857-88 0-84.571-124-103.429-125.714-148h72.571v34.286h60zM1024 201.143v-109.714c0-9.714-8.571-18.286-18.286-18.286h-694.857c-10.286 0-18.286 8.571-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h694.857c9.714 0 18.286-8 18.286-18.286zM219.429 714.857v-56.571h-191.429v56.571h61.143c0 46.286 0.571 92.571 0.571 138.857v6.857h-1.143c-6.286-12.571-17.714-21.143-28.571-30.857l-40.571 43.429 77.714 72.571h60.571v-230.857h61.714zM1024 493.714v-109.714c0-9.714-8.571-18.286-18.286-18.286h-694.857c-10.286 0-18.286 8.571-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h694.857c9.714 0 18.286-8 18.286-18.286zM1024 786.286v-109.714c0-9.714-8.571-18.286-18.286-18.286h-694.857c-10.286 0-18.286 8.571-18.286 18.286v109.714c0 9.714 8 18.286 18.286 18.286h694.857c9.714 0 18.286-8.571 18.286-18.286z" />
<glyph unicode="&#xf0ce;" glyph-name="table" horiz-adv-x="951" d="M292.571 164.571v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM292.571 384v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM585.143 164.571v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM292.571 603.428v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM585.143 384v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM877.714 164.571v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM585.143 603.428v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM877.714 384v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM877.714 603.428v109.714c0 10.286-8 18.286-18.286 18.286h-182.857c-10.286 0-18.286-8-18.286-18.286v-109.714c0-10.286 8-18.286 18.286-18.286h182.857c10.286 0 18.286 8 18.286 18.286zM950.857 786.286v-621.714c0-50.286-41.143-91.429-91.429-91.429h-768c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h768c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf0d7;" glyph-name="caret-down" horiz-adv-x="585" d="M585.143 548.571c0-9.714-4-18.857-10.857-25.714l-256-256c-6.857-6.857-16-10.857-25.714-10.857s-18.857 4-25.714 10.857l-256 256c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h512c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf0d8;" glyph-name="caret-up" horiz-adv-x="585" d="M585.143 256c0-20-16.571-36.571-36.571-36.571h-512c-20 0-36.571 16.571-36.571 36.571 0 9.714 4 18.857 10.857 25.714l256 256c6.857 6.857 16 10.857 25.714 10.857s18.857-4 25.714-10.857l256-256c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf0d9;" glyph-name="caret-left" horiz-adv-x="402" d="M365.714 694.857v-512c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-256 256c-6.857 6.857-10.857 16-10.857 25.714s4 18.857 10.857 25.714l256 256c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf0da;" glyph-name="caret-right" horiz-adv-x="329" d="M329.143 438.857c0-9.714-4-18.857-10.857-25.714l-256-256c-6.857-6.857-16-10.857-25.714-10.857-20 0-36.571 16.571-36.571 36.571v512c0 20 16.571 36.571 36.571 36.571 9.714 0 18.857-4 25.714-10.857l256-256c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf0db;" glyph-name="columns" horiz-adv-x="951" d="M91.429 73.143h347.429v658.286h-365.714v-640c0-9.714 8.571-18.286 18.286-18.286zM877.714 91.428v640h-365.714v-658.286h347.429c9.714 0 18.286 8.571 18.286 18.286zM950.857 786.286v-694.857c0-50.286-41.143-91.429-91.429-91.429h-768c-50.286 0-91.429 41.143-91.429 91.429v694.857c0 50.286 41.143 91.429 91.429 91.429h768c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf0dc;" glyph-name="sort, unsorted" horiz-adv-x="585" d="M585.143 329.143c0-9.714-4-18.857-10.857-25.714l-256-256c-6.857-6.857-16-10.857-25.714-10.857s-18.857 4-25.714 10.857l-256 256c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h512c20 0 36.571-16.571 36.571-36.571zM585.143 548.571c0-20-16.571-36.571-36.571-36.571h-512c-20 0-36.571 16.571-36.571 36.571 0 9.714 4 18.857 10.857 25.714l256 256c6.857 6.857 16 10.857 25.714 10.857s18.857-4 25.714-10.857l256-256c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf0dd;" glyph-name="sort-desc, sort-down" horiz-adv-x="585" d="M585.143 329.143c0-9.714-4-18.857-10.857-25.714l-256-256c-6.857-6.857-16-10.857-25.714-10.857s-18.857 4-25.714 10.857l-256 256c-6.857 6.857-10.857 16-10.857 25.714 0 20 16.571 36.571 36.571 36.571h512c20 0 36.571-16.571 36.571-36.571z" />
<glyph unicode="&#xf0de;" glyph-name="sort-asc, sort-up" horiz-adv-x="585" d="M585.143 548.571c0-20-16.571-36.571-36.571-36.571h-512c-20 0-36.571 16.571-36.571 36.571 0 9.714 4 18.857 10.857 25.714l256 256c6.857 6.857 16 10.857 25.714 10.857s18.857-4 25.714-10.857l256-256c6.857-6.857 10.857-16 10.857-25.714z" />
<glyph unicode="&#xf0e2;" glyph-name="rotate-left, undo" horiz-adv-x="878" d="M877.714 438.857c0-241.714-197.143-438.857-438.857-438.857-130.857 0-254.286 57.714-337.714 158.286-5.714 7.429-5.143 18.286 1.143 24.571l78.286 78.857c4 3.429 9.143 5.143 14.286 5.143 5.143-0.571 10.286-2.857 13.143-6.857 56-72.571 140-113.714 230.857-113.714 161.143 0 292.571 131.429 292.571 292.571s-131.429 292.571-292.571 292.571c-74.857 0-145.714-28.571-198.857-78.286l78.286-78.857c10.857-10.286 13.714-26.286 8-39.429-5.714-13.714-18.857-22.857-33.714-22.857h-256c-20 0-36.571 16.571-36.571 36.571v256c0 14.857 9.143 28 22.857 33.714 13.143 5.714 29.143 2.857 39.429-8l74.286-73.714c80.571 76 189.714 121.143 302.286 121.143 241.714 0 438.857-197.143 438.857-438.857z" />
<glyph unicode="&#xf0f6;" glyph-name="file-text-o" horiz-adv-x="878" d="M838.857 733.714c21.143-21.143 38.857-63.429 38.857-93.714v-658.286c0-30.286-24.571-54.857-54.857-54.857h-768c-30.286 0-54.857 24.571-54.857 54.857v914.286c0 30.286 24.571 54.857 54.857 54.857h512c30.286 0 72.571-17.714 93.714-38.857zM585.143 873.143v-214.857h214.857c-3.429 9.714-8.571 19.429-12.571 23.429l-178.857 178.857c-4 4-13.714 9.143-23.429 12.571zM804.571 0v585.143h-237.714c-30.286 0-54.857 24.571-54.857 54.857v237.714h-438.857v-877.714h731.429zM219.429 493.714c0 10.286 8 18.286 18.286 18.286h402.286c10.286 0 18.286-8 18.286-18.286v-36.571c0-10.286-8-18.286-18.286-18.286h-402.286c-10.286 0-18.286 8-18.286 18.286v36.571zM640 365.714c10.286 0 18.286-8 18.286-18.286v-36.571c0-10.286-8-18.286-18.286-18.286h-402.286c-10.286 0-18.286 8-18.286 18.286v36.571c0 10.286 8 18.286 18.286 18.286h402.286zM640 219.428c10.286 0 18.286-8 18.286-18.286v-36.571c0-10.286-8-18.286-18.286-18.286h-402.286c-10.286 0-18.286 8-18.286 18.286v36.571c0 10.286 8 18.286 18.286 18.286h402.286z" />
<glyph unicode="&#xf0fe;" glyph-name="plus-square" horiz-adv-x="878" d="M731.429 402.286v73.143c0 20-16.571 36.571-36.571 36.571h-182.857v182.857c0 20-16.571 36.571-36.571 36.571h-73.143c-20 0-36.571-16.571-36.571-36.571v-182.857h-182.857c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h182.857v-182.857c0-20 16.571-36.571 36.571-36.571h73.143c20 0 36.571 16.571 36.571 36.571v182.857h182.857c20 0 36.571 16.571 36.571 36.571zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf100;" glyph-name="angle-double-left" horiz-adv-x="603" d="M358.286 164.571c0-4.571-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-266.286 266.286c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l28.571-28.571c3.429-3.429 5.714-8.571 5.714-13.143s-2.286-9.714-5.714-13.143l-224.571-224.571 224.571-224.571c3.429-3.429 5.714-8.571 5.714-13.143zM577.714 164.571c0-4.571-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-266.286 266.286c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l28.571-28.571c3.429-3.429 5.714-8.571 5.714-13.143s-2.286-9.714-5.714-13.143l-224.571-224.571 224.571-224.571c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf101;" glyph-name="angle-double-right" horiz-adv-x="567" d="M340 402.286c0-4.571-2.286-9.714-5.714-13.143l-266.286-266.286c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l224.571 224.571-224.571 224.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l28.571 28.571c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143zM559.429 402.286c0-4.571-2.286-9.714-5.714-13.143l-266.286-266.286c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l224.571 224.571-224.571 224.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l28.571 28.571c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf102;" glyph-name="angle-double-up" horiz-adv-x="658" d="M614.286 201.143c0-4.571-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8-5.714-13.143-5.714-4.571 0-9.714 2.286-13.143 5.714l-224.571 224.571-224.571-224.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143zM614.286 420.571c0-4.571-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8-5.714-13.143-5.714-4.571 0-9.714 2.286-13.143 5.714l-224.571 224.571-224.571-224.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf103;" glyph-name="angle-double-down" horiz-adv-x="658" d="M614.286 457.143c0-4.571-2.286-9.714-5.714-13.143l-266.286-266.286c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-266.286 266.286c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l28.571 28.571c3.429 3.429 8 5.714 13.143 5.714 4.571 0 9.714-2.286 13.143-5.714l224.571-224.571 224.571 224.571c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l28.571-28.571c3.429-3.429 5.714-8.571 5.714-13.143zM614.286 676.571c0-4.571-2.286-9.714-5.714-13.143l-266.286-266.286c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-266.286 266.286c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l28.571 28.571c3.429 3.429 8 5.714 13.143 5.714 4.571 0 9.714-2.286 13.143-5.714l224.571-224.571 224.571 224.571c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l28.571-28.571c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf104;" glyph-name="angle-left" horiz-adv-x="384" d="M358.286 640c0-4.571-2.286-9.714-5.714-13.143l-224.571-224.571 224.571-224.571c3.429-3.429 5.714-8.571 5.714-13.143s-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-266.286 266.286c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l28.571-28.571c3.429-3.429 5.714-8 5.714-13.143z" />
<glyph unicode="&#xf105;" glyph-name="angle-right" horiz-adv-x="347" d="M340 402.286c0-4.571-2.286-9.714-5.714-13.143l-266.286-266.286c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8-5.714 13.143 0 4.571 2.286 9.714 5.714 13.143l224.571 224.571-224.571 224.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l28.571 28.571c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf106;" glyph-name="angle-up" horiz-adv-x="658" d="M614.286 274.286c0-4.571-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8-5.714-13.143-5.714-4.571 0-9.714 2.286-13.143 5.714l-224.571 224.571-224.571-224.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf107;" glyph-name="angle-down" horiz-adv-x="658" d="M614.286 530.286c0-4.571-2.286-9.714-5.714-13.143l-266.286-266.286c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-266.286 266.286c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l28.571 28.571c3.429 3.429 8 5.714 13.143 5.714 4.571 0 9.714-2.286 13.143-5.714l224.571-224.571 224.571 224.571c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l28.571-28.571c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xf10c;" glyph-name="circle-o" horiz-adv-x="878" d="M438.857 749.714c-171.429 0-310.857-139.429-310.857-310.857s139.429-310.857 310.857-310.857 310.857 139.429 310.857 310.857-139.429 310.857-310.857 310.857zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857v0c242.286 0 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf110;" glyph-name="spinner" d="M300.571 154.286c0-40-32.571-73.143-73.143-73.143-40 0-73.143 33.143-73.143 73.143 0 40.571 33.143 73.143 73.143 73.143 40.571 0 73.143-32.571 73.143-73.143zM585.143 36.571c0-40.571-32.571-73.143-73.143-73.143s-73.143 32.571-73.143 73.143 32.571 73.143 73.143 73.143 73.143-32.571 73.143-73.143zM182.857 438.857c0-40.571-32.571-73.143-73.143-73.143s-73.143 32.571-73.143 73.143 32.571 73.143 73.143 73.143 73.143-32.571 73.143-73.143zM869.714 154.286c0-40-33.143-73.143-73.143-73.143-40.571 0-73.143 33.143-73.143 73.143 0 40.571 32.571 73.143 73.143 73.143 40 0 73.143-32.571 73.143-73.143zM318.857 723.428c0-50.286-41.143-91.429-91.429-91.429s-91.429 41.143-91.429 91.429 41.143 91.429 91.429 91.429 91.429-41.143 91.429-91.429zM987.429 438.857c0-40.571-32.571-73.143-73.143-73.143s-73.143 32.571-73.143 73.143 32.571 73.143 73.143 73.143 73.143-32.571 73.143-73.143zM621.714 841.143c0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714 49.143 109.714 109.714 109.714 109.714-49.143 109.714-109.714zM924.571 723.428c0-70.857-57.714-128-128-128-70.857 0-128 57.143-128 128 0 70.286 57.143 128 128 128 70.286 0 128-57.714 128-128z" />
<glyph unicode="&#xf111;" glyph-name="circle" horiz-adv-x="878" d="M877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf112;" glyph-name="mail-reply, reply" d="M1024 310.857c0-80-40-184.571-72.571-257.714-6.286-13.143-12.571-31.429-21.143-43.429-4-5.714-8-9.714-16-9.714-11.429 0-18.286 9.143-18.286 20 0 9.143 2.286 19.429 2.857 28.571 1.714 23.429 2.857 46.857 2.857 70.286 0 272.571-161.714 320-408 320h-128v-146.286c0-20-16.571-36.571-36.571-36.571-9.714 0-18.857 4-25.714 10.857l-292.571 292.571c-6.857 6.857-10.857 16-10.857 25.714s4 18.857 10.857 25.714l292.571 292.571c6.857 6.857 16 10.857 25.714 10.857 20 0 36.571-16.571 36.571-36.571v-146.286h128c187.429 0 420.571-33.143 500-230.286 24-60.571 30.286-126.286 30.286-190.286z" />
<glyph unicode="&#xf114;" glyph-name="folder-o" horiz-adv-x="951" d="M877.714 201.143v402.286c0 30.286-24.571 54.857-54.857 54.857h-402.286c-30.286 0-54.857 24.571-54.857 54.857v36.571c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-548.571c0-30.286 24.571-54.857 54.857-54.857h694.857c30.286 0 54.857 24.571 54.857 54.857zM950.857 603.428v-402.286c0-70.286-57.714-128-128-128h-694.857c-70.286 0-128 57.714-128 128v548.571c0 70.286 57.714 128 128 128h182.857c70.286 0 128-57.714 128-128v-18.286h384c70.286 0 128-57.714 128-128z" />
<glyph unicode="&#xf115;" glyph-name="folder-open-o" horiz-adv-x="1091" d="M1017.714 418.857c0 16-17.714 20-30.286 20h-621.714c-30.286 0-70.286-18.857-89.714-42.286l-168-207.429c-5.143-6.857-10.286-14.286-10.286-22.857 0-16 17.714-20 30.286-20h621.714c30.286 0 70.286 18.857 89.714 42.857l168 207.429c5.143 6.286 10.286 13.714 10.286 22.286zM365.714 512h438.857v91.429c0 30.286-24.571 54.857-54.857 54.857h-329.143c-30.286 0-54.857 24.571-54.857 54.857v36.571c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-487.429l146.286 180c33.143 40.571 94.286 69.714 146.286 69.714zM1090.857 418.857c0-25.143-10.857-49.143-26.286-68.571l-168.571-207.429c-32.571-40-94.857-69.714-146.286-69.714h-621.714c-70.286 0-128 57.714-128 128v548.571c0 70.286 57.714 128 128 128h182.857c70.286 0 128-57.714 128-128v-18.286h310.857c70.286 0 128-57.714 128-128v-91.429h109.714c38.857 0 77.714-17.714 94.857-54.286 5.714-12 8.571-25.143 8.571-38.857z" />
<glyph unicode="&#xf11c;" glyph-name="keyboard-o" horiz-adv-x="1097" d="M219.429 283.428v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM292.571 429.714v-54.857c0-5.143-4-9.143-9.143-9.143h-128c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h128c5.143 0 9.143-4 9.143-9.143zM219.429 576v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM804.571 283.428v-54.857c0-5.143-4-9.143-9.143-9.143h-493.714c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h493.714c5.143 0 9.143-4 9.143-9.143zM438.857 429.714v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM365.714 576v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM585.143 429.714v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM512 576v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM731.429 429.714v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM950.857 283.428v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM658.286 576v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM804.571 576v-54.857c0-5.143-4-9.143-9.143-9.143h-54.857c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM950.857 576v-201.143c0-5.143-4-9.143-9.143-9.143h-128c-5.143 0-9.143 4-9.143 9.143v54.857c0 5.143 4 9.143 9.143 9.143h64v137.143c0 5.143 4 9.143 9.143 9.143h54.857c5.143 0 9.143-4 9.143-9.143zM1024 146.286v512h-950.857v-512h950.857zM1097.143 658.286v-512c0-40.571-32.571-73.143-73.143-73.143h-950.857c-40.571 0-73.143 32.571-73.143 73.143v512c0 40.571 32.571 73.143 73.143 73.143h950.857c40.571 0 73.143-32.571 73.143-73.143z" />
<glyph unicode="&#xf120;" glyph-name="terminal" horiz-adv-x="957" d="M334.286 389.143l-266.286-266.286c-7.429-7.429-18.857-7.429-26.286 0l-28.571 28.571c-7.429 7.429-7.429 18.857 0 26.286l224.571 224.571-224.571 224.571c-7.429 7.429-7.429 18.857 0 26.286l28.571 28.571c7.429 7.429 18.857 7.429 26.286 0l266.286-266.286c7.429-7.429 7.429-18.857 0-26.286zM950.857 128v-36.571c0-10.286-8-18.286-18.286-18.286h-548.571c-10.286 0-18.286 8-18.286 18.286v36.571c0 10.286 8 18.286 18.286 18.286h548.571c10.286 0 18.286-8 18.286-18.286z" />
<glyph unicode="&#xf121;" glyph-name="code" horiz-adv-x="1097" d="M352.571 151.428l-28.571-28.571c-7.429-7.429-18.857-7.429-26.286 0l-266.286 266.286c-7.429 7.429-7.429 18.857 0 26.286l266.286 266.286c7.429 7.429 18.857 7.429 26.286 0l28.571-28.571c7.429-7.429 7.429-18.857 0-26.286l-224.571-224.571 224.571-224.571c7.429-7.429 7.429-18.857 0-26.286zM690.286 761.143l-213.143-737.714c-2.857-9.714-13.143-15.429-22.286-12.571l-35.429 9.714c-9.714 2.857-15.429 13.143-12.571 22.857l213.143 737.714c2.857 9.714 13.143 15.429 22.286 12.571l35.429-9.714c9.714-2.857 15.429-13.143 12.571-22.857zM1065.714 389.143l-266.286-266.286c-7.429-7.429-18.857-7.429-26.286 0l-28.571 28.571c-7.429 7.429-7.429 18.857 0 26.286l224.571 224.571-224.571 224.571c-7.429 7.429-7.429 18.857 0 26.286l28.571 28.571c7.429 7.429 18.857 7.429 26.286 0l266.286-266.286c7.429-7.429 7.429-18.857 0-26.286z" />
<glyph unicode="&#xf122;" glyph-name="mail-reply-all, reply-all" horiz-adv-x="1021" d="M365.714 332.571v-40c0-14.857-9.143-28-22.286-33.714-4.571-1.714-9.714-2.857-14.286-2.857-9.714 0-18.857 3.429-25.714 10.857l-292.571 292.571c-14.286 14.286-14.286 37.143 0 51.429l292.571 292.571c10.286 10.857 26.286 13.714 40 8 13.143-5.714 22.286-18.857 22.286-33.714v-39.429l-226.857-227.429c-14.286-14.286-14.286-37.143 0-51.429zM1024 310.857c0-118.857-89.714-293.714-93.714-301.143-2.857-6.286-9.143-9.714-16-9.714-1.714 0-3.429 0-5.143 0.571-8.571 2.857-13.714 10.857-13.143 19.429 16.571 156-2.857 258.857-60.571 322.857-48.571 54.286-127.429 83.429-250.286 93.143v-143.429c0-14.857-9.143-28-22.286-33.714-4.571-1.714-9.714-2.857-14.286-2.857-9.714 0-18.857 3.429-25.714 10.857l-292.571 292.571c-14.286 14.286-14.286 37.143 0 51.429l292.571 292.571c10.286 10.857 26.286 13.714 40 8 13.143-5.714 22.286-18.857 22.286-33.714v-149.714c157.714-10.857 270.286-52.571 342.286-126.286 86.286-88.571 96.571-208.571 96.571-290.857z" />
<glyph unicode="&#xf123;" glyph-name="star-half-empty, star-half-full, star-half-o" horiz-adv-x="951" d="M677.714 404l146.857 142.857-241.143 35.429-17.143 34.286-90.857 184v-550.286l33.714-17.714 181.714-96-34.286 202.857-6.857 37.714zM936 553.714l-207.429-202.286 49.143-285.714c4-25.143-5.143-40-22.857-40-6.286 0-14.286 2.286-22.857 6.857l-256.571 134.857-256.571-134.857c-8.571-4.571-16.571-6.857-22.857-6.857-17.714 0-26.857 14.857-22.857 40l49.143 285.714-208 202.286c-24.571 24.571-16.571 48.571 17.714 53.714l286.857 41.714 128.571 260c7.429 15.429 17.714 23.429 28 23.429v0c10.286 0 20-8 28-23.429l128.571-260 286.857-41.714c34.286-5.143 42.286-29.143 17.143-53.714z" />
<glyph unicode="&#xf125;" glyph-name="crop" horiz-adv-x="953" d="M318.286 219.428h340v340zM292.571 245.143l340 340h-340v-340zM950.857 201.143v-109.714c0-10.286-8-18.286-18.286-18.286h-128v-128c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v128h-493.714c-10.286 0-18.286 8-18.286 18.286v493.714h-128c-10.286 0-18.286 8-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h128v128c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286v-128h486.286l140.571 141.143c7.429 6.857 18.857 6.857 26.286 0 6.857-7.429 6.857-18.857 0-26.286l-141.143-140.571v-486.286h128c10.286 0 18.286-8 18.286-18.286z" />
<glyph unicode="&#xf126;" glyph-name="code-fork" horiz-adv-x="585" d="M164.571 109.714c0 30.286-24.571 54.857-54.857 54.857s-54.857-24.571-54.857-54.857 24.571-54.857 54.857-54.857 54.857 24.571 54.857 54.857zM164.571 768c0 30.286-24.571 54.857-54.857 54.857s-54.857-24.571-54.857-54.857 24.571-54.857 54.857-54.857 54.857 24.571 54.857 54.857zM530.286 694.857c0 30.286-24.571 54.857-54.857 54.857s-54.857-24.571-54.857-54.857 24.571-54.857 54.857-54.857 54.857 24.571 54.857 54.857zM585.143 694.857c0-40.571-22.286-76-54.857-94.857-1.714-206.286-148-252-245.143-282.857-90.857-28.571-120.571-42.286-120.571-97.714v-14.857c32.571-18.857 54.857-54.286 54.857-94.857 0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714c0 40.571 22.286 76 54.857 94.857v468.571c-32.571 18.857-54.857 54.286-54.857 94.857 0 60.571 49.143 109.714 109.714 109.714s109.714-49.143 109.714-109.714c0-40.571-22.286-76-54.857-94.857v-284c29.143 14.286 60 24 88 32.571 106.286 33.714 166.857 58.857 168 178.286-32.571 18.857-54.857 54.286-54.857 94.857 0 60.571 49.143 109.714 109.714 109.714s109.714-49.143 109.714-109.714z" />
<glyph unicode="&#xf127;" glyph-name="chain-broken, unlink" horiz-adv-x="951" d="M250.857 224.571l-146.286-146.286c-4-3.429-8.571-5.143-13.143-5.143s-9.143 1.714-13.143 5.143c-6.857 7.429-6.857 18.857 0 26.286l146.286 146.286c7.429 6.857 18.857 6.857 26.286 0 6.857-7.429 6.857-18.857 0-26.286zM347.429 201.143v-182.857c0-10.286-8-18.286-18.286-18.286s-18.286 8-18.286 18.286v182.857c0 10.286 8 18.286 18.286 18.286s18.286-8 18.286-18.286zM219.429 329.143c0-10.286-8-18.286-18.286-18.286h-182.857c-10.286 0-18.286 8-18.286 18.286s8 18.286 18.286 18.286h182.857c10.286 0 18.286-8 18.286-18.286zM941.714 256c0-44-17.143-85.143-48.571-116l-84-83.429c-30.857-30.857-72-47.429-116-47.429s-85.714 17.143-116.571 48.571l-190.857 191.429c-9.714 9.714-17.143 20.571-24 32l136.571 10.286 156-156.571c20.571-20.571 57.143-21.143 77.714-0.571l84 83.429c10.286 10.286 16 24 16 38.286 0 14.857-5.714 28.571-16 38.857l-156.571 157.143 10.286 136.571c11.429-6.857 22.286-14.286 32-24l192-192c30.857-31.429 48-72.571 48-116.571zM589.143 669.714l-136.571-10.286-156 156.571c-10.286 10.286-24 16-38.857 16s-28.571-5.714-38.857-15.429l-84-83.429c-10.286-10.286-16-24-16-38.286 0-14.857 5.714-28.571 16-38.857l156.571-156.571-10.286-137.143c-11.429 6.857-22.286 14.286-32 24l-192 192c-30.857 31.429-48 72.571-48 116.571s17.143 85.143 48.571 116l84 83.429c30.857 30.857 72 47.429 116 47.429s85.714-17.143 116.571-48.571l190.857-191.429c9.714-9.714 17.143-20.571 24-32zM950.857 621.714c0-10.286-8-18.286-18.286-18.286h-182.857c-10.286 0-18.286 8-18.286 18.286s8 18.286 18.286 18.286h182.857c10.286 0 18.286-8 18.286-18.286zM640 932.571v-182.857c0-10.286-8-18.286-18.286-18.286s-18.286 8-18.286 18.286v182.857c0 10.286 8 18.286 18.286 18.286s18.286-8 18.286-18.286zM872.571 846.286l-146.286-146.286c-4-3.429-8.571-5.143-13.143-5.143s-9.143 1.714-13.143 5.143c-6.857 7.429-6.857 18.857 0 26.286l146.286 146.286c7.429 6.857 18.857 6.857 26.286 0 6.857-7.429 6.857-18.857 0-26.286z" />
<glyph unicode="&#xf128;" glyph-name="question" horiz-adv-x="635" d="M402.286 233.143v-137.143c0-12.571-10.286-22.857-22.857-22.857h-137.143c-12.571 0-22.857 10.286-22.857 22.857v137.143c0 12.571 10.286 22.857 22.857 22.857h137.143c12.571 0 22.857-10.286 22.857-22.857zM582.857 576c0-108.571-73.714-150.286-128-180.571-33.714-19.429-54.857-58.857-54.857-75.429v0c0-12.571-9.714-27.429-22.857-27.429h-137.143c-12.571 0-20.571 19.429-20.571 32v25.714c0 69.143 68.571 128.571 118.857 151.429 44 20 62.286 38.857 62.286 75.429 0 32-41.714 60.571-88 60.571-25.714 0-49.143-8-61.714-16.571-13.714-9.714-27.429-23.429-61.143-65.714-4.571-5.714-11.429-9.143-17.714-9.143-5.143 0-9.714 1.714-14.286 4.571l-93.714 71.429c-9.714 7.429-12 20-5.714 30.286 61.714 102.286 148.571 152 265.143 152 122.286 0 259.429-97.714 259.429-228.571z" />
<glyph unicode="&#xf129;" glyph-name="info" d="M511.999-9.143c-88.606 0-175.222 26.276-248.895 75.5-73.673 49.229-131.095 119.199-165.002 201.057-33.908 81.864-42.78 171.94-25.494 258.844s59.954 166.729 122.608 229.383c62.654 62.654 142.48 105.322 229.383 122.608s176.98 8.414 258.844-25.494c81.859-33.908 151.828-91.329 201.057-165.002 49.224-73.673 75.5-160.29 75.5-248.895 0-118.815-47.201-232.765-131.215-316.785-84.019-84.014-197.97-131.215-316.785-131.215zM511.999 822.857c-75.948 0-150.19-22.521-213.339-64.716s-112.367-102.167-141.431-172.334c-29.064-70.167-36.668-147.375-21.852-221.866 14.817-74.486 51.389-142.909 105.093-196.613s122.126-90.276 196.615-105.093c74.488-14.817 151.699-7.214 221.863 21.852 70.17 29.066 130.14 78.285 172.334 141.43 42.194 63.15 64.717 137.39 64.717 213.34 0 101.843-40.458 199.515-112.471 271.529s-169.687 112.471-271.529 112.471zM495.999 598.857c26.511 0 48 21.49 48 48s-21.489 48-48 48c-26.51 0-48-21.49-48-48s21.49-48 48-48zM671.999 246.857c0-8.489-3.369-16.625-9.375-22.625-6.001-6.006-14.136-9.375-22.625-9.375h-256c-8.487 0-16.626 3.369-22.627 9.375-6.001 6.001-9.373 14.136-9.373 22.625s3.372 16.625 9.373 22.625c6.001 6.006 14.14 9.375 22.627 9.375h96v192h-64c-8.487 0-16.626 3.372-22.627 9.373s-9.373 14.14-9.373 22.627c0 8.487 3.372 16.626 9.373 22.627s14.14 9.373 22.627 9.373h96c8.489 0 16.625-3.372 22.625-9.373 6.006-6.001 9.375-14.14 9.375-22.627v-224h96c8.489 0 16.625-3.369 22.625-9.375 6.006-6.001 9.375-14.136 9.375-22.625z" />
<glyph unicode="&#xf12a;" glyph-name="exclamation" d="M917.119 6.517h-812.798c-5.469 0.020-10.84 1.444-15.602 4.137-4.762 2.688-8.755 6.554-11.598 11.223-2.809 4.864-4.287 10.383-4.287 16s1.479 11.136 4.287 16l406.4 799.999c2.685 5.242 6.765 9.64 11.79 12.712s10.8 4.697 16.688 4.697c5.893 0 11.668-1.625 16.691-4.697 5.028-3.072 9.103-7.471 11.791-12.712l406.4-799.999c2.806-4.864 4.285-10.383 4.285-16s-1.48-11.136-4.285-16c-3.057-5.059-7.46-9.175-12.713-11.884-5.253-2.714-11.151-3.917-17.050-3.476zM156.481 70.517h708.481l-354.243 697.279-354.238-697.279zM510.709 134.517c26.511 0 48 21.489 48 48s-21.489 48-48 48c-26.51 0-48-21.489-48-48s21.49-48 48-48zM510.709 294.517c-8.487 0-16.626 3.369-22.627 9.375-6.001 6.001-9.373 14.136-9.373 22.625v224c0 8.487 3.372 16.626 9.373 22.627s14.14 9.373 22.627 9.373c8.489 0 16.625-3.372 22.625-9.373 6.006-6.001 9.375-14.14 9.375-22.627v-224c0-8.489-3.369-16.625-9.375-22.625-6.001-6.006-14.136-9.375-22.625-9.375z" />
<glyph unicode="&#xf135;" glyph-name="rocket" horiz-adv-x="967" d="M822.857 694.857c0 30.286-24.571 54.857-54.857 54.857s-54.857-24.571-54.857-54.857 24.571-54.857 54.857-54.857 54.857 24.571 54.857 54.857zM950.857 859.428c0-189.714-52.571-316-188-452-33.143-32.571-70.857-66.286-111.429-100.571l-11.429-216.571c-0.571-5.714-4-11.429-9.143-14.857l-219.429-128c-2.857-1.714-5.714-2.286-9.143-2.286-4.571 0-9.143 1.714-13.143 5.143l-36.571 36.571c-4.571 5.143-6.286 12-4.571 18.286l48.571 157.714-160.571 160.571-157.714-48.571c-1.714-0.571-3.429-0.571-5.143-0.571-4.571 0-9.714 1.714-13.143 5.143l-36.571 36.571c-5.714 6.286-6.857 15.429-2.857 22.286l128 219.429c3.429 5.143 9.143 8.571 14.857 9.143l216.571 11.429c34.286 40.571 68 78.286 100.571 111.429 142.857 142.286 252 188 450.857 188 10.286 0 19.429-8 19.429-18.286z" />
<glyph unicode="&#xf136;" glyph-name="maxcdn" horiz-adv-x="1013" d="M997.143 509.143l-93.714-436h-190.857l101.714 475.429c4.571 20 1.714 38.286-8.571 50.286-9.714 12-26.857 18.857-47.429 18.857h-96.571l-116.571-544.571h-190.857l116.571 544.571h-163.429l-116.571-544.571h-190.857l116.571 544.571-87.429 186.857h729.143c77.143 0 147.429-32 192.571-88 45.714-56 62.286-132 46.286-207.429z" />
<glyph unicode="&#xf137;" glyph-name="chevron-circle-left" horiz-adv-x="878" d="M519.429 153.714l58.286 58.286c14.286 14.286 14.286 37.143 0 51.429l-175.429 175.429 175.429 175.429c14.286 14.286 14.286 37.143 0 51.429l-58.286 58.286c-14.286 14.286-37.143 14.286-51.429 0l-259.429-259.429c-14.286-14.286-14.286-37.143 0-51.429l259.429-259.429c14.286-14.286 37.143-14.286 51.429 0zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf138;" glyph-name="chevron-circle-right" horiz-adv-x="878" d="M409.714 153.714l259.429 259.429c14.286 14.286 14.286 37.143 0 51.429l-259.429 259.429c-14.286 14.286-37.143 14.286-51.429 0l-58.286-58.286c-14.286-14.286-14.286-37.143 0-51.429l175.429-175.429-175.429-175.429c-14.286-14.286-14.286-37.143 0-51.429l58.286-58.286c14.286-14.286 37.143-14.286 51.429 0zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf139;" glyph-name="chevron-circle-up" horiz-adv-x="878" d="M665.714 300l58.286 58.286c14.286 14.286 14.286 37.143 0 51.429l-259.429 259.429c-14.286 14.286-37.143 14.286-51.429 0l-259.429-259.429c-14.286-14.286-14.286-37.143 0-51.429l58.286-58.286c14.286-14.286 37.143-14.286 51.429 0l175.429 175.429 175.429-175.429c14.286-14.286 37.143-14.286 51.429 0zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf13a;" glyph-name="chevron-circle-down" horiz-adv-x="878" d="M464.571 208.571l259.429 259.429c14.286 14.286 14.286 37.143 0 51.429l-58.286 58.286c-14.286 14.286-37.143 14.286-51.429 0l-175.429-175.429-175.429 175.429c-14.286 14.286-37.143 14.286-51.429 0l-58.286-58.286c-14.286-14.286-14.286-37.143 0-51.429l259.429-259.429c14.286-14.286 37.143-14.286 51.429 0zM877.714 438.857c0-242.286-196.571-438.857-438.857-438.857s-438.857 196.571-438.857 438.857 196.571 438.857 438.857 438.857 438.857-196.571 438.857-438.857z" />
<glyph unicode="&#xf141;" glyph-name="ellipsis-h" horiz-adv-x="805" d="M219.429 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-109.714c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h109.714c30.286 0 54.857-24.571 54.857-54.857zM512 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-109.714c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h109.714c30.286 0 54.857-24.571 54.857-54.857zM804.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-109.714c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h109.714c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf175;" glyph-name="long-arrow-down" horiz-adv-x="439" d="M437.143 208.571c2.857-6.857 1.714-14.286-2.857-20l-200-219.429c-3.429-3.429-8-5.714-13.143-5.714v0c-5.143 0-10.286 2.286-13.714 5.714l-202.857 219.429c-4.571 5.714-5.714 13.143-2.857 20 2.857 6.286 9.143 10.857 16.571 10.857h128v713.143c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286v-713.143h128c7.429 0 13.714-4 16.571-10.857z" />
<glyph unicode="&#xf176;" glyph-name="long-arrow-up" horiz-adv-x="439" d="M437.143 669.143c-2.857-6.286-9.143-10.857-16.571-10.857h-128v-713.143c0-10.286-8-18.286-18.286-18.286h-109.714c-10.286 0-18.286 8-18.286 18.286v713.143h-128c-7.429 0-13.714 4-16.571 10.857s-1.714 14.286 2.857 20l200 219.429c3.429 3.429 8 5.714 13.143 5.714v0c5.143 0 10.286-2.286 13.714-5.714l202.857-219.429c4.571-5.714 5.714-13.143 2.857-20z" />
<glyph unicode="&#xf177;" glyph-name="long-arrow-left" horiz-adv-x="1061" d="M1024 493.714v-109.714c0-10.286-8-18.286-18.286-18.286h-713.143v-128c0-7.429-4-13.714-10.857-16.571s-14.286-1.714-20 2.857l-219.429 200c-3.429 3.429-5.714 8-5.714 13.143v0c0 5.143 2.286 10.286 5.714 13.714l219.429 202.286c5.714 5.143 13.143 6.286 20 3.429 6.286-2.857 10.857-9.143 10.857-16.571v-128h713.143c10.286 0 18.286-8 18.286-18.286z" />
<glyph unicode="&#xf178;" glyph-name="long-arrow-right" horiz-adv-x="987" d="M987.429 440.571c0-5.143-2.286-10.286-5.714-13.714l-219.429-202.286c-5.714-5.143-13.143-6.286-20-3.429-6.286 2.857-10.857 9.143-10.857 16.571v128h-713.143c-10.286 0-18.286 8-18.286 18.286v109.714c0 10.286 8 18.286 18.286 18.286h713.143v128c0 7.429 4 13.714 10.857 16.571s14.286 1.714 20-2.857l219.429-200c3.429-3.429 5.714-8 5.714-13.143v0z" />
<glyph unicode="&#xf2db;" glyph-name="microchip" horiz-adv-x="878" d="M109.714 219.428v-73.143h-64c-5.143 0-9.143 4-9.143 9.143v9.143h-27.429c-5.143 0-9.143 4-9.143 9.143v18.286c0 5.143 4 9.143 9.143 9.143h27.429v9.143c0 5.143 4 9.143 9.143 9.143h64zM109.714 365.714v-73.143h-64c-5.143 0-9.143 4-9.143 9.143v9.143h-27.429c-5.143 0-9.143 4-9.143 9.143v18.286c0 5.143 4 9.143 9.143 9.143h27.429v9.143c0 5.143 4 9.143 9.143 9.143h64zM109.714 512v-73.143h-64c-5.143 0-9.143 4-9.143 9.143v9.143h-27.429c-5.143 0-9.143 4-9.143 9.143v18.286c0 5.143 4 9.143 9.143 9.143h27.429v9.143c0 5.143 4 9.143 9.143 9.143h64zM109.714 658.286v-73.143h-64c-5.143 0-9.143 4-9.143 9.143v9.143h-27.429c-5.143 0-9.143 4-9.143 9.143v18.286c0 5.143 4 9.143 9.143 9.143h27.429v9.143c0 5.143 4 9.143 9.143 9.143h64zM109.714 804.571v-73.143h-64c-5.143 0-9.143 4-9.143 9.143v9.143h-27.429c-5.143 0-9.143 4-9.143 9.143v18.286c0 5.143 4 9.143 9.143 9.143h27.429v9.143c0 5.143 4 9.143 9.143 9.143h64zM731.429 896v-841.143c0-30.286-24.571-54.857-54.857-54.857h-475.429c-30.286 0-54.857 24.571-54.857 54.857v841.143c0 30.286 24.571 54.857 54.857 54.857h475.429c30.286 0 54.857-24.571 54.857-54.857zM877.714 192v-18.286c0-5.143-4-9.143-9.143-9.143h-27.429v-9.143c0-5.143-4-9.143-9.143-9.143h-64v73.143h64c5.143 0 9.143-4 9.143-9.143v-9.143h27.429c5.143 0 9.143-4 9.143-9.143zM877.714 338.286v-18.286c0-5.143-4-9.143-9.143-9.143h-27.429v-9.143c0-5.143-4-9.143-9.143-9.143h-64v73.143h64c5.143 0 9.143-4 9.143-9.143v-9.143h27.429c5.143 0 9.143-4 9.143-9.143zM877.714 484.571v-18.286c0-5.143-4-9.143-9.143-9.143h-27.429v-9.143c0-5.143-4-9.143-9.143-9.143h-64v73.143h64c5.143 0 9.143-4 9.143-9.143v-9.143h27.429c5.143 0 9.143-4 9.143-9.143zM877.714 630.857v-18.286c0-5.143-4-9.143-9.143-9.143h-27.429v-9.143c0-5.143-4-9.143-9.143-9.143h-64v73.143h64c5.143 0 9.143-4 9.143-9.143v-9.143h27.429c5.143 0 9.143-4 9.143-9.143zM877.714 777.143v-18.286c0-5.143-4-9.143-9.143-9.143h-27.429v-9.143c0-5.143-4-9.143-9.143-9.143h-64v73.143h64c5.143 0 9.143-4 9.143-9.143v-9.143h27.429c5.143 0 9.143-4 9.143-9.143z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 148 KiB

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