From 5916bf314d56e1ec615006e2d06cfb243b606eca Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 22 Jan 2020 23:18:22 -0800 Subject: [PATCH 1/4] initial commit --- .prettierrc.yaml | 5 + .pylintrc | 586 ++ package.json | 12 +- src/adafruit_circuitplayground/__init__.py | 4 +- src/adafruit_circuitplayground/constants.py | 39 +- .../debugger_communication_client.py | 14 +- src/adafruit_circuitplayground/express.py | 79 +- src/adafruit_circuitplayground/pixel.py | 29 +- src/adafruit_circuitplayground/telemetry.py | 19 +- .../test/test_express.py | 59 +- src/adafruit_circuitplayground/utils.py | 9 +- src/constants.ts | 673 +- src/cpxWorkspace.ts | 8 +- src/debug_user_code.py | 14 +- src/debuggerCommunicationServer.ts | 136 +- src/device.py | 37 +- src/deviceContext.ts | 69 +- src/extension.ts | 1656 ++--- src/extension_utils/dependencyChecker.ts | 37 +- src/extension_utils/utils.ts | 502 +- src/process_user_code.py | 27 +- src/python_constants.py | 8 +- src/requirements.txt | 1 + src/serialMonitor.ts | 148 +- src/serialPortControl.ts | 135 +- src/simulatorDebugConfigurationProvider.ts | 168 +- src/telemetry/getPackageInfo.ts | 77 +- src/telemetry/telemetryAI.ts | 132 +- src/test/runTest.ts | 30 +- src/test/suite/extension.test.ts | 7 +- src/test/suite/index.ts | 14 +- src/test_code/code.py | 1 - src/test_code/control.py | 30 +- src/usbDetector.ts | 33 +- src/view/App.css | 22 +- src/view/App.tsx | 20 +- src/view/components/Button.tsx | 44 +- src/view/components/Dropdown.tsx | 78 +- src/view/components/Simulator.tsx | 655 +- .../components/cpx/Accessibility_utils.ts | 26 +- src/view/components/cpx/CpxImage.tsx | 626 +- src/view/components/cpx/Cpx_svg.tsx | 5579 +++++++++-------- src/view/components/cpx/Cpx_svg_style.tsx | 4 +- src/view/components/cpx/Svg_utils.tsx | 108 +- src/view/components/simulator/ActionBar.tsx | 56 +- src/view/components/toolbar/InputSlider.tsx | 237 +- .../components/toolbar/LightSensorBar.tsx | 79 +- .../components/toolbar/MotionSensorBar.tsx | 288 +- src/view/components/toolbar/SensorButton.tsx | 24 +- .../components/toolbar/SensorModalUtils.tsx | 296 +- .../toolbar/TemperatureSensorBar.tsx | 86 +- src/view/components/toolbar/ToolBar.tsx | 250 +- src/view/constants.ts | 74 +- src/view/container/device/Device.tsx | 88 +- src/view/index.css | 7 +- src/view/index.tsx | 10 +- src/view/styles/Button.css | 34 +- src/view/styles/Dropdown.css | 34 +- src/view/styles/InputSlider.css | 116 +- src/view/styles/LightSensorBar.css | 24 +- src/view/styles/MotionSensorBar.css | 16 +- src/view/styles/SensorButton.css | 36 +- src/view/styles/Simulator.css | 24 +- src/view/styles/TemperatureSensorBar.css | 22 +- src/view/styles/ToolBar.css | 152 +- src/view/svgs/arrow_right_svg.tsx | 28 +- src/view/svgs/close_svg.tsx | 40 +- src/view/svgs/play_svg.tsx | 26 +- src/view/svgs/refresh_svg.tsx | 28 +- src/view/svgs/stop_svg.tsx | 24 +- src/view/svgs/tag_input_svg.tsx | 42 +- src/view/svgs/tag_output_svg.tsx | 33 +- src/view/svgs/toolbar_svg.tsx | 804 +-- src/view/viewUtils.tsx | 30 +- tslint.json | 10 +- 75 files changed, 8074 insertions(+), 6904 deletions(-) create mode 100644 .prettierrc.yaml create mode 100644 .pylintrc diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 000000000..b8a8009c4 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,5 @@ +trailingComma: "es5" +tabWidth: 4 +semi: true +endOfLine: lf +printWidth: 80 \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..5a62b6ca1 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,586 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + missing-function-docstring, + missing-module-docstring, + too-many-public-methods, + missing-class-docstring, + invalid-name + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/package.json b/package.json index a95336b75..8e78adf6a 100644 --- a/package.json +++ b/package.json @@ -279,9 +279,15 @@ "test": "npm-run-all test:*", "test:extension-tests": "node ./out/test/runTest.js", "test:api-tests": "pytest src", - "lint": "tslint -c tslint.json src/**/*.{ts,tsx}", - "format": "prettier --write src/**/*.{ts,tsx}", - "tslint-check": "tslint-config-prettier-check ./tslint.json", + "lint": "npm-run-all lint:*", + "lint:ts": "tslint -c tslint.json src/**/*.{ts,tsx}", + "lint:python": "pylint src", + "format": "npm-run-all format:*", + "format:ts": "prettier --write src/**/*.{css,ts,tsx}", + "format:python": "black src", + "check": "npm-run-all check:*", + "check:ts": "tslint-config-prettier-check ./tslint.json", + "check:python": "black src --check", "package": "vsce package" }, "devDependencies": { diff --git a/src/adafruit_circuitplayground/__init__.py b/src/adafruit_circuitplayground/__init__.py index d85cad6b0..b4bc33da8 100644 --- a/src/adafruit_circuitplayground/__init__.py +++ b/src/adafruit_circuitplayground/__init__.py @@ -1,4 +1,4 @@ # added compatibility for new import structure in CircuitPython -# https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground/pull/79 +# https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground/pull/79 -from .express import cpx as cp \ No newline at end of file +from .express import cpx as cp diff --git a/src/adafruit_circuitplayground/constants.py b/src/adafruit_circuitplayground/constants.py index 4474dc46b..26642a710 100644 --- a/src/adafruit_circuitplayground/constants.py +++ b/src/adafruit_circuitplayground/constants.py @@ -1,32 +1,40 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. -ASSIGN_PIXEL_TYPE_ERROR = "The pixel color value type should be tuple, list or hexadecimal." +ASSIGN_PIXEL_TYPE_ERROR = ( + "The pixel color value type should be tuple, list or hexadecimal." +) BRIGHTNESS_RANGE_ERROR = "The brightness value should be a number between 0 and 1." -INDEX_ERROR = "The index is not a valid number, you can access the Neopixels from 0 to 9." +INDEX_ERROR = ( + "The index is not a valid number, you can access the Neopixels from 0 to 9." +) MAC_OS = "darwin" NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" -NOT_SUITABLE_FILE_ERROR = "Your .wav file is not suitable for the Circuit Playground Express." +NOT_SUITABLE_FILE_ERROR = ( + "Your .wav file is not suitable for the Circuit Playground Express." +) -PIXEL_RANGE_ERROR = "The pixel hexadicimal color value should be in range #000000 and #FFFFFF." +PIXEL_RANGE_ERROR = ( + "The pixel hexadicimal color value should be in range #000000 and #FFFFFF." +) VALID_PIXEL_ASSIGN_ERROR = "The pixel color value should be a tuple with three values between 0 and 255 or a hexadecimal color between 0x000000 and 0xFFFFFF." TELEMETRY_EVENT_NAMES = { - 'TAPPED': "API.TAPPED", - 'PLAY_FILE': "API.PLAY.FILE", - 'PLAY_TONE': "API.PLAY.TONE", - 'START_TONE': "API.START.TONE", - 'STOP_TONE': "API.STOP.TONE", - 'DETECT_TAPS': "API.DETECT.TAPS", - 'ADJUST_THRESHOLD': "API.ADJUST.THRESHOLD", - 'RED_LED': "API.RED.LED", - 'PIXELS': "API.PIXELS" + "TAPPED": "API.TAPPED", + "PLAY_FILE": "API.PLAY.FILE", + "PLAY_TONE": "API.PLAY.TONE", + "START_TONE": "API.START.TONE", + "STOP_TONE": "API.STOP.TONE", + "DETECT_TAPS": "API.DETECT.TAPS", + "ADJUST_THRESHOLD": "API.ADJUST.THRESHOLD", + "RED_LED": "API.RED.LED", + "PIXELS": "API.PIXELS", } ERROR_SENDING_EVENT = "Error trying to send event to the process : " @@ -35,6 +43,5 @@ DEFAULT_PORT = "5577" -EVENTS_BUTTON_PRESS = ['button_a', 'button_b', 'switch'] -EVENTS_SENSOR_CHANGED = ['temperature', - 'light', 'motion_x', 'motion_y', 'motion_z'] +EVENTS_BUTTON_PRESS = ["button_a", "button_b", "switch"] +EVENTS_SENSOR_CHANGED = ["temperature", "light", "motion_x", "motion_y", "motion_z"] diff --git a/src/adafruit_circuitplayground/debugger_communication_client.py b/src/adafruit_circuitplayground/debugger_communication_client.py index b2867c53d..32f02d273 100644 --- a/src/adafruit_circuitplayground/debugger_communication_client.py +++ b/src/adafruit_circuitplayground/debugger_communication_client.py @@ -16,7 +16,7 @@ # Initialize connection def init_connection(port=CONSTANTS.DEFAULT_PORT): - sio.connect('http://localhost:{}'.format(port)) + sio.connect("http://localhost:{}".format(port)) # Transfer the user's inputs to the API @@ -25,27 +25,27 @@ def __update_api_state(data, expected_events): event_state = json.loads(data) for event in expected_events: express.cpx._Express__state[event] = event_state.get( - event, express.cpx._Express__state[event]) + event, express.cpx._Express__state[event] + ) except Exception as e: - print(CONSTANTS.ERROR_SENDING_EVENT, - e, file=sys.stderr, flush=True) + print(CONSTANTS.ERROR_SENDING_EVENT, e, file=sys.stderr, flush=True) # Method : Update State def update_state(state): - sio.emit('updateState', state) + sio.emit("updateState", state) ## Events Handlers ## # Event : Button pressed (A, B, A+B, Switch) -@sio.on('button_press') +@sio.on("button_press") def button_press(data): __update_api_state(data, CONSTANTS.EVENTS_BUTTON_PRESS) # Event : Sensor changed (Temperature, light, Motion) -@sio.on('sensor_changed') +@sio.on("sensor_changed") def button_press(data): __update_api_state(data, CONSTANTS.EVENTS_SENSOR_CHANGED) diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index ad40be2ec..99507a691 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -12,17 +12,18 @@ from applicationinsights import TelemetryClient from .telemetry import telemetry_py -Acceleration = namedtuple('acceleration', ['x', 'y', 'z']) +Acceleration = namedtuple("acceleration", ["x", "y", "z"]) class Express: def __init__(self): # State in the Python process self.__state = { - 'brightness': 1.0, - 'button_a': False, - 'button_b': False, - 'pixels': [ + "brightness": 1.0, + "button_a": False, + "button_b": False, + "pixels": [ + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), @@ -32,44 +33,46 @@ def __init__(self): (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0) ], - 'red_led': False, - 'switch': False, - 'temperature': 0, - 'light': 0, - 'motion_x': 0, - 'motion_y': 0, - 'motion_z': 0, - 'touch': [False]*7, - 'shake': False, + "red_led": False, + "switch": False, + "temperature": 0, + "light": 0, + "motion_x": 0, + "motion_y": 0, + "motion_z": 0, + "touch": [False] * 7, + "shake": False, } self.__debug_mode = False - self.__abs_path_to_code_file = '' + self.__abs_path_to_code_file = "" self.pixels = Pixel(self.__state, self.__debug_mode) @property def acceleration(self): - return Acceleration(self.__state['motion_x'], self.__state['motion_y'], self.__state['motion_z']) + return Acceleration( + self.__state["motion_x"], self.__state["motion_y"], self.__state["motion_z"] + ) @property def button_a(self): - return self.__state['button_a'] + return self.__state["button_a"] @property def button_b(self): - return self.__state['button_b'] + return self.__state["button_b"] @property def detect_taps(self): telemetry_py.send_telemetry("DETECT_TAPS") - return self.__state['detect_taps'] + return self.__state["detect_taps"] @detect_taps.setter def detect_taps(self, value): value_int = int(value) - self.__state['detect_taps'] = value_int if ( - value_int == 1 or value_int == 2) else 1 + self.__state["detect_taps"] = ( + value_int if (value_int == 1 or value_int == 2) else 1 + ) @property def tapped(self): @@ -81,31 +84,31 @@ def tapped(self): @property def red_led(self): telemetry_py.send_telemetry("RED_LED") - return self.__state['red_led'] + return self.__state["red_led"] @red_led.setter def red_led(self, value): telemetry_py.send_telemetry("RED_LED") - self.__state['red_led'] = bool(value) + self.__state["red_led"] = bool(value) self.__show() @property def switch(self): - return self.__state['switch'] + return self.__state["switch"] @property def temperature(self): - return self.__state['temperature'] + return self.__state["temperature"] @property def light(self): - return self.__state['light'] + return self.__state["light"] def __show(self): utils.show(self.__state, self.__debug_mode) def __touch(self, i): - return self.__state['touch'][i-1] + return self.__state["touch"][i - 1] @property def touch_A1(self): @@ -140,19 +143,20 @@ def adjust_touch_threshold(self, adjustement): The CPX Simulator doesn't use capacitive touch threshold. """ telemetry_py.send_telemetry("ADJUST_THRESHOLD") - raise NotImplementedError( - CONSTANTS.NOT_IMPLEMENTED_ERROR) + raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def shake(self, shake_threshold=30): - return self.__state['shake'] + return self.__state["shake"] def play_file(self, file_name): telemetry_py.send_telemetry("PLAY_FILE") file_name = utils.remove_leading_slashes(file_name) abs_path_parent_dir = os.path.abspath( - os.path.join(self.__abs_path_to_code_file, os.pardir)) + os.path.join(self.__abs_path_to_code_file, os.pardir) + ) abs_path_wav_file = os.path.normpath( - os.path.join(abs_path_parent_dir, file_name)) + os.path.join(abs_path_parent_dir, file_name) + ) abs_path_wav_file = utils.escape_if_OSX(abs_path_wav_file) if sys.implementation.version[0] >= 3: @@ -171,22 +175,19 @@ def play_tone(self, frequency, duration): """ Not Implemented! """ telemetry_py.send_telemetry("PLAY_TONE") - raise NotImplementedError( - CONSTANTS.NOT_IMPLEMENTED_ERROR) + raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def start_tone(self, frequency): """ Not Implemented! """ telemetry_py.send_telemetry("START_TONE") - raise NotImplementedError( - CONSTANTS.NOT_IMPLEMENTED_ERROR) + raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def stop_tone(self): """ Not Implemented! """ telemetry_py.send_telemetry("STOP_TONE") - raise NotImplementedError( - CONSTANTS.NOT_IMPLEMENTED_ERROR) + raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) cpx = Express() diff --git a/src/adafruit_circuitplayground/pixel.py b/src/adafruit_circuitplayground/pixel.py index 530dbd48b..3833de2f7 100644 --- a/src/adafruit_circuitplayground/pixel.py +++ b/src/adafruit_circuitplayground/pixel.py @@ -33,7 +33,7 @@ def __getitem__(self, index): if not self.__valid_index(index): raise IndexError(CONSTANTS.INDEX_ERROR) telemetry_py.send_telemetry("PIXELS") - return self.__state['pixels'][index] + return self.__state["pixels"][index] def __setitem__(self, index, val): telemetry_py.send_telemetry("PIXELS") @@ -43,8 +43,7 @@ def __setitem__(self, index, val): else: if not self.__valid_index(index): raise IndexError(CONSTANTS.INDEX_ERROR) - self.__state['pixels'][index] = self.__extract_pixel_value( - val, is_slice) + self.__state["pixels"][index] = self.__extract_pixel_value(val, is_slice) self.__show_if_auto_write() def __iter__(self): @@ -60,11 +59,15 @@ def __len__(self): return len(self.__state["pixels"]) def __valid_index(self, index): - return type(index) is int and index >= -len(self.__state['pixels']) and index < len(self.__state['pixels']) + return ( + type(index) is int + and index >= -len(self.__state["pixels"]) + and index < len(self.__state["pixels"]) + ) def fill(self, val): - for index in range(len(self.__state['pixels'])): - self.__state['pixels'][index] = self.__extract_pixel_value(val) + for index in range(len(self.__state["pixels"])): + self.__state["pixels"][index] = self.__extract_pixel_value(val) self.__show_if_auto_write() def __extract_pixel_value(self, val, is_slice=False): @@ -83,13 +86,15 @@ def __extract_pixel_value(self, val, is_slice=False): else: raise ValueError(CONSTANTS.ASSIGN_PIXEL_TYPE_ERROR) # Values validation - if len(rgb_value) != 3 or any(not self.__valid_rgb_value(pix) for pix in rgb_value): + if len(rgb_value) != 3 or any( + not self.__valid_rgb_value(pix) for pix in rgb_value + ): raise ValueError(CONSTANTS.VALID_PIXEL_ASSIGN_ERROR) extracted_values.append(rgb_value) return rgb_value if not is_slice else extracted_values def __hex_to_rgb(self, hexValue): - if hexValue[0:2] == '0x' and len(hexValue) <= 8: + if hexValue[0:2] == "0x" and len(hexValue) <= 8: hexToRgbValue = [0, 0, 0] hexColor = hexValue[2:].zfill(6) hexToRgbValue[0] = int(hexColor[0:2], 16) # R @@ -104,14 +109,16 @@ def __valid_rgb_value(self, pixValue): @property def brightness(self): - return self.__state['brightness'] + return self.__state["brightness"] @brightness.setter def brightness(self, brightness): if not self.__valid_brightness(brightness): raise ValueError(CONSTANTS.BRIGHTNESS_RANGE_ERROR) - self.__state['brightness'] = brightness + self.__state["brightness"] = brightness self.__show_if_auto_write() def __valid_brightness(self, brightness): - return (type(brightness) is float or type(brightness) is int) and (brightness >= 0 and brightness <= 1) + return (type(brightness) is float or type(brightness) is int) and ( + brightness >= 0 and brightness <= 1 + ) diff --git a/src/adafruit_circuitplayground/telemetry.py b/src/adafruit_circuitplayground/telemetry.py index 98156469c..9fd1edb1c 100644 --- a/src/adafruit_circuitplayground/telemetry.py +++ b/src/adafruit_circuitplayground/telemetry.py @@ -15,20 +15,27 @@ def __init__(self): "PLAY_TONE": False, "START_TONE": False, "STOP_TONE": False, - "PIXELS": False + "PIXELS": False, } - self.telemetry_client = TelemetryClient('__AIKEY__') - self.extension_name = '__EXTENSIONNAME__' + self.telemetry_client = TelemetryClient("__AIKEY__") + self.extension_name = "__EXTENSIONNAME__" def send_telemetry(self, event_name): - if self.__enable_telemetry and self.telemetry_available() and not self.telemetry_state[event_name]: + if ( + self.__enable_telemetry + and self.telemetry_available() + and not self.telemetry_state[event_name] + ): self.telemetry_client.track_event( - '{}/{}'.format(self.extension_name, CONSTANTS.TELEMETRY_EVENT_NAMES[event_name])) + "{}/{}".format( + self.extension_name, CONSTANTS.TELEMETRY_EVENT_NAMES[event_name] + ) + ) self.telemetry_client.flush() self.telemetry_state[event_name] = True def telemetry_available(self): - return self.telemetry_client.context.instrumentation_key != '__AIKEY__' + return self.telemetry_client.context.instrumentation_key != "__AIKEY__" telemetry_py = Telemetry() diff --git a/src/adafruit_circuitplayground/test/test_express.py b/src/adafruit_circuitplayground/test/test_express.py index a360aa4fb..07d96acdd 100644 --- a/src/adafruit_circuitplayground/test/test_express.py +++ b/src/adafruit_circuitplayground/test/test_express.py @@ -5,30 +5,29 @@ class TestExpress(object): - def setup_method(self): self.cpx = Express() self.__state = { - 'brightness': 1.0, - 'button_a': False, - 'button_b': False, - 'pixels': [(255, 0, 0)] * 10, - 'red_led': False, - 'switch': False + "brightness": 1.0, + "button_a": False, + "button_b": False, + "pixels": [(255, 0, 0)] * 10, + "red_led": False, + "switch": False, } self.pixels = Pixel(self.__state) self.__speaker_enabled = False def test_button_a(self): - self.cpx._Express__state['button_a'] = True + self.cpx._Express__state["button_a"] = True assert True == self.cpx.button_a def test_button_b(self): - self.cpx._Express__state['button_b'] = True + self.cpx._Express__state["button_b"] = True assert True == self.cpx.button_b def test_red_led(self): - self.cpx._Express__state['red_led'] = True + self.cpx._Express__state["red_led"] = True assert True == self.cpx.red_led def test_red_led_int(self): @@ -36,83 +35,83 @@ def test_red_led_int(self): assert True == self.cpx.red_led def test_red_led_string(self): - self.cpx.red_led = 'foo' + self.cpx.red_led = "foo" assert True == self.cpx.red_led def test_switch(self): - self.cpx._Express__state['switch'] = True + self.cpx._Express__state["switch"] = True assert True == self.cpx.switch def test_set_item_tuple(self): self.cpx.pixels[0] = (255, 255, 255) - assert (255, 255, 255) == self.cpx._Express__state['pixels'][0] + assert (255, 255, 255) == self.cpx._Express__state["pixels"][0] def test_set_item_list(self): self.cpx.pixels[0] = [255, 255, 255] - assert (255, 255, 255) == self.cpx._Express__state['pixels'][0] + assert (255, 255, 255) == self.cpx._Express__state["pixels"][0] def test_set_item_hex(self): self.cpx.pixels[0] = 0xFFFFFF - assert (255, 255, 255) == self.cpx._Express__state['pixels'][0] + assert (255, 255, 255) == self.cpx._Express__state["pixels"][0] def test_set_item_invalid(self): with pytest.raises(ValueError): self.cpx.pixels[0] = "hello" def test_shake(self): - self.cpx._Express__state['shake'] = True + self.cpx._Express__state["shake"] = True assert True == self.cpx.shake() def test_touch_A1(self): - self.cpx._Express__state['touch'][0] = True + self.cpx._Express__state["touch"][0] = True assert True == self.cpx.touch_A1 def test_touch_A2(self): - self.cpx._Express__state['touch'][1] = True + self.cpx._Express__state["touch"][1] = True assert True == self.cpx.touch_A2 def test_touch_A3(self): - self.cpx._Express__state['touch'][2] = True + self.cpx._Express__state["touch"][2] = True assert True == self.cpx.touch_A3 - + def test_touch_A4(self): - self.cpx._Express__state['touch'][3] = True + self.cpx._Express__state["touch"][3] = True assert True == self.cpx.touch_A4 def test_touch_A5(self): - self.cpx._Express__state['touch'][4] = True + self.cpx._Express__state["touch"][4] = True assert True == self.cpx.touch_A5 - + def test_touch_A6(self): - self.cpx._Express__state['touch'][5] = True + self.cpx._Express__state["touch"][5] = True assert True == self.cpx.touch_A6 def test_touch_A7(self): - self.cpx._Express__state['touch'][6] = True + self.cpx._Express__state["touch"][6] = True assert True == self.cpx.touch_A7 def test_play_file_mp4(self): with pytest.raises(TypeError): - self.cpx.play_file('sample.mp4') + self.cpx.play_file("sample.mp4") def test_fill(self): self.cpx.pixels.fill((0, 255, 0)) expected_pixels = [(0, 255, 0)] * 10 - assert expected_pixels == self.cpx._Express__state['pixels'] + assert expected_pixels == self.cpx._Express__state["pixels"] def test_extract_pixel_value_list(self): assert (0, 255, 0) == self.cpx.pixels._Pixel__extract_pixel_value((0, 255, 0)) def test_extract_pixel_value_list1(self): assert (123, 123, 123) == self.cpx.pixels._Pixel__extract_pixel_value( - [123, 123, 123]) + [123, 123, 123] + ) def test_extract_pixel_value_int(self): assert (0, 0, 255) == self.cpx.pixels._Pixel__extract_pixel_value(255) def test_extract_pixel_value_tuple(self): - assert (0, 255, 0) == self.cpx.pixels._Pixel__extract_pixel_value( - (0, 255, 0)) + assert (0, 255, 0) == self.cpx.pixels._Pixel__extract_pixel_value((0, 255, 0)) def test_extract_pixel_value_invalid_length(self): with pytest.raises(ValueError): diff --git a/src/adafruit_circuitplayground/utils.py b/src/adafruit_circuitplayground/utils.py index 83a595d3b..14fddf227 100644 --- a/src/adafruit_circuitplayground/utils.py +++ b/src/adafruit_circuitplayground/utils.py @@ -16,21 +16,20 @@ def show(state, debug_mode=False): global previous_state if state != previous_state: previous_state = copy.deepcopy(state) - message = {'type': 'state', 'data': json.dumps(state)} + message = {"type": "state", "data": json.dumps(state)} if debug_mode: debugger_communication_client.update_state(json.dumps(message)) else: - print(json.dumps(message) + '\0', end='', - file=sys.__stdout__, flush=True) + print(json.dumps(message) + "\0", end="", file=sys.__stdout__, flush=True) time.sleep(CONSTANTS.TIME_DELAY) def remove_leading_slashes(string): - string = string.lstrip('\\/') + string = string.lstrip("\\/") return string def escape_if_OSX(file_name): if sys.platform.startswith(CONSTANTS.MAC_OS): file_name = file_name.replace(" ", "%20") - return file_name \ No newline at end of file + return file_name diff --git a/src/constants.ts b/src/constants.ts index 9618d5c02..0a2db62dc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,381 +7,394 @@ import * as nls from "vscode-nls"; // Debugger Server export const SERVER_INFO = { - DEFAULT_SERVER_PORT: 5577, - ERROR_CODE_INIT_SERVER: "ERROR_INIT_SERVER", - SERVER_PORT_CONFIGURATION: "deviceSimulatorExpress.debuggerServerPort" + DEFAULT_SERVER_PORT: 5577, + ERROR_CODE_INIT_SERVER: "ERROR_INIT_SERVER", + SERVER_PORT_CONFIGURATION: "deviceSimulatorExpress.debuggerServerPort", }; const localize: nls.LocalizeFunc = nls.config({ - messageFormat: nls.MessageFormat.file + messageFormat: nls.MessageFormat.file, })(); export const CONFIG = { - SHOW_DEPENDENCY_INSTALL: "deviceSimulatorExpress.showDependencyInstall", - SHOW_NEW_FILE_POPUP: "deviceSimulatorExpress.showNewFilePopup" + SHOW_DEPENDENCY_INSTALL: "deviceSimulatorExpress.showDependencyInstall", + SHOW_NEW_FILE_POPUP: "deviceSimulatorExpress.showNewFilePopup", }; export const CONSTANTS = { - DEBUG_CONFIGURATION_TYPE: "deviceSimulatorExpress", - DEPENDENCY_CHECKER: { - PIP3: "pip3", - PYTHON: "python", - PYTHON3: "python3.7", - }, - ERROR: { - COMPORT_UNKNOWN_ERROR: - "Writing to COM port (GetOverlappedResult): Unknown error code 121", - CPX_FILE_ERROR: localize( - "error.cpxFileFormat", - "The cpx.json file format is not correct." - ), - DEBUGGER_SERVER_INIT_FAILED: (port: number) => { - return localize( - "error.debuggerServerInitFailed", - `Warning : The Debugger Server cannot be opened. Please try to free the port ${port} if it's already in use or select another one in your Settings 'Device Simulator Express: Debugger Server Port' and start another debug session.\n You can still debug your code but you won't be able to use the Simulator.` - ); + DEBUG_CONFIGURATION_TYPE: "deviceSimulatorExpress", + DEPENDENCY_CHECKER: { + PIP3: "pip3", + PYTHON: "python", + PYTHON3: "python3.7", }, - DEBUGGING_SESSION_IN_PROGESS: localize( - "error.debuggingSessionInProgress", - "[ERROR] A debugging session is currently in progress, please stop it before running your code. \n" - ), - DEPENDENCY_DOWNLOAD_ERROR: - "Package downloads failed. Some functionalities may not work. Try restarting the simulator or review the installation docs.", - FAILED_TO_OPEN_SERIAL_PORT: (port: string): string => { - return localize( - "error.failedToOpenSerialPort", - `[ERROR] Failed to open serial port ${port}.` - ); + ERROR: { + COMPORT_UNKNOWN_ERROR: + "Writing to COM port (GetOverlappedResult): Unknown error code 121", + CPX_FILE_ERROR: localize( + "error.cpxFileFormat", + "The cpx.json file format is not correct." + ), + DEBUGGER_SERVER_INIT_FAILED: (port: number) => { + return localize( + "error.debuggerServerInitFailed", + `Warning : The Debugger Server cannot be opened. Please try to free the port ${port} if it's already in use or select another one in your Settings 'Device Simulator Express: Debugger Server Port' and start another debug session.\n You can still debug your code but you won't be able to use the Simulator.` + ); + }, + DEBUGGING_SESSION_IN_PROGESS: localize( + "error.debuggingSessionInProgress", + "[ERROR] A debugging session is currently in progress, please stop it before running your code. \n" + ), + DEPENDENCY_DOWNLOAD_ERROR: + "Package downloads failed. Some functionalities may not work. Try restarting the simulator or review the installation docs.", + FAILED_TO_OPEN_SERIAL_PORT: (port: string): string => { + return localize( + "error.failedToOpenSerialPort", + `[ERROR] Failed to open serial port ${port}.` + ); + }, + FAILED_TO_OPEN_SERIAL_PORT_DUE_TO: (port: string, error: any) => { + return localize( + "error.failedToOpenSerialPortDueTo", + `[ERROR] Failed to open serial port ${port} due to error: ${error}. \n` + ); + }, + INCORRECT_FILE_NAME_FOR_DEVICE: localize( + "error.incorrectFileNameForDevice", + '[ERROR] Can\'t deploy to your Circuit Playground Express device, please rename your file to "code.py" or "main.py". \n' + ), + INCORRECT_FILE_NAME_FOR_DEVICE_POPUP: localize( + "error.incorrectFileNameForDevicePopup", + 'Seems like you have a different file name than what CPX requires, please rename it to "code.py" or "main.py".' + ), + INVALID_FILE_EXTENSION_DEBUG: localize( + "error.invalidFileExtensionDebug", + "The file you tried to run isn't a Python file." + ), + NO_DEVICE: localize( + "error.noDevice", + "No plugged in boards detected. Please double check if your board is connected and/or properly formatted" + ), + NO_FILE_TO_RUN: localize( + "error.noFileToRun", + '[ERROR] We can\'t find a Python file to run. Please make sure you select or open a new ".py" code file, or use the "New File" command to get started and see useful links.\n' + ), + NO_FOLDER_OPENED: localize( + "error.noFolderCreated", + "In order to use the Serial Monitor, you need to open a folder and reload VS Code." + ), + NO_PROGRAM_FOUND_DEBUG: localize( + "error.noProgramFoundDebug", + "Cannot find a program to debug." + ), + NO_PYTHON_PATH: localize( + "error.noPythonPath", + "We found that you don't have Python 3 installed on your computer, please install the latest version, add it to your PATH and try again." + ), + RECONNECT_DEVICE: localize( + "error.reconnectDevice", + "Please disconnect your Circuit Playground Express and try again." + ), + STDERR: (data: string) => { + return localize("error.stderr", `\n[ERROR] ${data} \n`); + }, + UNEXPECTED_MESSAGE: localize( + "error.unexpectedMessage", + "Webview sent an unexpected message" + ), }, - FAILED_TO_OPEN_SERIAL_PORT_DUE_TO: (port: string, error: any) => { - return localize( - "error.failedToOpenSerialPortDueTo", - `[ERROR] Failed to open serial port ${port} due to error: ${error}. \n` - ); + FILESYSTEM: { + OUTPUT_DIRECTORY: "out", + PYTHON_LIBS_DIR: "python_libs", }, - INCORRECT_FILE_NAME_FOR_DEVICE: localize( - "error.incorrectFileNameForDevice", - '[ERROR] Can\'t deploy to your Circuit Playground Express device, please rename your file to "code.py" or "main.py". \n' - ), - INCORRECT_FILE_NAME_FOR_DEVICE_POPUP: localize( - "error.incorrectFileNameForDevicePopup", - 'Seems like you have a different file name than what CPX requires, please rename it to "code.py" or "main.py".' - ), - INVALID_FILE_EXTENSION_DEBUG: localize( - "error.invalidFileExtensionDebug", - "The file you tried to run isn't a Python file." - ), - NO_DEVICE: localize( - "error.noDevice", - "No plugged in boards detected. Please double check if your board is connected and/or properly formatted" - ), - NO_FILE_TO_RUN: localize( - "error.noFileToRun", - '[ERROR] We can\'t find a Python file to run. Please make sure you select or open a new ".py" code file, or use the "New File" command to get started and see useful links.\n' - ), - NO_FOLDER_OPENED: localize( - "error.noFolderCreated", - "In order to use the Serial Monitor, you need to open a folder and reload VS Code." - ), - NO_PROGRAM_FOUND_DEBUG: localize( - "error.noProgramFoundDebug", - "Cannot find a program to debug." - ), - NO_PYTHON_PATH: localize( - "error.noPythonPath", - "We found that you don't have Python 3 installed on your computer, please install the latest version, add it to your PATH and try again." - ), - RECONNECT_DEVICE: localize( - "error.reconnectDevice", - "Please disconnect your Circuit Playground Express and try again." - ), - STDERR: (data: string) => { - return localize("error.stderr", `\n[ERROR] ${data} \n`); + INFO: { + ARE_YOU_SURE: localize( + "info.areYouSure", + "Are you sure you don't want to install the dependencies? The extension can't run without installing it" + ), + CLOSED_SERIAL_PORT: (port: string) => { + return localize( + "info.closedSerialPort", + `[DONE] Closed the serial port - ${port} \n` + ); + }, + COMPLETED_MESSAGE: "Completed", + CPX_JSON_ALREADY_GENERATED: localize( + "info.cpxJsonAlreadyGenerated", + "cpx.json has already been generated." + ), + DEPLOY_DEVICE: localize( + "info.deployDevice", + "\n[INFO] Deploying code to the device...\n" + ), + DEPLOY_SIMULATOR: localize( + "info.deploySimulator", + "\n[INFO] Deploying code to the simulator...\n" + ), + DEPLOY_SUCCESS: localize( + "info.deploySuccess", + "\n[INFO] Code successfully copied! Your Circuit Playground Express should be loading and ready to go shortly.\n" + ), + EXTENSION_ACTIVATED: localize( + "info.extensionActivated", + "Congratulations, your extension Adafruit_Simulator is now active!" + ), + FILE_SELECTED: (filePath: string) => { + return localize( + "info.fileSelected", + `[INFO] File selected : ${filePath} \n` + ); + }, + FIRST_TIME_WEBVIEW: localize( + "info.firstTimeWebview", + 'To reopen the simulator click on the "Open Simulator" button on the upper right corner of the text editor, or select the command "Open Simulator" from command palette.' + ), + INCORRECT_FILE_NAME_FOR_SIMULATOR_POPUP: localize( + "info.incorrectFileNameForSimulatorPopup", + 'We want your code to work on your actual board as well. Make sure you name your file "code.py" or "main.py" to be able to run your code on an actual physical device' + ), + INSTALLING_PYTHON_DEPENDENCIES: localize( + "info.installingPythonDependencies", + "The Python packages are currently being installed. You will be prompt a message telling you when the installation is done." + ), + INSTALL_PYTHON_DEPENDENCIES: localize( + "info.installPythonDependencies", + "Do you want us to try and install this extensions dependencies for you?" + ), + INVALID_FILE_NAME_DEBUG: localize( + "info.invalidFileNameDebug", + 'The file you tried to debug isn\'t named "code.py" or "main.py". Rename your file if you want your code to work on your actual device.' + ), + NEW_FILE: localize( + "info.newFile", + "New to Python or the Circuit Playground Express? We are here to help!" + ), + OPENED_SERIAL_PORT: (port: string) => { + return localize( + "info.openedSerialPort", + `[INFO] Opened the serial port - ${port} \n` + ); + }, + OPENING_SERIAL_PORT: (port: string) => { + return localize( + "info.openingSerialPort", + `[STARTING] Opening the serial port - ${port} \n` + ); + }, + PLEASE_OPEN_FOLDER: localize( + "info.pleaseOpenFolder", + "Please open a folder first." + ), + REDIRECT: localize("info.redirect", "You are being redirected."), + RUNNING_CODE: localize("info.runningCode", "Running user code"), + SUCCESSFUL_INSTALL: localize( + "info.successfulInstall", + "Successfully installed Python dependencies." + ), + THIRD_PARTY_WEBSITE: localize( + "info.thirdPartyWebsite", + 'By clicking "Agree and Proceed" you will be redirected to adafruit.com, a third party website not managed by Microsoft. Please note that your activity on adafruit.com is subject to Adafruit\'s privacy policy' + ), + WELCOME_OUTPUT_TAB: localize( + "info.welcomeOutputTab", + "Welcome to the Adafruit Simulator output tab!\n\n" + ), }, - UNEXPECTED_MESSAGE: localize( - "error.unexpectedMessage", - "Webview sent an unexpected message" - ) - }, - FILESYSTEM: { - OUTPUT_DIRECTORY: "out", - PYTHON_LIBS_DIR: "python_libs" - }, - INFO: { - ARE_YOU_SURE: localize( - "info.areYouSure", - "Are you sure you don't want to install the dependencies? The extension can't run without installing it" - ), - CLOSED_SERIAL_PORT: (port: string) => { - return localize( - "info.closedSerialPort", - `[DONE] Closed the serial port - ${port} \n` - ); + LABEL: { + WEBVIEW_PANEL: localize( + "label.webviewPanel", + "Device Simulator Express" + ), }, - COMPLETED_MESSAGE: "Completed", - CPX_JSON_ALREADY_GENERATED: localize( - "info.cpxJsonAlreadyGenerated", - "cpx.json has already been generated." - ), - DEPLOY_DEVICE: localize( - "info.deployDevice", - "\n[INFO] Deploying code to the device...\n" - ), - DEPLOY_SIMULATOR: localize( - "info.deploySimulator", - "\n[INFO] Deploying code to the simulator...\n" - ), - DEPLOY_SUCCESS: localize( - "info.deploySuccess", - "\n[INFO] Code successfully copied! Your Circuit Playground Express should be loading and ready to go shortly.\n" - ), - EXTENSION_ACTIVATED: localize( - "info.extensionActivated", - "Congratulations, your extension Adafruit_Simulator is now active!" - ), - FILE_SELECTED: (filePath: string) => { - return localize( - "info.fileSelected", - `[INFO] File selected : ${filePath} \n` - ); + LINKS: { + DOWNLOAD_PYTHON: "https://www.python.org/downloads/", + EXAMPLE_CODE: + "https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground/tree/master/examples", + HELP: + "https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-quickstart", + INSTALL: + "https://github.com/microsoft/vscode-python-devicesimulator/blob/dev/docs/install.md", + PRIVACY: "https://www.adafruit.com/privacy", + TUTORIALS: + "https://learn.adafruit.com/circuitpython-made-easy-on-circuit-playground-express/circuit-playground-express-library", }, - FIRST_TIME_WEBVIEW: localize( - "info.firstTimeWebview", - 'To reopen the simulator click on the "Open Simulator" button on the upper right corner of the text editor, or select the command "Open Simulator" from command palette.' - ), - INCORRECT_FILE_NAME_FOR_SIMULATOR_POPUP: localize( - "info.incorrectFileNameForSimulatorPopup", - 'We want your code to work on your actual board as well. Make sure you name your file "code.py" or "main.py" to be able to run your code on an actual physical device' - ), - INSTALLING_PYTHON_DEPENDENCIES: localize( - "info.installingPythonDependencies", - "The Python packages are currently being installed. You will be prompt a message telling you when the installation is done." - ), - INSTALL_PYTHON_DEPENDENCIES: localize( - "info.installPythonDependencies", - "Do you want us to try and install this extensions dependencies for you?" - ), - INVALID_FILE_NAME_DEBUG: localize( - "info.invalidFileNameDebug", - 'The file you tried to debug isn\'t named "code.py" or "main.py". Rename your file if you want your code to work on your actual device.' - ), - NEW_FILE: localize( - "info.newFile", - "New to Python or the Circuit Playground Express? We are here to help!" - ), - OPENED_SERIAL_PORT: (port: string) => { - return localize( - "info.openedSerialPort", - `[INFO] Opened the serial port - ${port} \n` - ); + MISC: { + SELECT_PORT_PLACEHOLDER: localize( + "misc.selectPortPlaceholder", + "Select a serial port" + ), + SERIAL_MONITOR_NAME: localize( + "misc.serialMonitorName", + "Device Simulator Express Serial Monitor" + ), + SERIAL_MONITOR_TEST_IF_OPEN: localize( + "misc.testIfPortOpen", + "Test if serial port is open" + ), }, - OPENING_SERIAL_PORT: (port: string) => { - return localize( - "info.openingSerialPort", - `[STARTING] Opening the serial port - ${port} \n` - ); + NAME: localize("name", "Device Simulator Express"), + WARNING: { + ACCEPT_AND_RUN: localize( + "warning.agreeAndRun", + "By selecting ‘Agree and Run’, you understand the extension executes Python code on your local computer, which may be a potential security risk." + ), + INVALID_BAUD_RATE: localize( + "warning.invalidBaudRate", + "Invalid baud rate, keep baud rate unchanged." + ), + NO_RATE_SELECTED: localize( + "warning.noRateSelected", + "No rate is selected, keep baud rate unchanged." + ), + NO_SERIAL_PORT_SELECTED: localize( + "warning.noSerialPortSelected", + "No serial port was selected, please select a serial port first" + ), + SERIAL_MONITOR_ALREADY_OPENED: (port: string) => { + return localize( + "warning.serialMonitorAlreadyOpened", + `Serial monitor is already opened for ${port} \n` + ); + }, + SERIAL_MONITOR_NOT_STARTED: localize( + "warning.serialMonitorNotStarted", + "Serial monitor has not been started." + ), + SERIAL_PORT_NOT_STARTED: localize( + "warning.serialPortNotStarted", + "Serial port has not been started.\n" + ), }, - PLEASE_OPEN_FOLDER: localize( - "info.pleaseOpenFolder", - "Please open a folder first." - ), - REDIRECT: localize("info.redirect", "You are being redirected."), - RUNNING_CODE: localize("info.runningCode", "Running user code"), - SUCCESSFUL_INSTALL: localize( - "info.successfulInstall", - "Successfully installed Python dependencies." - ), - THIRD_PARTY_WEBSITE: localize( - "info.thirdPartyWebsite", - 'By clicking "Agree and Proceed" you will be redirected to adafruit.com, a third party website not managed by Microsoft. Please note that your activity on adafruit.com is subject to Adafruit\'s privacy policy' - ), - WELCOME_OUTPUT_TAB: localize( - "info.welcomeOutputTab", - "Welcome to the Adafruit Simulator output tab!\n\n" - ) - }, - LABEL: { - WEBVIEW_PANEL: localize("label.webviewPanel", "Device Simulator Express") - }, - LINKS: { - DOWNLOAD_PYTHON: "https://www.python.org/downloads/", - EXAMPLE_CODE: - "https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground/tree/master/examples", - HELP: - "https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-quickstart", - INSTALL: "https://github.com/microsoft/vscode-python-devicesimulator/blob/dev/docs/install.md", - PRIVACY: "https://www.adafruit.com/privacy", - TUTORIALS: - "https://learn.adafruit.com/circuitpython-made-easy-on-circuit-playground-express/circuit-playground-express-library" - }, - MISC: { - SELECT_PORT_PLACEHOLDER: localize( - "misc.selectPortPlaceholder", - "Select a serial port" - ), - SERIAL_MONITOR_NAME: localize( - "misc.serialMonitorName", - "Device Simulator Express Serial Monitor" - ), - SERIAL_MONITOR_TEST_IF_OPEN: localize( - "misc.testIfPortOpen", - "Test if serial port is open" - ) - }, - NAME: localize("name", "Device Simulator Express"), - WARNING: { - ACCEPT_AND_RUN: localize( - "warning.agreeAndRun", - "By selecting ‘Agree and Run’, you understand the extension executes Python code on your local computer, which may be a potential security risk." - ), - INVALID_BAUD_RATE: localize( - "warning.invalidBaudRate", - "Invalid baud rate, keep baud rate unchanged." - ), - NO_RATE_SELECTED: localize( - "warning.noRateSelected", - "No rate is selected, keep baud rate unchanged." - ), - NO_SERIAL_PORT_SELECTED: localize( - "warning.noSerialPortSelected", - "No serial port was selected, please select a serial port first" - ), - SERIAL_MONITOR_ALREADY_OPENED: (port: string) => { - return localize( - "warning.serialMonitorAlreadyOpened", - `Serial monitor is already opened for ${port} \n` - ); - }, - SERIAL_MONITOR_NOT_STARTED: localize( - "warning.serialMonitorNotStarted", - "Serial monitor has not been started." - ), - SERIAL_PORT_NOT_STARTED: localize( - "warning.serialPortNotStarted", - "Serial port has not been started.\n" - ) - } }; export enum CONFIG_KEYS { - ENABLE_USB_DETECTION = "deviceSimulatorExpress.enableUSBDetection" + ENABLE_USB_DETECTION = "deviceSimulatorExpress.enableUSBDetection", } export enum TelemetryEventName { - FAILED_TO_OPEN_SIMULATOR = "SIMULATOR.FAILED_TO_OPEN", + FAILED_TO_OPEN_SIMULATOR = "SIMULATOR.FAILED_TO_OPEN", - // Extension commands - COMMAND_DEPLOY_DEVICE = "COMMAND.DEPLOY.DEVICE", - COMMAND_NEW_FILE = "COMMAND.NEW.FILE", - COMMAND_OPEN_SIMULATOR = "COMMAND.OPEN.SIMULATOR", - COMMAND_RUN_SIMULATOR_BUTTON = "COMMAND.RUN.SIMULATOR_BUTTON", - COMMAND_RUN_PALETTE = "COMMAND.RUN.PALETTE", - COMMAND_RUN_EDITOR_ICON = "COMMAND.RUN.EDITOR_ICON", - COMMAND_SERIAL_MONITOR_CHOOSE_PORT = "COMMAND.SERIAL_MONITOR.CHOOSE_PORT", - COMMAND_SERIAL_MONITOR_OPEN = "COMMAND.SERIAL_MONITOR.OPEN", - COMMAND_SERIAL_MONITOR_BAUD_RATE = "COMMAND.SERIAL_MONITOR.BAUD_RATE", - COMMAND_SERIAL_MONITOR_CLOSE = "COMMAND.SERIAL_MONITOR.CLOSE", + // Extension commands + COMMAND_DEPLOY_DEVICE = "COMMAND.DEPLOY.DEVICE", + COMMAND_NEW_FILE = "COMMAND.NEW.FILE", + COMMAND_OPEN_SIMULATOR = "COMMAND.OPEN.SIMULATOR", + COMMAND_RUN_SIMULATOR_BUTTON = "COMMAND.RUN.SIMULATOR_BUTTON", + COMMAND_RUN_PALETTE = "COMMAND.RUN.PALETTE", + COMMAND_RUN_EDITOR_ICON = "COMMAND.RUN.EDITOR_ICON", + COMMAND_SERIAL_MONITOR_CHOOSE_PORT = "COMMAND.SERIAL_MONITOR.CHOOSE_PORT", + COMMAND_SERIAL_MONITOR_OPEN = "COMMAND.SERIAL_MONITOR.OPEN", + COMMAND_SERIAL_MONITOR_BAUD_RATE = "COMMAND.SERIAL_MONITOR.BAUD_RATE", + COMMAND_SERIAL_MONITOR_CLOSE = "COMMAND.SERIAL_MONITOR.CLOSE", - // Simulator interaction - SIMULATOR_BUTTON_A = "SIMULATOR.BUTTON.A", - SIMULATOR_BUTTON_B = "SIMULATOR.BUTTON.B", - SIMULATOR_BUTTON_AB = "SIMULATOR.BUTTON.AB", - SIMULATOR_SWITCH = "SIMULATOR.SWITCH", + // Simulator interaction + SIMULATOR_BUTTON_A = "SIMULATOR.BUTTON.A", + SIMULATOR_BUTTON_B = "SIMULATOR.BUTTON.B", + SIMULATOR_BUTTON_AB = "SIMULATOR.BUTTON.AB", + SIMULATOR_SWITCH = "SIMULATOR.SWITCH", - // Sensors - SIMULATOR_TEMPERATURE_SENSOR = "SIMULATOR.TEMPERATURE", - SIMULATOR_LIGHT_SENSOR = " SIMULATOR.LIGHT", - SIMULATOR_MOTION_SENSOR = "SIMULATOR.MOTION", - SIMULATOR_SHAKE = "SIMULATOR.SHAKE", - SIMULATOR_CAPACITIVE_TOUCH = "SIMULATOR.CAPACITIVE.TOUCH", + // Sensors + SIMULATOR_TEMPERATURE_SENSOR = "SIMULATOR.TEMPERATURE", + SIMULATOR_LIGHT_SENSOR = " SIMULATOR.LIGHT", + SIMULATOR_MOTION_SENSOR = "SIMULATOR.MOTION", + SIMULATOR_SHAKE = "SIMULATOR.SHAKE", + SIMULATOR_CAPACITIVE_TOUCH = "SIMULATOR.CAPACITIVE.TOUCH", - // Pop-up dialog - CLICK_DIALOG_DONT_SHOW = "CLICK.DIALOG.DONT.SHOW", - CLICK_DIALOG_EXAMPLE_CODE = "CLICK.DIALOG.EXAMPLE.CODE", - CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE = "CLICK.DIALOG.HELP.DEPLOY.TO.DEVICE", - CLICK_DIALOG_TUTORIALS = "CLICK.DIALOG.TUTORIALS", + // Pop-up dialog + CLICK_DIALOG_DONT_SHOW = "CLICK.DIALOG.DONT.SHOW", + CLICK_DIALOG_EXAMPLE_CODE = "CLICK.DIALOG.EXAMPLE.CODE", + CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE = "CLICK.DIALOG.HELP.DEPLOY.TO.DEVICE", + CLICK_DIALOG_TUTORIALS = "CLICK.DIALOG.TUTORIALS", - ERROR_PYTHON_DEVICE_PROCESS = "ERROR.PYTHON.DEVICE.PROCESS", - ERROR_PYTHON_PROCESS = "ERROR.PYTHON.PROCESS", - ERROR_COMMAND_NEW_FILE = "ERROR.COMMAND.NEW.FILE", - ERROR_DEPLOY_WITHOUT_DEVICE = "ERROR.DEPLOY.WITHOUT.DEVICE", + ERROR_PYTHON_DEVICE_PROCESS = "ERROR.PYTHON.DEVICE.PROCESS", + ERROR_PYTHON_PROCESS = "ERROR.PYTHON.PROCESS", + ERROR_COMMAND_NEW_FILE = "ERROR.COMMAND.NEW.FILE", + ERROR_DEPLOY_WITHOUT_DEVICE = "ERROR.DEPLOY.WITHOUT.DEVICE", - SUCCESS_COMMAND_DEPLOY_DEVICE = "SUCCESS.COMMAND.DEPLOY.DEVICE", + SUCCESS_COMMAND_DEPLOY_DEVICE = "SUCCESS.COMMAND.DEPLOY.DEVICE", - // Performance - PERFORMANCE_DEPLOY_DEVICE = "PERFORMANCE.DEPLOY.DEVICE", - PERFORMANCE_NEW_FILE = "PERFORMANCE.NEW.FILE", - PERFORMANCE_OPEN_SIMULATOR = "PERFORMANCE.OPEN.SIMULATOR" + // Performance + PERFORMANCE_DEPLOY_DEVICE = "PERFORMANCE.DEPLOY.DEVICE", + PERFORMANCE_NEW_FILE = "PERFORMANCE.NEW.FILE", + PERFORMANCE_OPEN_SIMULATOR = "PERFORMANCE.OPEN.SIMULATOR", } export enum WebviewMessages { - BUTTON_PRESS = "button-press", - PLAY_SIMULATOR = "play-simulator", - SENSOR_CHANGED = "sensor-changed", - REFRESH_SIMULATOR = "refresh-simulator", - SLIDER_TELEMETRY = "slider-telemetry" + BUTTON_PRESS = "button-press", + PLAY_SIMULATOR = "play-simulator", + SENSOR_CHANGED = "sensor-changed", + REFRESH_SIMULATOR = "refresh-simulator", + SLIDER_TELEMETRY = "slider-telemetry", } // tslint:disable-next-line: no-namespace export namespace DialogResponses { - export const ACCEPT_AND_RUN: MessageItem = { - title: localize("dialogResponses.agreeAndRun", "Agree and Run") - }; - export const AGREE_AND_PROCEED: MessageItem = { - title: localize("dialogResponses.agreeAndProceed", "Agree and Proceed") - }; - export const CANCEL: MessageItem = { - title: localize("dialogResponses.cancel", "Cancel") - }; - export const HELP: MessageItem = { - title: localize("dialogResponses.help", "I need help") - }; - export const DONT_SHOW: MessageItem = { - title: localize("dialogResponses.dontShowAgain", "Don't Show Again") - }; - export const NO: MessageItem = { - title: localize("dialogResponses.No", "No") - }; - export const INSTALL_NOW: MessageItem = { - title: localize("dialogResponses.installNow", "Install Now") - }; - export const DONT_INSTALL: MessageItem = { - title: localize("dialogResponses.dontInstall", "Don't Install") - }; - export const PRIVACY_STATEMENT: MessageItem = { - title: localize("info.privacyStatement", "Privacy Statement") - }; - export const TUTORIALS: MessageItem = { - title: localize("dialogResponses.tutorials", "Tutorials on Adafruit") - }; - export const EXAMPLE_CODE: MessageItem = { - title: localize("dialogResponses.exampleCode", "Example Code on GitHub") - }; - export const MESSAGE_UNDERSTOOD: MessageItem = { - title: localize("dialogResponses.messageUnderstood", "Got It") - }; - export const INSTALL_PYTHON: MessageItem = { - title: localize("dialogResponses.installPython", "Install from python.org") - }; - export const YES: MessageItem = { - title: localize("dialogResponses.Yes", "Yes") - }; - export const READ_INSTALL_MD: MessageItem = { - title: localize("dialogResponses.readInstall", "Read installation docs") - }; + export const ACCEPT_AND_RUN: MessageItem = { + title: localize("dialogResponses.agreeAndRun", "Agree and Run"), + }; + export const AGREE_AND_PROCEED: MessageItem = { + title: localize("dialogResponses.agreeAndProceed", "Agree and Proceed"), + }; + export const CANCEL: MessageItem = { + title: localize("dialogResponses.cancel", "Cancel"), + }; + export const HELP: MessageItem = { + title: localize("dialogResponses.help", "I need help"), + }; + export const DONT_SHOW: MessageItem = { + title: localize("dialogResponses.dontShowAgain", "Don't Show Again"), + }; + export const NO: MessageItem = { + title: localize("dialogResponses.No", "No"), + }; + export const INSTALL_NOW: MessageItem = { + title: localize("dialogResponses.installNow", "Install Now"), + }; + export const DONT_INSTALL: MessageItem = { + title: localize("dialogResponses.dontInstall", "Don't Install"), + }; + export const PRIVACY_STATEMENT: MessageItem = { + title: localize("info.privacyStatement", "Privacy Statement"), + }; + export const TUTORIALS: MessageItem = { + title: localize("dialogResponses.tutorials", "Tutorials on Adafruit"), + }; + export const EXAMPLE_CODE: MessageItem = { + title: localize( + "dialogResponses.exampleCode", + "Example Code on GitHub" + ), + }; + export const MESSAGE_UNDERSTOOD: MessageItem = { + title: localize("dialogResponses.messageUnderstood", "Got It"), + }; + export const INSTALL_PYTHON: MessageItem = { + title: localize( + "dialogResponses.installPython", + "Install from python.org" + ), + }; + export const YES: MessageItem = { + title: localize("dialogResponses.Yes", "Yes"), + }; + export const READ_INSTALL_MD: MessageItem = { + title: localize( + "dialogResponses.readInstall", + "Read installation docs" + ), + }; } export const CPX_CONFIG_FILE = path.join(".vscode", "cpx.json"); export const USER_CODE_NAMES = { - CODE_PY: "code.py", - MAIN_PY: "main.py" + CODE_PY: "code.py", + MAIN_PY: "main.py", }; export const STATUS_BAR_PRIORITY = { - PORT: 20, - OPEN_PORT: 30, - BAUD_RATE: 40 + PORT: 20, + OPEN_PORT: 30, + BAUD_RATE: 40, }; export default CONSTANTS; diff --git a/src/cpxWorkspace.ts b/src/cpxWorkspace.ts index b1276a35b..47239a79a 100644 --- a/src/cpxWorkspace.ts +++ b/src/cpxWorkspace.ts @@ -3,7 +3,7 @@ import * as path from "path"; import * as vscode from "vscode"; export class CPXWorkspace { - static get rootPath(): string|undefined { + static get rootPath(): string | undefined { const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { return undefined; @@ -11,7 +11,11 @@ export class CPXWorkspace { for (const workspaceFolder of workspaceFolders) { const workspaceFolderPath = workspaceFolder.uri.fsPath; - const cpxConfigPath = path.join(workspaceFolderPath, ".vscode", "cpx.json"); + const cpxConfigPath = path.join( + workspaceFolderPath, + ".vscode", + "cpx.json" + ); if (fs.existsSync(cpxConfigPath)) { return workspaceFolderPath; } diff --git a/src/debug_user_code.py b/src/debug_user_code.py index 3a5297c21..8d5b57873 100644 --- a/src/debug_user_code.py +++ b/src/debug_user_code.py @@ -8,7 +8,6 @@ import python_constants as CONSTANTS - # Insert absolute path to Adafruit library into sys.path abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) abs_path_to_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.LIBRARY_NAME) @@ -16,8 +15,7 @@ # Insert absolute path to python libraries into sys.path abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) -abs_path_to_lib = os.path.join( - abs_path_to_parent_dir, CONSTANTS.PYTHON_LIBS_DIR) +abs_path_to_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.PYTHON_LIBS_DIR) sys.path.insert(0, abs_path_to_lib) # This import must happen after the sys.path is modified @@ -29,7 +27,7 @@ # Get user's code path -abs_path_to_code_file = '' +abs_path_to_code_file = "" if len(sys.argv) > 1 and sys.argv[1]: abs_path_to_code_file = sys.argv[1] else: @@ -52,16 +50,14 @@ with open(abs_path_to_code_file) as user_code_file: user_code = user_code_file.read() try: - codeObj = compile(user_code, abs_path_to_code_file, - CONSTANTS.EXEC_COMMAND) + codeObj = compile(user_code, abs_path_to_code_file, CONSTANTS.EXEC_COMMAND) exec(codeObj, {}) sys.stdout.flush() except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() errorMessage = CONSTANTS.ERROR_TRACEBACK - stackTrace = traceback.format_exception( - exc_type, exc_value, exc_traceback) + stackTrace = traceback.format_exception(exc_type, exc_value, exc_traceback) for frameIndex in range(2, len(stackTrace) - 1): - errorMessage += '\t' + str(stackTrace[frameIndex]) + errorMessage += "\t" + str(stackTrace[frameIndex]) print(e, errorMessage, file=sys.stderr, flush=True) diff --git a/src/debuggerCommunicationServer.ts b/src/debuggerCommunicationServer.ts index 23d429475..76eb02f42 100644 --- a/src/debuggerCommunicationServer.ts +++ b/src/debuggerCommunicationServer.ts @@ -7,85 +7,87 @@ import { WebviewPanel } from "vscode"; import { SERVER_INFO } from "./constants"; export class DebuggerCommunicationServer { - private port: number; - private serverHttp: http.Server; - private serverIo: socketio.Server; - private simulatorWebview: WebviewPanel | undefined; + private port: number; + private serverHttp: http.Server; + private serverIo: socketio.Server; + private simulatorWebview: WebviewPanel | undefined; - constructor( - webviewPanel: WebviewPanel | undefined, - port = SERVER_INFO.DEFAULT_SERVER_PORT - ) { - this.port = port; - this.serverHttp = new http.Server(); - this.initHttpServer(); + constructor( + webviewPanel: WebviewPanel | undefined, + port = SERVER_INFO.DEFAULT_SERVER_PORT + ) { + this.port = port; + this.serverHttp = new http.Server(); + this.initHttpServer(); - this.serverIo = socketio(this.serverHttp); - this.simulatorWebview = webviewPanel; - this.initEventsHandlers(); - console.info(`Server running on port ${this.port}`); - } + this.serverIo = socketio(this.serverHttp); + this.simulatorWebview = webviewPanel; + this.initEventsHandlers(); + console.info(`Server running on port ${this.port}`); + } - public closeConnection(): void { - this.serverIo.close(); - this.serverHttp.close(); - console.info("Closing the server"); - } + public closeConnection(): void { + this.serverIo.close(); + this.serverHttp.close(); + console.info("Closing the server"); + } - public setWebview(webviewPanel: WebviewPanel | undefined) { - this.simulatorWebview = webviewPanel; - } + public setWebview(webviewPanel: WebviewPanel | undefined) { + this.simulatorWebview = webviewPanel; + } - // Emit Buttons Inputs Events - public emitButtonPress(newState: string): void { - console.log(`Emit Button Press: ${newState} \n`); - this.serverIo.emit("button_press", newState); - } + // Emit Buttons Inputs Events + public emitButtonPress(newState: string): void { + console.log(`Emit Button Press: ${newState} \n`); + this.serverIo.emit("button_press", newState); + } - // Emit Sensors Inputs Events - public emitSensorChanged(newState: string): void { - console.log(`Emit Sensor Changed: ${newState} \n`); - this.serverIo.emit("sensor_changed", newState); - } + // Emit Sensors Inputs Events + public emitSensorChanged(newState: string): void { + console.log(`Emit Sensor Changed: ${newState} \n`); + this.serverIo.emit("sensor_changed", newState); + } - private initHttpServer(): void { - this.serverHttp.listen(this.port); - if (!this.serverHttp.listening) { - throw new Error(SERVER_INFO.ERROR_CODE_INIT_SERVER); + private initHttpServer(): void { + this.serverHttp.listen(this.port); + if (!this.serverHttp.listening) { + throw new Error(SERVER_INFO.ERROR_CODE_INIT_SERVER); + } } - } - private initEventsHandlers(): void { - this.serverIo.on("connection", (socket: any) => { - console.log("Connection received"); + private initEventsHandlers(): void { + this.serverIo.on("connection", (socket: any) => { + console.log("Connection received"); - socket.on("updateState", (data: any) => { - this.handleState(data); - }); + socket.on("updateState", (data: any) => { + this.handleState(data); + }); - socket.on("disconnect", () => { - console.log("Socket disconnected"); - if (this.simulatorWebview) { - this.simulatorWebview.webview.postMessage({ command: "reset-state" }); - } - }); - }); - } + socket.on("disconnect", () => { + console.log("Socket disconnected"); + if (this.simulatorWebview) { + this.simulatorWebview.webview.postMessage({ + command: "reset-state", + }); + } + }); + }); + } - private handleState(data: any): void { - try { - const messageToWebview = JSON.parse(data); - if (messageToWebview.type === "state") { - console.log(`State recieved: ${messageToWebview.data}`); - if (this.simulatorWebview) { - this.simulatorWebview.webview.postMessage({ - command: "set-state", - state: JSON.parse(messageToWebview.data) - }); + private handleState(data: any): void { + try { + const messageToWebview = JSON.parse(data); + if (messageToWebview.type === "state") { + console.log(`State recieved: ${messageToWebview.data}`); + if (this.simulatorWebview) { + this.simulatorWebview.webview.postMessage({ + command: "set-state", + state: JSON.parse(messageToWebview.data), + }); + } + } + } catch (err) { + console.error(`Error: Non-JSON output from the process : ${data}`); } - } - } catch (err) { - console.error(`Error: Non-JSON output from the process : ${data}`); } - } } diff --git a/src/device.py b/src/device.py index 5eca629ee..60b081b50 100644 --- a/src/device.py +++ b/src/device.py @@ -7,6 +7,7 @@ import sys import json import python_constants as CONSTANTS + if sys.platform == "win32": # pylint: disable=import-error import win32api @@ -23,10 +24,15 @@ def find_device_directory(self): """ found_directory = None - if sys.platform.startswith(CONSTANTS.LINUX_OS) or sys.platform.startswith(CONSTANTS.MAC_OS): + if sys.platform.startswith(CONSTANTS.LINUX_OS) or sys.platform.startswith( + CONSTANTS.MAC_OS + ): # Mac or Linux - mounted = check_output(CONSTANTS.MOUNT_COMMAND).decode( - CONSTANTS.UTF_FORMAT).split('\n') + mounted = ( + check_output(CONSTANTS.MOUNT_COMMAND) + .decode(CONSTANTS.UTF_FORMAT) + .split("\n") + ) for mount in mounted: drive_path = mount.split()[2] if mount else "" if drive_path.endswith(CONSTANTS.CPX_DRIVE_NAME): @@ -36,19 +42,20 @@ def find_device_directory(self): # Windows for drive_letter in string.ascii_uppercase: drive_path = "{}:{}".format(drive_letter, os.sep) - if (os.path.exists(drive_path)): + if os.path.exists(drive_path): drive_name = win32api.GetVolumeInformation(drive_path)[0] if drive_name == CONSTANTS.CPX_DRIVE_NAME: found_directory = drive_path break else: - raise NotImplementedError( - CONSTANTS.NOT_SUPPORTED_OS.format(sys.platform)) + raise NotImplementedError(CONSTANTS.NOT_SUPPORTED_OS.format(sys.platform)) if not found_directory: self.connected = False - self.error_message = (CONSTANTS.NO_CPX_DETECTED_ERROR_TITLE, - CONSTANTS.NO_CPX_DETECTED_ERROR_DETAIL.format(sys.platform)) + self.error_message = ( + CONSTANTS.NO_CPX_DETECTED_ERROR_TITLE, + CONSTANTS.NO_CPX_DETECTED_ERROR_DETAIL.format(sys.platform), + ) else: self.connected = True self.error_message = None @@ -61,13 +68,15 @@ def find_device_directory(self): cpx = Device() device_directory = cpx.find_device_directory() if cpx.error_message: - print("{}:\t{}".format( - cpx.error_message[0], cpx.error_message[1]), file=sys.stderr, flush=True) + print( + "{}:\t{}".format(cpx.error_message[0], cpx.error_message[1]), + file=sys.stderr, + flush=True, + ) if cpx.connected: - dest_path = os.path.join( - device_directory, sys.argv[1].rsplit(os.sep, 1)[-1]) + dest_path = os.path.join(device_directory, sys.argv[1].rsplit(os.sep, 1)[-1]) shutil.copyfile(sys.argv[1], dest_path) - message = {'type': 'complete'} + message = {"type": "complete"} else: - message = {'type': 'no-device'} + message = {"type": "no-device"} print(json.dumps(message), flush=True) diff --git a/src/deviceContext.ts b/src/deviceContext.ts index 35374cb83..a4f0afd2e 100644 --- a/src/deviceContext.ts +++ b/src/deviceContext.ts @@ -21,11 +21,18 @@ export class DeviceContext implements vscode.Disposable { private _watcher: vscode.FileSystemWatcher; private _vscodeWatcher: vscode.FileSystemWatcher; private _port!: string; - + private constructor() { if (vscode.workspace && CPXWorkspace.rootPath) { - this._watcher = vscode.workspace.createFileSystemWatcher(path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE)); - this._vscodeWatcher = vscode.workspace.createFileSystemWatcher(path.join(CPXWorkspace.rootPath, ".vscode"), true, true, false); + this._watcher = vscode.workspace.createFileSystemWatcher( + path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE) + ); + this._vscodeWatcher = vscode.workspace.createFileSystemWatcher( + path.join(CPXWorkspace.rootPath, ".vscode"), + true, + true, + false + ); // Reloads the config into the code if the cpx config file has changed this._watcher.onDidCreate(() => this.loadContext()); @@ -37,12 +44,14 @@ export class DeviceContext implements vscode.Disposable { } public loadContext(): Thenable { - return vscode.workspace.findFiles(CPX_CONFIG_FILE, null, 1) - .then((files) => { + return vscode.workspace.findFiles(CPX_CONFIG_FILE, null, 1).then( + files => { let cpxConfigJson: any = {}; if (files && files.length > 0) { const configFile = files[0]; - cpxConfigJson = utils.tryParseJSON(fs.readFileSync(configFile.fsPath, "utf8")); + cpxConfigJson = utils.tryParseJSON( + fs.readFileSync(configFile.fsPath, "utf8") + ); if (cpxConfigJson) { this._port = cpxConfigJson.port; this._onDidChange.fire(); @@ -54,11 +63,13 @@ export class DeviceContext implements vscode.Disposable { this._onDidChange.fire(); } return this; - }, (reason) => { + }, + reason => { this._port = null; this._onDidChange.fire(); return this; - }); + } + ); } public saveContext() { @@ -68,7 +79,9 @@ export class DeviceContext implements vscode.Disposable { const cpxConfigFile = path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE); let cpxConfigJson: any = {}; if (utils.fileExistsSync(cpxConfigFile)) { - cpxConfigJson = utils.tryParseJSON(fs.readFileSync(cpxConfigFile, "utf8")); + cpxConfigJson = utils.tryParseJSON( + fs.readFileSync(cpxConfigFile, "utf8") + ); } if (!cpxConfigJson) { // log and notify user error @@ -77,31 +90,47 @@ export class DeviceContext implements vscode.Disposable { cpxConfigJson.port = this.port; utils.mkdirRecursivelySync(path.dirname(cpxConfigFile)); - fs.writeFileSync(cpxConfigFile, JSON.stringify(cpxConfigJson, (key, value) => { - if (value === null) { - return undefined; - } - return value; - }, 4)); + fs.writeFileSync( + cpxConfigFile, + JSON.stringify( + cpxConfigJson, + (key, value) => { + if (value === null) { + return undefined; + } + return value; + }, + 4 + ) + ); } public dispose() { if (this._watcher) { this._watcher.dispose(); } - + if (this._vscodeWatcher) { this._vscodeWatcher.dispose(); } } public async initialize() { - if (CPXWorkspace.rootPath && utils.fileExistsSync(path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE))) { - vscode.window.showInformationMessage(CONSTANTS.INFO.CPX_JSON_ALREADY_GENERATED); + if ( + CPXWorkspace.rootPath && + utils.fileExistsSync( + path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE) + ) + ) { + vscode.window.showInformationMessage( + CONSTANTS.INFO.CPX_JSON_ALREADY_GENERATED + ); return; } else { if (!CPXWorkspace.rootPath) { - vscode.window.showInformationMessage(CONSTANTS.INFO.PLEASE_OPEN_FOLDER); + vscode.window.showInformationMessage( + CONSTANTS.INFO.PLEASE_OPEN_FOLDER + ); return; } } @@ -119,4 +148,4 @@ export class DeviceContext implements vscode.Disposable { this._port = value; this.saveContext(); } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index 93c5fbfc0..2dcce75db 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,13 +7,13 @@ import * as open from "open"; import * as path from "path"; import * as vscode from "vscode"; import { - CONFIG, - CONSTANTS, - CPX_CONFIG_FILE, - DialogResponses, - SERVER_INFO, - TelemetryEventName, - WebviewMessages + CONFIG, + CONSTANTS, + CPX_CONFIG_FILE, + DialogResponses, + SERVER_INFO, + TelemetryEventName, + WebviewMessages, } from "./constants"; import { CPXWorkspace } from "./cpxWorkspace"; import { DebuggerCommunicationServer } from "./debuggerCommunicationServer"; @@ -37,867 +37,961 @@ let shouldShowRunCodePopup: boolean = true; export let outChannel: vscode.OutputChannel | undefined; function loadScript(context: vscode.ExtensionContext, scriptPath: string) { - return ``; + return ``; } const setPathAndSendMessage = ( - currentPanel: vscode.WebviewPanel, - newFilePath: string + currentPanel: vscode.WebviewPanel, + newFilePath: string ) => { - currentFileAbsPath = newFilePath; - if (currentPanel) { - currentPanel.webview.postMessage({ - command: "current-file", - state: { running_file: newFilePath } - }); - } + currentFileAbsPath = newFilePath; + if (currentPanel) { + currentPanel.webview.postMessage({ + command: "current-file", + state: { running_file: newFilePath }, + }); + } }; // Extension activation export async function activate(context: vscode.ExtensionContext) { - console.info(CONSTANTS.INFO.EXTENSION_ACTIVATED); + console.info(CONSTANTS.INFO.EXTENSION_ACTIVATED); - telemetryAI = new TelemetryAI(context); - let currentPanel: vscode.WebviewPanel | undefined; - let childProcess: cp.ChildProcess | undefined; - let messageListener: vscode.Disposable; - let activeEditorListener: vscode.Disposable; + telemetryAI = new TelemetryAI(context); + let currentPanel: vscode.WebviewPanel | undefined; + let childProcess: cp.ChildProcess | undefined; + let messageListener: vscode.Disposable; + let activeEditorListener: vscode.Disposable; - // Add our library path to settings.json for autocomplete functionality - updatePythonExtraPaths(); + // Add our library path to settings.json for autocomplete functionality + updatePythonExtraPaths(); - pythonExecutableName = await utils.setPythonExectuableName(); + pythonExecutableName = await utils.setPythonExectuableName(); - await utils.checkPythonDependencies(context, pythonExecutableName) + await utils.checkPythonDependencies(context, pythonExecutableName); - // Generate cpx.json - try { - utils.generateCPXConfig(); - configFileCreated = true; - } catch (err) { - console.info("Failed to create the CPX config file."); - configFileCreated = false; - } - - - if (pythonExecutableName === "") { - return; - } + // Generate cpx.json + try { + utils.generateCPXConfig(); + configFileCreated = true; + } catch (err) { + console.info("Failed to create the CPX config file."); + configFileCreated = false; + } - if (outChannel === undefined) { - outChannel = vscode.window.createOutputChannel(CONSTANTS.NAME); - utils.logToOutputChannel(outChannel, CONSTANTS.INFO.WELCOME_OUTPUT_TAB); - } + if (pythonExecutableName === "") { + return; + } - vscode.workspace.onDidSaveTextDocument( - async (document: vscode.TextDocument) => { - await updateCurrentFileIfPython(document, currentPanel); + if (outChannel === undefined) { + outChannel = vscode.window.createOutputChannel(CONSTANTS.NAME); + utils.logToOutputChannel(outChannel, CONSTANTS.INFO.WELCOME_OUTPUT_TAB); } - ); - const openWebview = () => { - if (currentPanel) { - currentPanel.reveal(vscode.ViewColumn.Beside); - } else { - currentPanel = vscode.window.createWebviewPanel( - "adafruitSimulator", - CONSTANTS.LABEL.WEBVIEW_PANEL, - { preserveFocus: true, viewColumn: vscode.ViewColumn.Beside }, - { - // Only allow the webview to access resources in our extension's media directory - localResourceRoots: [ - vscode.Uri.file(path.join(context.extensionPath, CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY)) - ], - enableScripts: true + vscode.workspace.onDidSaveTextDocument( + async (document: vscode.TextDocument) => { + await updateCurrentFileIfPython(document, currentPanel); } - ); + ); - currentPanel.webview.html = getWebviewContent(context); + const openWebview = () => { + if (currentPanel) { + currentPanel.reveal(vscode.ViewColumn.Beside); + } else { + currentPanel = vscode.window.createWebviewPanel( + "adafruitSimulator", + CONSTANTS.LABEL.WEBVIEW_PANEL, + { preserveFocus: true, viewColumn: vscode.ViewColumn.Beside }, + { + // Only allow the webview to access resources in our extension's media directory + localResourceRoots: [ + vscode.Uri.file( + path.join( + context.extensionPath, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY + ) + ), + ], + enableScripts: true, + } + ); - if (messageListener !== undefined) { - messageListener.dispose(); - const index = context.subscriptions.indexOf(messageListener); - if (index > -1) { - context.subscriptions.splice(index, 1); - } - } + currentPanel.webview.html = getWebviewContent(context); - if (activeEditorListener !== undefined) { - activeEditorListener.dispose(); - const index = context.subscriptions.indexOf(activeEditorListener); - if (index > -1) { - context.subscriptions.splice(index, 1); - } - } - - if (currentPanel) { - // Handle messages from webview - messageListener = currentPanel.webview.onDidReceiveMessage( - message => { - const messageJson = JSON.stringify(message.text); - switch (message.command) { - case WebviewMessages.BUTTON_PRESS: - // Send input to the Python process - handleButtonPressTelemetry(message.text); - console.log(`About to write ${messageJson} \n`); - if (inDebugMode && debuggerCommunicationHandler) { - debuggerCommunicationHandler.emitButtonPress(messageJson); - } else if (childProcess) { - childProcess.stdin.write(messageJson + "\n"); - } - break; - case WebviewMessages.PLAY_SIMULATOR: - console.log(`Play button ${messageJson} \n`); - if (message.text.state as boolean) { - setPathAndSendMessage( - currentPanel, - message.text.selected_file - ); - if (currentFileAbsPath) { - const foundDocument = utils.getActiveEditorFromPath( - currentFileAbsPath - ); - if (foundDocument !== undefined) { - currentTextDocument = foundDocument; - } - } - telemetryAI.trackFeatureUsage( - TelemetryEventName.COMMAND_RUN_SIMULATOR_BUTTON - ); - runSimulatorCommand(); - } else { - killProcessIfRunning(); + if (messageListener !== undefined) { + messageListener.dispose(); + const index = context.subscriptions.indexOf(messageListener); + if (index > -1) { + context.subscriptions.splice(index, 1); } - break; - case WebviewMessages.SENSOR_CHANGED: - checkForTelemetry(message.text); - console.log(`Sensor changed ${messageJson} \n`); - if (inDebugMode && debuggerCommunicationHandler) { - debuggerCommunicationHandler.emitSensorChanged(messageJson); - } else if (childProcess) { - childProcess.stdin.write(messageJson + "\n"); + } + + if (activeEditorListener !== undefined) { + activeEditorListener.dispose(); + const index = context.subscriptions.indexOf( + activeEditorListener + ); + if (index > -1) { + context.subscriptions.splice(index, 1); } - break; - case WebviewMessages.REFRESH_SIMULATOR: - console.log("Refresh button"); - runSimulatorCommand(); - break; - case WebviewMessages.SLIDER_TELEMETRY: - handleSensorTelemetry(message.text); - break; - default: - vscode.window.showInformationMessage( - CONSTANTS.ERROR.UNEXPECTED_MESSAGE + } + + if (currentPanel) { + // Handle messages from webview + messageListener = currentPanel.webview.onDidReceiveMessage( + message => { + const messageJson = JSON.stringify(message.text); + switch (message.command) { + case WebviewMessages.BUTTON_PRESS: + // Send input to the Python process + handleButtonPressTelemetry(message.text); + console.log(`About to write ${messageJson} \n`); + if ( + inDebugMode && + debuggerCommunicationHandler + ) { + debuggerCommunicationHandler.emitButtonPress( + messageJson + ); + } else if (childProcess) { + childProcess.stdin.write( + messageJson + "\n" + ); + } + break; + case WebviewMessages.PLAY_SIMULATOR: + console.log(`Play button ${messageJson} \n`); + if (message.text.state as boolean) { + setPathAndSendMessage( + currentPanel, + message.text.selected_file + ); + if (currentFileAbsPath) { + const foundDocument = utils.getActiveEditorFromPath( + currentFileAbsPath + ); + if (foundDocument !== undefined) { + currentTextDocument = foundDocument; + } + } + telemetryAI.trackFeatureUsage( + TelemetryEventName.COMMAND_RUN_SIMULATOR_BUTTON + ); + runSimulatorCommand(); + } else { + killProcessIfRunning(); + } + break; + case WebviewMessages.SENSOR_CHANGED: + checkForTelemetry(message.text); + console.log(`Sensor changed ${messageJson} \n`); + if ( + inDebugMode && + debuggerCommunicationHandler + ) { + debuggerCommunicationHandler.emitSensorChanged( + messageJson + ); + } else if (childProcess) { + childProcess.stdin.write( + messageJson + "\n" + ); + } + break; + case WebviewMessages.REFRESH_SIMULATOR: + console.log("Refresh button"); + runSimulatorCommand(); + break; + case WebviewMessages.SLIDER_TELEMETRY: + handleSensorTelemetry(message.text); + break; + default: + vscode.window.showInformationMessage( + CONSTANTS.ERROR.UNEXPECTED_MESSAGE + ); + break; + } + }, + undefined, + context.subscriptions ); - break; + + activeEditorListener = utils.addVisibleTextEditorCallback( + currentPanel, + context + ); + console.log("sent"); } - }, - undefined, - context.subscriptions - ); - activeEditorListener = utils.addVisibleTextEditorCallback( - currentPanel, - context - ); - console.log("sent"); - } + currentPanel.onDidDispose( + () => { + currentPanel = undefined; + if (debuggerCommunicationHandler) { + debuggerCommunicationHandler.setWebview(undefined); + } + killProcessIfRunning(); + if (firstTimeClosed) { + vscode.window.showInformationMessage( + CONSTANTS.INFO.FIRST_TIME_WEBVIEW + ); + firstTimeClosed = false; + } + }, + undefined, + context.subscriptions + ); + } + }; - currentPanel.onDidDispose( + // Open Simulator on the webview + const openSimulator: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.openSimulator", () => { - currentPanel = undefined; - if (debuggerCommunicationHandler) { - debuggerCommunicationHandler.setWebview(undefined); - } - killProcessIfRunning(); - if (firstTimeClosed) { - vscode.window.showInformationMessage( - CONSTANTS.INFO.FIRST_TIME_WEBVIEW - ); - firstTimeClosed = false; - } - }, - undefined, - context.subscriptions - ); - } - }; - - // Open Simulator on the webview - const openSimulator: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.openSimulator", - () => { - telemetryAI.trackFeatureUsage(TelemetryEventName.COMMAND_OPEN_SIMULATOR); - telemetryAI.runWithLatencyMeasure( - openWebview, - TelemetryEventName.PERFORMANCE_OPEN_SIMULATOR - ); - } - ); - - const openTemplateFile = () => { - const fileName = "template.py"; - const filePath = __dirname + path.sep + fileName; - const file = fs.readFileSync(filePath, "utf8"); - const showNewFilePopup: boolean = vscode.workspace.getConfiguration().get(CONFIG.SHOW_NEW_FILE_POPUP); - - if (showNewFilePopup) { - vscode.window - .showInformationMessage( - CONSTANTS.INFO.NEW_FILE, - DialogResponses.DONT_SHOW, - DialogResponses.EXAMPLE_CODE, - DialogResponses.TUTORIALS - ) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.DONT_SHOW) { - vscode.workspace.getConfiguration().update(CONFIG.SHOW_NEW_FILE_POPUP, false); telemetryAI.trackFeatureUsage( - TelemetryEventName.CLICK_DIALOG_DONT_SHOW + TelemetryEventName.COMMAND_OPEN_SIMULATOR ); - } else if (selection === DialogResponses.EXAMPLE_CODE) { - open(CONSTANTS.LINKS.EXAMPLE_CODE); - telemetryAI.trackFeatureUsage( - TelemetryEventName.CLICK_DIALOG_EXAMPLE_CODE + telemetryAI.runWithLatencyMeasure( + openWebview, + TelemetryEventName.PERFORMANCE_OPEN_SIMULATOR ); - } else if (selection === DialogResponses.TUTORIALS) { - const okAction = () => { - open(CONSTANTS.LINKS.TUTORIALS); - telemetryAI.trackFeatureUsage( - TelemetryEventName.CLICK_DIALOG_TUTORIALS - ); - }; - utils.showPrivacyModal(okAction); - } - }); - } + } + ); - // tslint:disable-next-line: ban-comma-operator - vscode.workspace - .openTextDocument({ content: file, language: "python" }) - .then((template: vscode.TextDocument) => { - vscode.window.showTextDocument(template, 1, false).then(() => { - openWebview(); - }); - }), - // tslint:disable-next-line: no-unused-expression - (error: any) => { - telemetryAI.trackFeatureUsage( - TelemetryEventName.ERROR_COMMAND_NEW_FILE - ); - console.error(`Failed to open a new text document: ${error}`); - }; - }; - - const newFile: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.newFile", - () => { - telemetryAI.trackFeatureUsage(TelemetryEventName.COMMAND_NEW_FILE); - telemetryAI.runWithLatencyMeasure( - openTemplateFile, - TelemetryEventName.PERFORMANCE_NEW_FILE - ); - } - ); - - const killProcessIfRunning = () => { - if (childProcess !== undefined) { - if (currentPanel) { - console.info("Sending clearing state command"); - currentPanel.webview.postMessage({ command: "reset-state" }); - } - // TODO: We need to check the process was correctly killed - childProcess.kill(); - childProcess = undefined; - } - }; - - const runSimulatorCommand = async () => { - // Prevent running new code if a debug session is active - if (inDebugMode) { - vscode.window.showErrorMessage( - CONSTANTS.ERROR.DEBUGGING_SESSION_IN_PROGESS - ); - return; - } - if (shouldShowRunCodePopup) { - const shouldExitCommand = await vscode.window - .showWarningMessage( - CONSTANTS.WARNING.ACCEPT_AND_RUN, - DialogResponses.ACCEPT_AND_RUN, - DialogResponses.CANCEL - ) - .then((selection: vscode.MessageItem | undefined) => { - let hasAccepted = true; - if (selection === DialogResponses.ACCEPT_AND_RUN) { - shouldShowRunCodePopup = false; - hasAccepted = false; - } - return hasAccepted; - }); - // Don't run users code if they don't accept - if (shouldExitCommand) { - return; - } - } + const openTemplateFile = () => { + const fileName = "template.py"; + const filePath = __dirname + path.sep + fileName; + const file = fs.readFileSync(filePath, "utf8"); + const showNewFilePopup: boolean = vscode.workspace + .getConfiguration() + .get(CONFIG.SHOW_NEW_FILE_POPUP); + + if (showNewFilePopup) { + vscode.window + .showInformationMessage( + CONSTANTS.INFO.NEW_FILE, + DialogResponses.DONT_SHOW, + DialogResponses.EXAMPLE_CODE, + DialogResponses.TUTORIALS + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.DONT_SHOW) { + vscode.workspace + .getConfiguration() + .update(CONFIG.SHOW_NEW_FILE_POPUP, false); + telemetryAI.trackFeatureUsage( + TelemetryEventName.CLICK_DIALOG_DONT_SHOW + ); + } else if (selection === DialogResponses.EXAMPLE_CODE) { + open(CONSTANTS.LINKS.EXAMPLE_CODE); + telemetryAI.trackFeatureUsage( + TelemetryEventName.CLICK_DIALOG_EXAMPLE_CODE + ); + } else if (selection === DialogResponses.TUTORIALS) { + const okAction = () => { + open(CONSTANTS.LINKS.TUTORIALS); + telemetryAI.trackFeatureUsage( + TelemetryEventName.CLICK_DIALOG_TUTORIALS + ); + }; + utils.showPrivacyModal(okAction); + } + }); + } + + // tslint:disable-next-line: ban-comma-operator + vscode.workspace + .openTextDocument({ content: file, language: "python" }) + .then((template: vscode.TextDocument) => { + vscode.window.showTextDocument(template, 1, false).then(() => { + openWebview(); + }); + }), + // tslint:disable-next-line: no-unused-expression + (error: any) => { + telemetryAI.trackFeatureUsage( + TelemetryEventName.ERROR_COMMAND_NEW_FILE + ); + console.error(`Failed to open a new text document: ${error}`); + }; + }; - openWebview(); + const newFile: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.newFile", + () => { + telemetryAI.trackFeatureUsage(TelemetryEventName.COMMAND_NEW_FILE); + telemetryAI.runWithLatencyMeasure( + openTemplateFile, + TelemetryEventName.PERFORMANCE_NEW_FILE + ); + } + ); - if (!currentPanel) { - return; - } + const killProcessIfRunning = () => { + if (childProcess !== undefined) { + if (currentPanel) { + console.info("Sending clearing state command"); + currentPanel.webview.postMessage({ command: "reset-state" }); + } + // TODO: We need to check the process was correctly killed + childProcess.kill(); + childProcess = undefined; + } + }; - console.info(CONSTANTS.INFO.RUNNING_CODE); + const runSimulatorCommand = async () => { + // Prevent running new code if a debug session is active + if (inDebugMode) { + vscode.window.showErrorMessage( + CONSTANTS.ERROR.DEBUGGING_SESSION_IN_PROGESS + ); + return; + } + if (shouldShowRunCodePopup) { + const shouldExitCommand = await vscode.window + .showWarningMessage( + CONSTANTS.WARNING.ACCEPT_AND_RUN, + DialogResponses.ACCEPT_AND_RUN, + DialogResponses.CANCEL + ) + .then((selection: vscode.MessageItem | undefined) => { + let hasAccepted = true; + if (selection === DialogResponses.ACCEPT_AND_RUN) { + shouldShowRunCodePopup = false; + hasAccepted = false; + } + return hasAccepted; + }); + // Don't run users code if they don't accept + if (shouldExitCommand) { + return; + } + } - utils.logToOutputChannel(outChannel, CONSTANTS.INFO.DEPLOY_SIMULATOR, true); + openWebview(); - killProcessIfRunning(); + if (!currentPanel) { + return; + } - await updateCurrentFileIfPython( - vscode.window.activeTextEditor!.document, - currentPanel - ); + console.info(CONSTANTS.INFO.RUNNING_CODE); - if (currentFileAbsPath === "") { - utils.logToOutputChannel( - outChannel, - CONSTANTS.ERROR.NO_FILE_TO_RUN, - true - ); - vscode.window.showErrorMessage( - CONSTANTS.ERROR.NO_FILE_TO_RUN, - DialogResponses.MESSAGE_UNDERSTOOD - ); - } else { - // Save on run - await currentTextDocument.save(); - - if (!currentTextDocument.fileName.endsWith(".py")) { utils.logToOutputChannel( - outChannel, - CONSTANTS.ERROR.NO_FILE_TO_RUN, - true + outChannel, + CONSTANTS.INFO.DEPLOY_SIMULATOR, + true ); - return; - } - utils.logToOutputChannel( - outChannel, - CONSTANTS.INFO.FILE_SELECTED(currentFileAbsPath) - ); - - if ( - !utils.validCodeFileName(currentFileAbsPath) && - shouldShowInvalidFileNamePopup - ) { - // to the popup - vscode.window - .showInformationMessage( - CONSTANTS.INFO.INCORRECT_FILE_NAME_FOR_SIMULATOR_POPUP, - DialogResponses.DONT_SHOW, - DialogResponses.MESSAGE_UNDERSTOOD - ) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.DONT_SHOW) { - shouldShowInvalidFileNamePopup = false; - telemetryAI.trackFeatureUsage( - TelemetryEventName.CLICK_DIALOG_DONT_SHOW - ); - } - }); - } - // Activate the run webview button - currentPanel.webview.postMessage({ command: "activate-play" }); + killProcessIfRunning(); - childProcess = cp.spawn(pythonExecutableName, [ - utils.getPathToScript(context, CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, "process_user_code.py"), - currentFileAbsPath, - JSON.stringify({ enable_telemetry: utils.getTelemetryState() }) - ]); + await updateCurrentFileIfPython( + vscode.window.activeTextEditor!.document, + currentPanel + ); - let dataFromTheProcess = ""; - let oldMessage = ""; + if (currentFileAbsPath === "") { + utils.logToOutputChannel( + outChannel, + CONSTANTS.ERROR.NO_FILE_TO_RUN, + true + ); + vscode.window.showErrorMessage( + CONSTANTS.ERROR.NO_FILE_TO_RUN, + DialogResponses.MESSAGE_UNDERSTOOD + ); + } else { + // Save on run + await currentTextDocument.save(); + + if (!currentTextDocument.fileName.endsWith(".py")) { + utils.logToOutputChannel( + outChannel, + CONSTANTS.ERROR.NO_FILE_TO_RUN, + true + ); + return; + } + utils.logToOutputChannel( + outChannel, + CONSTANTS.INFO.FILE_SELECTED(currentFileAbsPath) + ); - // Data received from Python process - childProcess.stdout.on("data", data => { - dataFromTheProcess = data.toString(); - if (currentPanel) { - // Process the data from the process and send one state at a time - dataFromTheProcess.split("\0").forEach(message => { - if (currentPanel && message.length > 0 && message !== oldMessage) { - oldMessage = message; - let messageToWebview; - // Check the message is a JSON - try { - messageToWebview = JSON.parse(message); - // Check the JSON is a state - switch (messageToWebview.type) { - case "state": - console.log( - `Process state output = ${messageToWebview.data}` - ); - currentPanel.webview.postMessage({ - command: "set-state", - state: JSON.parse(messageToWebview.data) + if ( + !utils.validCodeFileName(currentFileAbsPath) && + shouldShowInvalidFileNamePopup + ) { + // to the popup + vscode.window + .showInformationMessage( + CONSTANTS.INFO.INCORRECT_FILE_NAME_FOR_SIMULATOR_POPUP, + DialogResponses.DONT_SHOW, + DialogResponses.MESSAGE_UNDERSTOOD + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.DONT_SHOW) { + shouldShowInvalidFileNamePopup = false; + telemetryAI.trackFeatureUsage( + TelemetryEventName.CLICK_DIALOG_DONT_SHOW + ); + } }); - break; + } - case "print": - console.log( - `Process print statement output = ${ - messageToWebview.data - }` - ); - utils.logToOutputChannel( - outChannel, - `[PRINT] ${messageToWebview.data}` - ); - break; + // Activate the run webview button + currentPanel.webview.postMessage({ command: "activate-play" }); + + childProcess = cp.spawn(pythonExecutableName, [ + utils.getPathToScript( + context, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + "process_user_code.py" + ), + currentFileAbsPath, + JSON.stringify({ enable_telemetry: utils.getTelemetryState() }), + ]); + + let dataFromTheProcess = ""; + let oldMessage = ""; + + // Data received from Python process + childProcess.stdout.on("data", data => { + dataFromTheProcess = data.toString(); + if (currentPanel) { + // Process the data from the process and send one state at a time + dataFromTheProcess.split("\0").forEach(message => { + if ( + currentPanel && + message.length > 0 && + message !== oldMessage + ) { + oldMessage = message; + let messageToWebview; + // Check the message is a JSON + try { + messageToWebview = JSON.parse(message); + // Check the JSON is a state + switch (messageToWebview.type) { + case "state": + console.log( + `Process state output = ${messageToWebview.data}` + ); + currentPanel.webview.postMessage({ + command: "set-state", + state: JSON.parse( + messageToWebview.data + ), + }); + break; + + case "print": + console.log( + `Process print statement output = ${messageToWebview.data}` + ); + utils.logToOutputChannel( + outChannel, + `[PRINT] ${messageToWebview.data}` + ); + break; + + default: + console.log( + `Non-state JSON output from the process : ${messageToWebview}` + ); + break; + } + } catch (err) { + console.log( + `Non-JSON output from the process : ${message}` + ); + } + } + }); + } + }); - default: - console.log( - `Non-state JSON output from the process : ${messageToWebview}` - ); - break; + // Std error output + childProcess.stderr.on("data", data => { + console.error( + `Error from the Python process through stderr: ${data}` + ); + telemetryAI.trackFeatureUsage( + TelemetryEventName.ERROR_PYTHON_PROCESS + ); + utils.logToOutputChannel( + outChannel, + CONSTANTS.ERROR.STDERR(data), + true + ); + if (currentPanel) { + console.log("Sending clearing state command"); + currentPanel.webview.postMessage({ + command: "reset-state", + }); } - } catch (err) { - console.log(`Non-JSON output from the process : ${message}`); - } - } - }); + }); + + // When the process is done + childProcess.on("end", (code: number) => { + console.info(`Command execution exited with code: ${code}`); + }); + } + }; + + const runSimulatorEditorButton: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.runSimulatorEditorButton", + () => { + telemetryAI.trackFeatureUsage( + TelemetryEventName.COMMAND_RUN_EDITOR_ICON + ); + runSimulatorCommand(); + } + ); + + // Send message to the webview + const runSimulator: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.runSimulator", + () => { + telemetryAI.trackFeatureUsage( + TelemetryEventName.COMMAND_RUN_PALETTE + ); + runSimulatorCommand(); } - }); + ); + + const deployCodeToDevice = async () => { + console.info("Sending code to device"); - // Std error output - childProcess.stderr.on("data", data => { - console.error(`Error from the Python process through stderr: ${data}`); - telemetryAI.trackFeatureUsage(TelemetryEventName.ERROR_PYTHON_PROCESS); utils.logToOutputChannel( - outChannel, - CONSTANTS.ERROR.STDERR(data), - true + outChannel, + CONSTANTS.INFO.DEPLOY_DEVICE, + true ); - if (currentPanel) { - console.log("Sending clearing state command"); - currentPanel.webview.postMessage({ command: "reset-state" }); + + await updateCurrentFileIfPython( + vscode.window.activeTextEditor!.document, + currentPanel + ); + + if (currentFileAbsPath === "") { + utils.logToOutputChannel( + outChannel, + CONSTANTS.ERROR.NO_FILE_TO_RUN, + true + ); + vscode.window.showErrorMessage( + CONSTANTS.ERROR.NO_FILE_TO_RUN, + DialogResponses.MESSAGE_UNDERSTOOD + ); + } else if (!utils.validCodeFileName(currentFileAbsPath)) { + // Save on run + await currentTextDocument.save(); + // Output panel + utils.logToOutputChannel( + outChannel, + CONSTANTS.ERROR.INCORRECT_FILE_NAME_FOR_DEVICE, + true + ); + // Popup + vscode.window.showErrorMessage( + CONSTANTS.ERROR.INCORRECT_FILE_NAME_FOR_DEVICE_POPUP + ); + } else { + utils.logToOutputChannel( + outChannel, + CONSTANTS.INFO.FILE_SELECTED(currentFileAbsPath) + ); + + const deviceProcess = cp.spawn(pythonExecutableName, [ + utils.getPathToScript( + context, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + "device.py" + ), + currentFileAbsPath, + ]); + + let dataFromTheProcess = ""; + + // Data received from Python process + deviceProcess.stdout.on("data", data => { + dataFromTheProcess = data.toString(); + console.log(`Device output = ${dataFromTheProcess}`); + let messageToWebview; + try { + messageToWebview = JSON.parse(dataFromTheProcess); + // Check the JSON is a state + switch (messageToWebview.type) { + case "complete": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SUCCESS_COMMAND_DEPLOY_DEVICE + ); + utils.logToOutputChannel( + outChannel, + CONSTANTS.INFO.DEPLOY_SUCCESS + ); + break; + + case "no-device": + telemetryAI.trackFeatureUsage( + TelemetryEventName.ERROR_DEPLOY_WITHOUT_DEVICE + ); + vscode.window + .showErrorMessage( + CONSTANTS.ERROR.NO_DEVICE, + DialogResponses.HELP + ) + .then( + ( + selection: + | vscode.MessageItem + | undefined + ) => { + if ( + selection === DialogResponses.HELP + ) { + const okAction = () => { + open(CONSTANTS.LINKS.HELP); + telemetryAI.trackFeatureUsage( + TelemetryEventName.CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE + ); + }; + utils.showPrivacyModal(okAction); + } + } + ); + break; + + default: + console.log( + `Non-state JSON output from the process : ${messageToWebview}` + ); + break; + } + } catch (err) { + console.log( + `Non-JSON output from the process : ${dataFromTheProcess}` + ); + } + }); + + // Std error output + deviceProcess.stderr.on("data", data => { + telemetryAI.trackFeatureUsage( + TelemetryEventName.ERROR_PYTHON_DEVICE_PROCESS, + { error: `${data}` } + ); + console.error( + `Error from the Python device process through stderr: ${data}` + ); + utils.logToOutputChannel( + outChannel, + `[ERROR] ${data} \n`, + true + ); + }); + + // When the process is done + deviceProcess.on("end", (code: number) => { + console.info(`Command execution exited with code: ${code}`); + }); } - }); + }; - // When the process is done - childProcess.on("end", (code: number) => { - console.info(`Command execution exited with code: ${code}`); - }); - } - }; + const runDevice: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.runDevice", + () => { + telemetryAI.trackFeatureUsage( + TelemetryEventName.COMMAND_DEPLOY_DEVICE + ); + telemetryAI.runWithLatencyMeasure( + deployCodeToDevice, + TelemetryEventName.PERFORMANCE_DEPLOY_DEVICE + ); + } + ); - const runSimulatorEditorButton: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.runSimulatorEditorButton", - () => { - telemetryAI.trackFeatureUsage(TelemetryEventName.COMMAND_RUN_EDITOR_ICON); - runSimulatorCommand(); + let serialMonitor: SerialMonitor | undefined; + if (configFileCreated) { + serialMonitor = SerialMonitor.getInstance(); + context.subscriptions.push(serialMonitor); } - ); - - // Send message to the webview - const runSimulator: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.runSimulator", - () => { - telemetryAI.trackFeatureUsage(TelemetryEventName.COMMAND_RUN_PALETTE); - runSimulatorCommand(); - } - ); - - const deployCodeToDevice = async () => { - console.info("Sending code to device"); - utils.logToOutputChannel(outChannel, CONSTANTS.INFO.DEPLOY_DEVICE, true); + const selectSerialPort: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.selectSerialPort", + () => { + if (serialMonitor) { + telemetryAI.runWithLatencyMeasure(() => { + serialMonitor.selectSerialPort(null, null); + }, TelemetryEventName.COMMAND_SERIAL_MONITOR_CHOOSE_PORT); + } else { + vscode.window.showErrorMessage( + CONSTANTS.ERROR.NO_FOLDER_OPENED + ); + console.info("Serial monitor is not defined."); + } + } + ); - await updateCurrentFileIfPython( - vscode.window.activeTextEditor!.document, - currentPanel + const openSerialMonitor: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.openSerialMonitor", + () => { + if (serialMonitor) { + telemetryAI.runWithLatencyMeasure( + serialMonitor.openSerialMonitor.bind(serialMonitor), + TelemetryEventName.COMMAND_SERIAL_MONITOR_OPEN + ); + } else { + vscode.window.showErrorMessage( + CONSTANTS.ERROR.NO_FOLDER_OPENED + ); + console.info("Serial monitor is not defined."); + } + } ); - if (currentFileAbsPath === "") { - utils.logToOutputChannel( - outChannel, - CONSTANTS.ERROR.NO_FILE_TO_RUN, - true - ); - vscode.window.showErrorMessage( - CONSTANTS.ERROR.NO_FILE_TO_RUN, - DialogResponses.MESSAGE_UNDERSTOOD - ); - } else if (!utils.validCodeFileName(currentFileAbsPath)) { - // Save on run - await currentTextDocument.save(); - // Output panel - utils.logToOutputChannel( - outChannel, - CONSTANTS.ERROR.INCORRECT_FILE_NAME_FOR_DEVICE, - true - ); - // Popup - vscode.window.showErrorMessage( - CONSTANTS.ERROR.INCORRECT_FILE_NAME_FOR_DEVICE_POPUP - ); - } else { - utils.logToOutputChannel( - outChannel, - CONSTANTS.INFO.FILE_SELECTED(currentFileAbsPath) - ); - - const deviceProcess = cp.spawn(pythonExecutableName, [ - utils.getPathToScript(context, CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, "device.py"), - currentFileAbsPath - ]); - - let dataFromTheProcess = ""; - - // Data received from Python process - deviceProcess.stdout.on("data", data => { - dataFromTheProcess = data.toString(); - console.log(`Device output = ${dataFromTheProcess}`); - let messageToWebview; - try { - messageToWebview = JSON.parse(dataFromTheProcess); - // Check the JSON is a state - switch (messageToWebview.type) { - case "complete": - telemetryAI.trackFeatureUsage( - TelemetryEventName.SUCCESS_COMMAND_DEPLOY_DEVICE - ); - utils.logToOutputChannel( - outChannel, - CONSTANTS.INFO.DEPLOY_SUCCESS - ); - break; - - case "no-device": - telemetryAI.trackFeatureUsage( - TelemetryEventName.ERROR_DEPLOY_WITHOUT_DEVICE - ); - vscode.window - .showErrorMessage( - CONSTANTS.ERROR.NO_DEVICE, - DialogResponses.HELP - ) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.HELP) { - const okAction = () => { - open(CONSTANTS.LINKS.HELP); - telemetryAI.trackFeatureUsage( - TelemetryEventName.CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE - ); - }; - utils.showPrivacyModal(okAction); - } - }); - break; - - default: - console.log( - `Non-state JSON output from the process : ${messageToWebview}` - ); - break; - } - } catch (err) { - console.log( - `Non-JSON output from the process : ${dataFromTheProcess}` - ); + const changeBaudRate: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.changeBaudRate", + () => { + if (serialMonitor) { + telemetryAI.runWithLatencyMeasure( + serialMonitor.changeBaudRate.bind(serialMonitor), + TelemetryEventName.COMMAND_SERIAL_MONITOR_BAUD_RATE + ); + } else { + vscode.window.showErrorMessage( + CONSTANTS.ERROR.NO_FOLDER_OPENED + ); + console.info("Serial monitor is not defined."); + } } - }); + ); - // Std error output - deviceProcess.stderr.on("data", data => { - telemetryAI.trackFeatureUsage( - TelemetryEventName.ERROR_PYTHON_DEVICE_PROCESS, - { error: `${data}` } - ); - console.error( - `Error from the Python device process through stderr: ${data}` - ); - utils.logToOutputChannel(outChannel, `[ERROR] ${data} \n`, true); - }); + const closeSerialMonitor: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.closeSerialMonitor", + (port, showWarning = true) => { + if (serialMonitor) { + telemetryAI.runWithLatencyMeasure(() => { + serialMonitor.closeSerialMonitor(port, showWarning); + }, TelemetryEventName.COMMAND_SERIAL_MONITOR_CLOSE); + } else { + vscode.window.showErrorMessage( + CONSTANTS.ERROR.NO_FOLDER_OPENED + ); + console.info("Serial monitor is not defined."); + } + } + ); - // When the process is done - deviceProcess.on("end", (code: number) => { - console.info(`Command execution exited with code: ${code}`); - }); - } - }; - - const runDevice: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.runDevice", - () => { - telemetryAI.trackFeatureUsage(TelemetryEventName.COMMAND_DEPLOY_DEVICE); - telemetryAI.runWithLatencyMeasure( - deployCodeToDevice, - TelemetryEventName.PERFORMANCE_DEPLOY_DEVICE - ); - } - ); - - let serialMonitor: SerialMonitor | undefined; - if (configFileCreated) { - serialMonitor = SerialMonitor.getInstance(); - context.subscriptions.push(serialMonitor); - } - - const selectSerialPort: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.selectSerialPort", - () => { - if (serialMonitor) { - telemetryAI.runWithLatencyMeasure(() => { - serialMonitor.selectSerialPort(null, null); - }, TelemetryEventName.COMMAND_SERIAL_MONITOR_CHOOSE_PORT); - } else { - vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_FOLDER_OPENED); - console.info("Serial monitor is not defined."); - } - } - ); - - const openSerialMonitor: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.openSerialMonitor", - () => { - if (serialMonitor) { - telemetryAI.runWithLatencyMeasure( - serialMonitor.openSerialMonitor.bind(serialMonitor), - TelemetryEventName.COMMAND_SERIAL_MONITOR_OPEN - ); - } else { - vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_FOLDER_OPENED); - console.info("Serial monitor is not defined."); - } - } - ); - - const changeBaudRate: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.changeBaudRate", - () => { - if (serialMonitor) { - telemetryAI.runWithLatencyMeasure( - serialMonitor.changeBaudRate.bind(serialMonitor), - TelemetryEventName.COMMAND_SERIAL_MONITOR_BAUD_RATE - ); - } else { - vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_FOLDER_OPENED); - console.info("Serial monitor is not defined."); - } - } - ); - - const closeSerialMonitor: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.closeSerialMonitor", - (port, showWarning = true) => { - if (serialMonitor) { - telemetryAI.runWithLatencyMeasure(() => { - serialMonitor.closeSerialMonitor(port, showWarning); - }, TelemetryEventName.COMMAND_SERIAL_MONITOR_CLOSE); - } else { - vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_FOLDER_OPENED); - console.info("Serial monitor is not defined."); - } + UsbDetector.getInstance().initialize(context.extensionPath); + UsbDetector.getInstance().startListening(); + + if ( + CPXWorkspace.rootPath && + (utils.fileExistsSync( + path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE) + ) || + vscode.window.activeTextEditor) + ) { + (() => { + if (!SerialMonitor.getInstance().initialized) { + SerialMonitor.getInstance().initialize(); + } + })(); } - ); - - UsbDetector.getInstance().initialize(context.extensionPath); - UsbDetector.getInstance().startListening(); - - if ( - CPXWorkspace.rootPath && - (utils.fileExistsSync(path.join(CPXWorkspace.rootPath, CPX_CONFIG_FILE)) || - vscode.window.activeTextEditor) - ) { - (() => { - if (!SerialMonitor.getInstance().initialized) { - SerialMonitor.getInstance().initialize(); - } - })(); - } - - // Debugger configuration - const simulatorDebugConfiguration = new SimulatorDebugConfigurationProvider( - utils.getPathToScript(context, "out/", "debug_user_code.py") - ); - - // On Debug Session Start: Init comunication - const debugSessionsStarted = vscode.debug.onDidStartDebugSession(() => { - if (simulatorDebugConfiguration.deviceSimulatorExpressDebug) { - // Reinitialize process - killProcessIfRunning(); - console.log("Debug Started"); - inDebugMode = true; - - try { - // Shut down existing server on debug restart - if (debuggerCommunicationHandler) { - debuggerCommunicationHandler.closeConnection(); - debuggerCommunicationHandler = undefined; - } - debuggerCommunicationHandler = new DebuggerCommunicationServer( - currentPanel, - utils.getServerPortConfig() - ); - openWebview(); - if (currentPanel) { - debuggerCommunicationHandler.setWebview(currentPanel); - currentPanel.webview.postMessage({ command: "activate-play" }); + // Debugger configuration + const simulatorDebugConfiguration = new SimulatorDebugConfigurationProvider( + utils.getPathToScript(context, "out/", "debug_user_code.py") + ); + + // On Debug Session Start: Init comunication + const debugSessionsStarted = vscode.debug.onDidStartDebugSession(() => { + if (simulatorDebugConfiguration.deviceSimulatorExpressDebug) { + // Reinitialize process + killProcessIfRunning(); + console.log("Debug Started"); + inDebugMode = true; + + try { + // Shut down existing server on debug restart + if (debuggerCommunicationHandler) { + debuggerCommunicationHandler.closeConnection(); + debuggerCommunicationHandler = undefined; + } + + debuggerCommunicationHandler = new DebuggerCommunicationServer( + currentPanel, + utils.getServerPortConfig() + ); + openWebview(); + if (currentPanel) { + debuggerCommunicationHandler.setWebview(currentPanel); + currentPanel.webview.postMessage({ + command: "activate-play", + }); + } + } catch (err) { + if (err.message === SERVER_INFO.ERROR_CODE_INIT_SERVER) { + console.error( + `Error trying to init the server on port ${utils.getServerPortConfig()}` + ); + vscode.window.showErrorMessage( + CONSTANTS.ERROR.DEBUGGER_SERVER_INIT_FAILED( + utils.getServerPortConfig() + ) + ); + } + } } - } catch (err) { - if (err.message === SERVER_INFO.ERROR_CODE_INIT_SERVER) { - console.error( - `Error trying to init the server on port ${utils.getServerPortConfig()}` - ); - vscode.window.showErrorMessage( - CONSTANTS.ERROR.DEBUGGER_SERVER_INIT_FAILED( - utils.getServerPortConfig() - ) - ); + }); + + // On Debug Session Stop: Stop communiation + const debugSessionStopped = vscode.debug.onDidTerminateDebugSession(() => { + if (simulatorDebugConfiguration.deviceSimulatorExpressDebug) { + console.log("Debug Stopped"); + inDebugMode = false; + simulatorDebugConfiguration.deviceSimulatorExpressDebug = false; + if (debuggerCommunicationHandler) { + debuggerCommunicationHandler.closeConnection(); + debuggerCommunicationHandler = undefined; + } + if (currentPanel) { + currentPanel.webview.postMessage({ command: "reset-state" }); + } } - } - } - }); - - // On Debug Session Stop: Stop communiation - const debugSessionStopped = vscode.debug.onDidTerminateDebugSession(() => { - if (simulatorDebugConfiguration.deviceSimulatorExpressDebug) { - console.log("Debug Stopped"); - inDebugMode = false; - simulatorDebugConfiguration.deviceSimulatorExpressDebug = false; - if (debuggerCommunicationHandler) { - debuggerCommunicationHandler.closeConnection(); - debuggerCommunicationHandler = undefined; - } - if (currentPanel) { - currentPanel.webview.postMessage({ command: "reset-state" }); - } - } - }); - - context.subscriptions.push( - changeBaudRate, - closeSerialMonitor, - openSerialMonitor, - openSimulator, - newFile, - runSimulator, - runSimulatorEditorButton, - runDevice, - selectSerialPort, - vscode.debug.registerDebugConfigurationProvider( - CONSTANTS.DEBUG_CONFIGURATION_TYPE, - simulatorDebugConfiguration - ), - debugSessionsStarted, - debugSessionStopped - ); + }); + + context.subscriptions.push( + changeBaudRate, + closeSerialMonitor, + openSerialMonitor, + openSimulator, + newFile, + runSimulator, + runSimulatorEditorButton, + runDevice, + selectSerialPort, + vscode.debug.registerDebugConfigurationProvider( + CONSTANTS.DEBUG_CONFIGURATION_TYPE, + simulatorDebugConfiguration + ), + debugSessionsStarted, + debugSessionStopped + ); } const getActivePythonFile = () => { - const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors; - const activeEditor = editors.find( - editor => editor.document.languageId === "python" - ); - if (activeEditor) { - currentTextDocument = activeEditor.document; - } - return activeEditor ? activeEditor.document.fileName : ""; + const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors; + const activeEditor = editors.find( + editor => editor.document.languageId === "python" + ); + if (activeEditor) { + currentTextDocument = activeEditor.document; + } + return activeEditor ? activeEditor.document.fileName : ""; }; const updateCurrentFileIfPython = async ( - activeTextDocument: vscode.TextDocument | undefined, - currentPanel: vscode.WebviewPanel + activeTextDocument: vscode.TextDocument | undefined, + currentPanel: vscode.WebviewPanel ) => { - if (activeTextDocument && activeTextDocument.languageId === "python") { - setPathAndSendMessage(currentPanel, activeTextDocument.fileName); - currentTextDocument = activeTextDocument; - } else if (currentFileAbsPath === "") { - setPathAndSendMessage(currentPanel, getActivePythonFile() || ""); - } - if ( - currentTextDocument && - utils.getActiveEditorFromPath(currentTextDocument.fileName) === undefined - ) { - await vscode.window.showTextDocument( - currentTextDocument, - vscode.ViewColumn.One - ); - } + if (activeTextDocument && activeTextDocument.languageId === "python") { + setPathAndSendMessage(currentPanel, activeTextDocument.fileName); + currentTextDocument = activeTextDocument; + } else if (currentFileAbsPath === "") { + setPathAndSendMessage(currentPanel, getActivePythonFile() || ""); + } + if ( + currentTextDocument && + utils.getActiveEditorFromPath(currentTextDocument.fileName) === + undefined + ) { + await vscode.window.showTextDocument( + currentTextDocument, + vscode.ViewColumn.One + ); + } }; const handleButtonPressTelemetry = (buttonState: any) => { - if (buttonState.button_a && buttonState.button_b) { - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_BUTTON_AB); - } else if (buttonState.button_a) { - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_BUTTON_A); - } else if (buttonState.button_b) { - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_BUTTON_B); - } else if (buttonState.switch) { - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_SWITCH); - } + if (buttonState.button_a && buttonState.button_b) { + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_BUTTON_AB); + } else if (buttonState.button_a) { + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_BUTTON_A); + } else if (buttonState.button_b) { + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_BUTTON_B); + } else if (buttonState.switch) { + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_SWITCH); + } }; const handleSensorTelemetry = (sensor: string) => { - switch (sensor) { - case "temperature": - telemetryAI.trackFeatureUsage( - TelemetryEventName.SIMULATOR_TEMPERATURE_SENSOR - ); - break; - case "light": - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_LIGHT_SENSOR); - break; - case "motion_x": - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_MOTION_SENSOR); - break; - case "motion_y": - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_MOTION_SENSOR); - break; - case "motion_z": - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_MOTION_SENSOR); - break; - case "shake": - telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_SHAKE); - break; - case "touch": - telemetryAI.trackFeatureUsage( - TelemetryEventName.SIMULATOR_CAPACITIVE_TOUCH - ); - break; - } + switch (sensor) { + case "temperature": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_TEMPERATURE_SENSOR + ); + break; + case "light": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_LIGHT_SENSOR + ); + break; + case "motion_x": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_MOTION_SENSOR + ); + break; + case "motion_y": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_MOTION_SENSOR + ); + break; + case "motion_z": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_MOTION_SENSOR + ); + break; + case "shake": + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_SHAKE); + break; + case "touch": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_CAPACITIVE_TOUCH + ); + break; + } }; const checkForTelemetry = (sensorState: any) => { - if (sensorState.shake) { - handleSensorTelemetry("shake"); - } else if (sensorState.touch) { - handleSensorTelemetry("touch"); - } + if (sensorState.shake) { + handleSensorTelemetry("shake"); + } else if (sensorState.touch) { + handleSensorTelemetry("touch"); + } }; const updatePythonExtraPaths = () => { - const pathToLib: string = __dirname; - const currentExtraPaths: string[] = - vscode.workspace.getConfiguration().get("python.autoComplete.extraPaths") || - []; - if (!currentExtraPaths.includes(pathToLib)) { - currentExtraPaths.push(pathToLib); - } - vscode.workspace - .getConfiguration() - .update( - "python.autoComplete.extraPaths", - currentExtraPaths, - vscode.ConfigurationTarget.Global - ); + const pathToLib: string = __dirname; + const currentExtraPaths: string[] = + vscode.workspace + .getConfiguration() + .get("python.autoComplete.extraPaths") || []; + if (!currentExtraPaths.includes(pathToLib)) { + currentExtraPaths.push(pathToLib); + } + vscode.workspace + .getConfiguration() + .update( + "python.autoComplete.extraPaths", + currentExtraPaths, + vscode.ConfigurationTarget.Global + ); }; function getWebviewContent(context: vscode.ExtensionContext) { - return ` + return ` @@ -918,7 +1012,7 @@ function getWebviewContent(context: vscode.ExtensionContext) { // this method is called when your extension is deactivated export async function deactivate() { - const monitor: SerialMonitor = SerialMonitor.getInstance(); - await monitor.closeSerialMonitor(null, false); - UsbDetector.getInstance().stopListening(); + const monitor: SerialMonitor = SerialMonitor.getInstance(); + await monitor.closeSerialMonitor(null, false); + UsbDetector.getInstance().stopListening(); } diff --git a/src/extension_utils/dependencyChecker.ts b/src/extension_utils/dependencyChecker.ts index 0dfe59667..de18806cc 100644 --- a/src/extension_utils/dependencyChecker.ts +++ b/src/extension_utils/dependencyChecker.ts @@ -1,5 +1,5 @@ import * as cp from "child_process"; -import * as compareVersions from 'compare-versions'; +import * as compareVersions from "compare-versions"; import * as os from "os"; import * as util from "util"; import { CONSTANTS } from "../constants"; @@ -7,7 +7,6 @@ const exec = util.promisify(cp.exec); interface IPayloadResponse { payload: IDependency; - } interface IDependency { @@ -16,22 +15,29 @@ interface IDependency { } const PYTHON3_REGEX = RegExp("^(Python )(3\\.[0-9]+\\.[0-9]+)"); -const MINIMUM_PYTHON_VERSION = "3.7.0" +const MINIMUM_PYTHON_VERSION = "3.7.0"; export class DependencyChecker { - constructor() { } + constructor() {} - public async checkDependency(dependencyName: string): Promise { + public async checkDependency( + dependencyName: string + ): Promise { let state: boolean = false; if (dependencyName === CONSTANTS.DEPENDENCY_CHECKER.PYTHON) { - if ( - await this.runCommandVersion(CONSTANTS.DEPENDENCY_CHECKER.PYTHON3, MINIMUM_PYTHON_VERSION) + await this.runCommandVersion( + CONSTANTS.DEPENDENCY_CHECKER.PYTHON3, + MINIMUM_PYTHON_VERSION + ) ) { state = true; dependencyName = CONSTANTS.DEPENDENCY_CHECKER.PYTHON3; } else if ( - await this.runCommandVersion(CONSTANTS.DEPENDENCY_CHECKER.PYTHON, MINIMUM_PYTHON_VERSION) + await this.runCommandVersion( + CONSTANTS.DEPENDENCY_CHECKER.PYTHON, + MINIMUM_PYTHON_VERSION + ) ) { state = true; dependencyName = CONSTANTS.DEPENDENCY_CHECKER.PYTHON; @@ -42,20 +48,25 @@ export class DependencyChecker { return { payload: { dependency: dependencyName, - installed: state - } + installed: state, + }, }; } - private async runCommandVersion(command: string, versionDependency?: string) { + private async runCommandVersion( + command: string, + versionDependency?: string + ) { let installed: boolean = false; try { const { stdout } = await exec(command + " --version"); const matches = PYTHON3_REGEX.exec(stdout); if (versionDependency) { - installed = matches ? compareVersions(matches[2], versionDependency) >= 0 : false; + installed = matches + ? compareVersions(matches[2], versionDependency) >= 0 + : false; } else { - installed = true + installed = true; } } catch (err) { installed = false; diff --git a/src/extension_utils/utils.ts b/src/extension_utils/utils.ts index 98b8473b9..a9c61a37f 100644 --- a/src/extension_utils/utils.ts +++ b/src/extension_utils/utils.ts @@ -9,12 +9,12 @@ import * as path from "path"; import * as util from "util"; import * as vscode from "vscode"; import { - CONFIG, - CONSTANTS, - CPX_CONFIG_FILE, - DialogResponses, - SERVER_INFO, - USER_CODE_NAMES + CONFIG, + CONSTANTS, + CPX_CONFIG_FILE, + DialogResponses, + SERVER_INFO, + USER_CODE_NAMES, } from "../constants"; import { CPXWorkspace } from "../cpxWorkspace"; import { DeviceContext } from "../deviceContext"; @@ -24,93 +24,93 @@ const exec = util.promisify(cp.exec); // tslint:disable-next-line: export-name export const getPathToScript = ( - context: vscode.ExtensionContext, - folderName: string, - fileName: string + context: vscode.ExtensionContext, + folderName: string, + fileName: string ) => { - const onDiskPath = vscode.Uri.file( - path.join(context.extensionPath, folderName, fileName) - ); - const scriptPath = onDiskPath.with({ scheme: "vscode-resource" }); - return scriptPath.fsPath; + const onDiskPath = vscode.Uri.file( + path.join(context.extensionPath, folderName, fileName) + ); + const scriptPath = onDiskPath.with({ scheme: "vscode-resource" }); + return scriptPath.fsPath; }; export const validCodeFileName = (filePath: string) => { - return ( - filePath.endsWith(USER_CODE_NAMES.CODE_PY) || - filePath.endsWith(USER_CODE_NAMES.MAIN_PY) - ); + return ( + filePath.endsWith(USER_CODE_NAMES.CODE_PY) || + filePath.endsWith(USER_CODE_NAMES.MAIN_PY) + ); }; export const showPrivacyModal = (okAction: () => void) => { - vscode.window - .showInformationMessage( - `${CONSTANTS.INFO.THIRD_PARTY_WEBSITE}: ${CONSTANTS.LINKS.PRIVACY}`, - DialogResponses.AGREE_AND_PROCEED, - DialogResponses.CANCEL - ) - .then((privacySelection: vscode.MessageItem | undefined) => { - if (privacySelection === DialogResponses.AGREE_AND_PROCEED) { - okAction(); - } else if (privacySelection === DialogResponses.CANCEL) { - // do nothing - } - }); + vscode.window + .showInformationMessage( + `${CONSTANTS.INFO.THIRD_PARTY_WEBSITE}: ${CONSTANTS.LINKS.PRIVACY}`, + DialogResponses.AGREE_AND_PROCEED, + DialogResponses.CANCEL + ) + .then((privacySelection: vscode.MessageItem | undefined) => { + if (privacySelection === DialogResponses.AGREE_AND_PROCEED) { + okAction(); + } else if (privacySelection === DialogResponses.CANCEL) { + // do nothing + } + }); }; export const logToOutputChannel = ( - outChannel: vscode.OutputChannel | undefined, - message: string, - show: boolean = false + outChannel: vscode.OutputChannel | undefined, + message: string, + show: boolean = false ): void => { - if (outChannel) { - if (show) { - outChannel.show(true); + if (outChannel) { + if (show) { + outChannel.show(true); + } + outChannel.append(message); } - outChannel.append(message); - } }; export function tryParseJSON(jsonString: string): any | boolean { - try { - const jsonObj = JSON.parse(jsonString); - if (jsonObj && typeof jsonObj === "object") { - return jsonObj; - } - } catch (exception) { } + try { + const jsonObj = JSON.parse(jsonString); + if (jsonObj && typeof jsonObj === "object") { + return jsonObj; + } + } catch (exception) {} - return false; + return false; } export function fileExistsSync(filePath: string): boolean { - try { - return fs.statSync(filePath).isFile(); - } catch (error) { - return false; - } + try { + return fs.statSync(filePath).isFile(); + } catch (error) { + return false; + } } export function mkdirRecursivelySync(dirPath: string): void { - if (directoryExistsSync(dirPath)) { - return; - } - const dirname = path.dirname(dirPath); - if (path.normalize(dirname) === path.normalize(dirPath)) { - fs.mkdirSync(dirPath); - } else if (directoryExistsSync(dirname)) { - fs.mkdirSync(dirPath); - } else { - mkdirRecursivelySync(dirname); - fs.mkdirSync(dirPath); - } + if (directoryExistsSync(dirPath)) { + return; + } + const dirname = path.dirname(dirPath); + if (path.normalize(dirname) === path.normalize(dirPath)) { + fs.mkdirSync(dirPath); + } else if (directoryExistsSync(dirname)) { + fs.mkdirSync(dirPath); + } else { + mkdirRecursivelySync(dirname); + fs.mkdirSync(dirPath); + } } export function directoryExistsSync(dirPath: string): boolean { - try { - return fs.statSync(dirPath).isDirectory(); - } catch (e) { - return false; - } + try { + return fs.statSync(dirPath).isDirectory(); + } catch (e) { + return false; + } } /** @@ -119,215 +119,257 @@ export function directoryExistsSync(dirPath: string): boolean { * The padding is applied from the start (left) of the current string. */ export function padStart( - sourceString: string, - targetLength: number, - padString?: string + sourceString: string, + targetLength: number, + padString?: string ): string { - if (!sourceString) { - return sourceString; - } + if (!sourceString) { + return sourceString; + } - if (!(String.prototype as any).padStart) { - // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js - padString = String(padString || " "); - if (sourceString.length > targetLength) { - return sourceString; + if (!(String.prototype as any).padStart) { + // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js + padString = String(padString || " "); + if (sourceString.length > targetLength) { + return sourceString; + } else { + targetLength = targetLength - sourceString.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length); // append to original to ensure we are longer than needed + } + return padString.slice(0, targetLength) + sourceString; + } } else { - targetLength = targetLength - sourceString.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); // append to original to ensure we are longer than needed - } - return padString.slice(0, targetLength) + sourceString; + return (sourceString as any).padStart(targetLength, padString); } - } else { - return (sourceString as any).padStart(targetLength, padString); - } } export function convertToHex(num: number, width = 0): string { - return padStart(num.toString(16), width, "0"); + return padStart(num.toString(16), width, "0"); } export function generateCPXConfig(): void { - const deviceContext: DeviceContext = DeviceContext.getInstance(); - const cpxJson = { - port: deviceContext.port - }; - const cpxConfigFilePath: string = path.join( - CPXWorkspace.rootPath, - CPX_CONFIG_FILE - ); - mkdirRecursivelySync(path.dirname(cpxConfigFilePath)); - fs.writeFileSync(cpxConfigFilePath, JSON.stringify(cpxJson, null, 4)); + const deviceContext: DeviceContext = DeviceContext.getInstance(); + const cpxJson = { + port: deviceContext.port, + }; + const cpxConfigFilePath: string = path.join( + CPXWorkspace.rootPath, + CPX_CONFIG_FILE + ); + mkdirRecursivelySync(path.dirname(cpxConfigFilePath)); + fs.writeFileSync(cpxConfigFilePath, JSON.stringify(cpxJson, null, 4)); } export const checkPythonDependency = async () => { - const dependencyChecker: DependencyChecker = new DependencyChecker(); - const result = await dependencyChecker.checkDependency( - CONSTANTS.DEPENDENCY_CHECKER.PYTHON - ); - return result.payload; + const dependencyChecker: DependencyChecker = new DependencyChecker(); + const result = await dependencyChecker.checkDependency( + CONSTANTS.DEPENDENCY_CHECKER.PYTHON + ); + return result.payload; }; export const checkPipDependency = async () => { - const dependencyChecker: DependencyChecker = new DependencyChecker(); - const result = await dependencyChecker.checkDependency( - CONSTANTS.DEPENDENCY_CHECKER.PIP3 - ); - return result.payload; + const dependencyChecker: DependencyChecker = new DependencyChecker(); + const result = await dependencyChecker.checkDependency( + CONSTANTS.DEPENDENCY_CHECKER.PIP3 + ); + return result.payload; }; export const setPythonExectuableName = async () => { - // Find our what command is the PATH for python - let executableName: string = ""; - const dependencyCheck = await checkPythonDependency(); - if (dependencyCheck.installed) { - executableName = dependencyCheck.dependency; - } else { - vscode.window - .showErrorMessage( - CONSTANTS.ERROR.NO_PYTHON_PATH, - DialogResponses.INSTALL_PYTHON - ) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.INSTALL_PYTHON) { - const okAction = () => { - open(CONSTANTS.LINKS.DOWNLOAD_PYTHON); - }; - showPrivacyModal(okAction); - } - }); - } + // Find our what command is the PATH for python + let executableName: string = ""; + const dependencyCheck = await checkPythonDependency(); + if (dependencyCheck.installed) { + executableName = dependencyCheck.dependency; + } else { + vscode.window + .showErrorMessage( + CONSTANTS.ERROR.NO_PYTHON_PATH, + DialogResponses.INSTALL_PYTHON + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.INSTALL_PYTHON) { + const okAction = () => { + open(CONSTANTS.LINKS.DOWNLOAD_PYTHON); + }; + showPrivacyModal(okAction); + } + }); + } - return executableName; + return executableName; }; export const addVisibleTextEditorCallback = ( - currentPanel: vscode.WebviewPanel, - context: vscode.ExtensionContext + currentPanel: vscode.WebviewPanel, + context: vscode.ExtensionContext ): vscode.Disposable => { - const initialPythonEditors = filterForPythonFiles( - vscode.window.visibleTextEditors - ); - currentPanel.webview.postMessage({ - command: "visible-editors", - state: { activePythonEditors: initialPythonEditors } - }); - return vscode.window.onDidChangeVisibleTextEditors( - (textEditors: vscode.TextEditor[]) => { - const activePythonEditors = filterForPythonFiles(textEditors); - currentPanel.webview.postMessage({ + const initialPythonEditors = filterForPythonFiles( + vscode.window.visibleTextEditors + ); + currentPanel.webview.postMessage({ command: "visible-editors", - state: { activePythonEditors } - }); - }, - {}, - context.subscriptions - ); + state: { activePythonEditors: initialPythonEditors }, + }); + return vscode.window.onDidChangeVisibleTextEditors( + (textEditors: vscode.TextEditor[]) => { + const activePythonEditors = filterForPythonFiles(textEditors); + currentPanel.webview.postMessage({ + command: "visible-editors", + state: { activePythonEditors }, + }); + }, + {}, + context.subscriptions + ); }; export const filterForPythonFiles = (textEditors: vscode.TextEditor[]) => { - return textEditors - .filter(editor => editor.document.languageId === "python") - .map(editor => editor.document.fileName); + return textEditors + .filter(editor => editor.document.languageId === "python") + .map(editor => editor.document.fileName); }; export const getActiveEditorFromPath = ( - filePath: string + filePath: string ): vscode.TextDocument => { - const activeEditor = vscode.window.visibleTextEditors.find( - (editor: vscode.TextEditor) => editor.document.fileName === filePath - ); - return activeEditor ? activeEditor.document : undefined; + const activeEditor = vscode.window.visibleTextEditors.find( + (editor: vscode.TextEditor) => editor.document.fileName === filePath + ); + return activeEditor ? activeEditor.document : undefined; }; export const getServerPortConfig = (): number => { - // tslint:disable: no-backbone-get-set-outside-model prefer-type-cast - if ( - vscode.workspace - .getConfiguration() - .has(SERVER_INFO.SERVER_PORT_CONFIGURATION) - ) { - return vscode.workspace - .getConfiguration() - .get(SERVER_INFO.SERVER_PORT_CONFIGURATION) as number; - } - return SERVER_INFO.DEFAULT_SERVER_PORT; + // tslint:disable: no-backbone-get-set-outside-model prefer-type-cast + if ( + vscode.workspace + .getConfiguration() + .has(SERVER_INFO.SERVER_PORT_CONFIGURATION) + ) { + return vscode.workspace + .getConfiguration() + .get(SERVER_INFO.SERVER_PORT_CONFIGURATION) as number; + } + return SERVER_INFO.DEFAULT_SERVER_PORT; }; export const checkConfig = (configName: string): boolean => { - return vscode.workspace.getConfiguration().get(configName) === true; + return vscode.workspace.getConfiguration().get(configName) === true; }; -export const checkPythonDependencies = async (context: vscode.ExtensionContext, pythonExecutable: string) => { - let hasInstalledDependencies: boolean = false; - const pathToLibs: string = getPathToScript(context, CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, CONSTANTS.FILESYSTEM.PYTHON_LIBS_DIR); - if (checkPipDependency() && checkPythonDependency()) { - if (checkConfig(CONFIG.SHOW_DEPENDENCY_INSTALL)) { - // check if ./out/python_libs exists; if not, the dependencies - // for adafruit_circuitpython are not (successfully) installed yet - hasInstalledDependencies = fs.existsSync(pathToLibs) || await promptInstallPythonDependencies(context, pythonExecutable, pathToLibs); +export const checkPythonDependencies = async ( + context: vscode.ExtensionContext, + pythonExecutable: string +) => { + let hasInstalledDependencies: boolean = false; + const pathToLibs: string = getPathToScript( + context, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + CONSTANTS.FILESYSTEM.PYTHON_LIBS_DIR + ); + if (checkPipDependency() && checkPythonDependency()) { + if (checkConfig(CONFIG.SHOW_DEPENDENCY_INSTALL)) { + // check if ./out/python_libs exists; if not, the dependencies + // for adafruit_circuitpython are not (successfully) installed yet + hasInstalledDependencies = + fs.existsSync(pathToLibs) || + (await promptInstallPythonDependencies( + context, + pythonExecutable, + pathToLibs + )); + } + } else { + hasInstalledDependencies = false; } - } else { - hasInstalledDependencies = false; - } - return hasInstalledDependencies; + return hasInstalledDependencies; }; -export const promptInstallPythonDependencies = (context: vscode.ExtensionContext, pythonExecutable: string, pathToLibs: string) => { - return vscode.window.showInformationMessage( - CONSTANTS.INFO.INSTALL_PYTHON_DEPENDENCIES, - DialogResponses.YES, - DialogResponses.NO) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.YES) { - return installPythonDependencies(context, pythonExecutable, pathToLibs); - } else if (selection === DialogResponses.NO) { - return vscode.window.showInformationMessage( - CONSTANTS.INFO.ARE_YOU_SURE, - DialogResponses.INSTALL_NOW, - DialogResponses.DONT_INSTALL - ).then((installChoice: vscode.MessageItem | undefined) => { - if (installChoice === DialogResponses.INSTALL_NOW) { - return installPythonDependencies(context, pythonExecutable, pathToLibs); - } else { - return false; - } - }) - } - }); +export const promptInstallPythonDependencies = ( + context: vscode.ExtensionContext, + pythonExecutable: string, + pathToLibs: string +) => { + return vscode.window + .showInformationMessage( + CONSTANTS.INFO.INSTALL_PYTHON_DEPENDENCIES, + DialogResponses.YES, + DialogResponses.NO + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.YES) { + return installPythonDependencies( + context, + pythonExecutable, + pathToLibs + ); + } else if (selection === DialogResponses.NO) { + return vscode.window + .showInformationMessage( + CONSTANTS.INFO.ARE_YOU_SURE, + DialogResponses.INSTALL_NOW, + DialogResponses.DONT_INSTALL + ) + .then((installChoice: vscode.MessageItem | undefined) => { + if (installChoice === DialogResponses.INSTALL_NOW) { + return installPythonDependencies( + context, + pythonExecutable, + pathToLibs + ); + } else { + return false; + } + }); + } + }); }; export const getTelemetryState = () => { - return vscode.workspace - .getConfiguration() - .get("telemetry.enableTelemetry", true); + return vscode.workspace + .getConfiguration() + .get("telemetry.enableTelemetry", true); }; -export const installPythonDependencies = async (context: vscode.ExtensionContext, pythonExecutable: string, pathToLibs: string) => { - let installed: boolean = false; - try { - vscode.window.showInformationMessage(CONSTANTS.INFO.INSTALLING_PYTHON_DEPENDENCIES); - - const requirementsPath: string = getPathToScript(context, CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, "requirements.txt"); +export const installPythonDependencies = async ( + context: vscode.ExtensionContext, + pythonExecutable: string, + pathToLibs: string +) => { + let installed: boolean = false; + try { + vscode.window.showInformationMessage( + CONSTANTS.INFO.INSTALLING_PYTHON_DEPENDENCIES + ); - // run command to download dependencies to out/python_libs - const { stdout } = await exec(`${pythonExecutable} -m pip install -r ${requirementsPath} -t ${pathToLibs}`); - console.info(stdout); - installed = true; + const requirementsPath: string = getPathToScript( + context, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + "requirements.txt" + ); - vscode.window.showInformationMessage(CONSTANTS.INFO.SUCCESSFUL_INSTALL); - } catch (err) { + // run command to download dependencies to out/python_libs + const { stdout } = await exec( + `${pythonExecutable} -m pip install -r ${requirementsPath} -t ${pathToLibs}` + ); + console.info(stdout); + installed = true; - vscode.window - .showErrorMessage(CONSTANTS.ERROR.DEPENDENCY_DOWNLOAD_ERROR, - DialogResponses.READ_INSTALL_MD) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.READ_INSTALL_MD) { - open(CONSTANTS.LINKS.INSTALL); - } - }); + vscode.window.showInformationMessage(CONSTANTS.INFO.SUCCESSFUL_INSTALL); + } catch (err) { + vscode.window + .showErrorMessage( + CONSTANTS.ERROR.DEPENDENCY_DOWNLOAD_ERROR, + DialogResponses.READ_INSTALL_MD + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.READ_INSTALL_MD) { + open(CONSTANTS.LINKS.INSTALL); + } + }); - console.error(err); - installed = false; - } - return installed -} + console.error(err); + installed = false; + } + return installed; +}; diff --git a/src/process_user_code.py b/src/process_user_code.py index ad2fcac7b..1991f9816 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -13,8 +13,7 @@ # Insert absolute path to python libraries into sys.path abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) -abs_path_to_lib = os.path.join( - abs_path_to_parent_dir, CONSTANTS.PYTHON_LIBS_DIR) +abs_path_to_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.PYTHON_LIBS_DIR) sys.path.insert(0, abs_path_to_lib) read_val = "" @@ -25,8 +24,7 @@ # Insert absolute path to Adafruit library into sys.path abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) -abs_path_to_lib = os.path.join( - abs_path_to_parent_dir, CONSTANTS.LIBRARY_NAME) +abs_path_to_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.LIBRARY_NAME) sys.path.insert(0, abs_path_to_lib) # This import must happen after the sys.path is modified @@ -34,10 +32,8 @@ from adafruit_circuitplayground.telemetry import telemetry_py - # Handle User Inputs Thread class UserInput(threading.Thread): - def __init__(self): threading.Thread.__init__(self) @@ -49,11 +45,11 @@ def run(self): new_state = json.loads(read_val) for event in CONSTANTS.EXPECTED_INPUT_EVENTS: cpx._Express__state[event] = new_state.get( - event, cpx._Express__state[event]) + event, cpx._Express__state[event] + ) except Exception as e: - print(CONSTANTS.ERROR_SENDING_EVENT, - e, file=sys.stderr, flush=True) + print(CONSTANTS.ERROR_SENDING_EVENT, e, file=sys.stderr, flush=True) user_input = UserInput() @@ -66,7 +62,7 @@ def handle_user_prints(): global user_stdout while True: if user_stdout.getvalue(): - message = {'type': 'print', 'data': user_stdout.getvalue()} + message = {"type": "print", "data": user_stdout.getvalue()} print(json.dumps(message), file=sys.__stdout__, flush=True) user_stdout.truncate(0) user_stdout.seek(0) @@ -84,25 +80,24 @@ def execute_user_code(abs_path_to_code_file): with open(abs_path_to_code_file) as user_code_file: user_code = user_code_file.read() try: - codeObj = compile(user_code, abs_path_to_code_file, - CONSTANTS.EXEC_COMMAND) + codeObj = compile(user_code, abs_path_to_code_file, CONSTANTS.EXEC_COMMAND) exec(codeObj, {}) sys.stdout.flush() except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() errorMessage = CONSTANTS.ERROR_TRACEBACK - stackTrace = traceback.format_exception( - exc_type, exc_value, exc_traceback) + stackTrace = traceback.format_exception(exc_type, exc_value, exc_traceback) for frameIndex in range(2, len(stackTrace) - 1): - errorMessage += '\t' + str(stackTrace[frameIndex]) + errorMessage += "\t" + str(stackTrace[frameIndex]) print(e, errorMessage, file=sys.stderr, flush=True) user_code = threading.Thread(args=(sys.argv[1],), target=execute_user_code) telemetry_state = json.loads(sys.argv[2]) telemetry_py._Telemetry__enable_telemetry = telemetry_state.get( - CONSTANTS.ENABLE_TELEMETRY, True) + CONSTANTS.ENABLE_TELEMETRY, True +) threads.append(user_code) user_code.start() diff --git a/src/python_constants.py b/src/python_constants.py index 25837377b..a700a3143 100644 --- a/src/python_constants.py +++ b/src/python_constants.py @@ -3,7 +3,7 @@ CPX_DRIVE_NAME = "CIRCUITPY" -ENABLE_TELEMETRY = 'enable_telemetry' +ENABLE_TELEMETRY = "enable_telemetry" EXPECTED_INPUT_EVENTS = [ "button_a", "button_b", @@ -14,7 +14,7 @@ "motion_x", "motion_y", "motion_z", - "touch" + "touch", ] EXEC_COMMAND = "exec" @@ -29,7 +29,9 @@ MOUNT_COMMAND = "mount" NO_CPX_DETECTED_ERROR_TITLE = "No Circuit Playground Express detected" -NO_CPX_DETECTED_ERROR_DETAIL = "Could not find drive with name 'CIRCUITPYTHON'. Detected OS: {}" +NO_CPX_DETECTED_ERROR_DETAIL = ( + "Could not find drive with name 'CIRCUITPYTHON'. Detected OS: {}" +) NOT_SUPPORTED_OS = 'The OS "{}" not supported.' NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" diff --git a/src/requirements.txt b/src/requirements.txt index c2fd41ef2..2f1574582 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,3 +1,4 @@ +black==19.10b0 playsound==1.2.2 pytest==5.0.1 applicationinsights==0.11.9 diff --git a/src/serialMonitor.ts b/src/serialMonitor.ts index 6cb687d39..630154859 100644 --- a/src/serialMonitor.ts +++ b/src/serialMonitor.ts @@ -18,11 +18,23 @@ export interface ISerialPortDetail { } export class SerialMonitor implements vscode.Disposable { - public static DEFAULT_BAUD_RATE: number = 115200; public static listBaudRates(): number[] { - return [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 250000]; + return [ + 300, + 1200, + 2400, + 4800, + 9600, + 19200, + 38400, + 57600, + 74880, + 115200, + 230400, + 250000, + ]; } public static getInstance(): SerialMonitor { @@ -56,21 +68,35 @@ export class SerialMonitor implements vscode.Disposable { public initialize() { const defaultBaudRate: number = SerialMonitor.DEFAULT_BAUD_RATE; - this._outputChannel = vscode.window.createOutputChannel(CONSTANTS.MISC.SERIAL_MONITOR_NAME); + this._outputChannel = vscode.window.createOutputChannel( + CONSTANTS.MISC.SERIAL_MONITOR_NAME + ); this._currentBaudRate = defaultBaudRate; - this._portsStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, STATUS_BAR_PRIORITY.PORT); - this._portsStatusBar.command = "deviceSimulatorExpress.selectSerialPort"; + this._portsStatusBar = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + STATUS_BAR_PRIORITY.PORT + ); + this._portsStatusBar.command = + "deviceSimulatorExpress.selectSerialPort"; this._portsStatusBar.tooltip = "Select Serial Port"; this._portsStatusBar.show(); - this._openPortStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, STATUS_BAR_PRIORITY.OPEN_PORT); - this._openPortStatusBar.command = "deviceSimulatorExpress.openSerialMonitor"; + this._openPortStatusBar = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + STATUS_BAR_PRIORITY.OPEN_PORT + ); + this._openPortStatusBar.command = + "deviceSimulatorExpress.openSerialMonitor"; this._openPortStatusBar.text = `$(plug)`; this._openPortStatusBar.tooltip = "Open Serial Monitor"; this._openPortStatusBar.show(); - this._baudRateStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, STATUS_BAR_PRIORITY.BAUD_RATE); - this._baudRateStatusBar.command = "deviceSimulatorExpress.changeBaudRate"; + this._baudRateStatusBar = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + STATUS_BAR_PRIORITY.BAUD_RATE + ); + this._baudRateStatusBar.command = + "deviceSimulatorExpress.changeBaudRate"; this._baudRateStatusBar.tooltip = "Baud Rate"; this._baudRateStatusBar.text = defaultBaudRate.toString(); this.updatePortListStatus(null); @@ -79,31 +105,50 @@ export class SerialMonitor implements vscode.Disposable { public async selectSerialPort(vid: string | null, pid: string | null) { const lists = await SerialPortControl.list(); if (!lists.length) { - vscode.window.showInformationMessage("No serial message is available."); + vscode.window.showInformationMessage( + "No serial message is available." + ); return; } if (vid && pid) { const valueOfVid = parseInt(vid, 16); const valueOfPid = parseInt(pid, 16); - const foundPort = lists.find((port) => { + const foundPort = lists.find(port => { if (port.productId && port.vendorId) { - return parseInt(port.productId, 16) === valueOfPid && parseInt(port.vendorId, 16) === valueOfVid; + return ( + parseInt(port.productId, 16) === valueOfPid && + parseInt(port.vendorId, 16) === valueOfVid + ); } return false; }); - if (foundPort && !(this._serialPortControl && this._serialPortControl.isActive)) { + if ( + foundPort && + !(this._serialPortControl && this._serialPortControl.isActive) + ) { this.updatePortListStatus(foundPort.comName); } } else { - const chosen = await vscode.window.showQuickPick(lists.map((port: ISerialPortDetail): vscode.QuickPickItem => { - return { - description: port.manufacturer, - label: port.comName - }; - }).sort((a, b): number => { - return a.label === b.label ? 0 : (a.label > b.label ? 1 : -1); - }) as vscode.QuickPickItem[], { placeHolder: CONSTANTS.MISC.SELECT_PORT_PLACEHOLDER}); + const chosen = await vscode.window.showQuickPick( + lists + .map( + (port: ISerialPortDetail): vscode.QuickPickItem => { + return { + description: port.manufacturer, + label: port.comName, + }; + } + ) + .sort((a, b): number => { + return a.label === b.label + ? 0 + : a.label > b.label + ? 1 + : -1; + }) as vscode.QuickPickItem[], + { placeHolder: CONSTANTS.MISC.SELECT_PORT_PLACEHOLDER } + ); if (chosen && chosen.label) { this.updatePortListStatus(chosen.label); @@ -116,7 +161,7 @@ export class SerialMonitor implements vscode.Disposable { const ans = await vscode.window.showInformationMessage( CONSTANTS.WARNING.NO_SERIAL_PORT_SELECTED, DialogResponses.YES, - DialogResponses.NO, + DialogResponses.NO ); if (ans === DialogResponses.YES) { await this.selectSerialPort(null, null); @@ -130,15 +175,23 @@ export class SerialMonitor implements vscode.Disposable { if (this._currentPort !== this._serialPortControl.currentPort) { await this._serialPortControl.changePort(this._currentPort); } else if (this._serialPortControl.isActive) { - vscode.window.showWarningMessage(`Serial Monitor is already opened for ${this._currentPort}`); + vscode.window.showWarningMessage( + `Serial Monitor is already opened for ${this._currentPort}` + ); return; } } else { - this._serialPortControl = new SerialPortControl(this._currentPort, this._currentBaudRate, this._outputChannel); + this._serialPortControl = new SerialPortControl( + this._currentPort, + this._currentBaudRate, + this._outputChannel + ); } if (!this._serialPortControl.currentPort) { - console.error(CONSTANTS.ERROR.FAILED_TO_OPEN_SERIAL_PORT(this._currentPort)); + console.error( + CONSTANTS.ERROR.FAILED_TO_OPEN_SERIAL_PORT(this._currentPort) + ); return; } @@ -146,12 +199,19 @@ export class SerialMonitor implements vscode.Disposable { await this._serialPortControl.open(); this.updatePortStatus(true); } catch (error) { - logToOutputChannel(outChannel, CONSTANTS.ERROR.FAILED_TO_OPEN_SERIAL_PORT_DUE_TO(this._currentPort, error), true); + logToOutputChannel( + outChannel, + CONSTANTS.ERROR.FAILED_TO_OPEN_SERIAL_PORT_DUE_TO( + this._currentPort, + error + ), + true + ); } } public get initialized(): boolean { - return !!this._outputChannel; + return !!this._outputChannel; } public dispose() { @@ -162,20 +222,34 @@ export class SerialMonitor implements vscode.Disposable { public async changeBaudRate() { const baudRates = SerialMonitor.listBaudRates(); - const chosen = await vscode.window.showQuickPick(baudRates.map((baudRate) => baudRate.toString())); + const chosen = await vscode.window.showQuickPick( + baudRates.map(baudRate => baudRate.toString()) + ); if (!chosen) { - logToOutputChannel(outChannel, CONSTANTS.WARNING.NO_RATE_SELECTED, true); + logToOutputChannel( + outChannel, + CONSTANTS.WARNING.NO_RATE_SELECTED, + true + ); return; } if (!parseInt(chosen, 10)) { - logToOutputChannel(outChannel, CONSTANTS.WARNING.INVALID_BAUD_RATE, true); + logToOutputChannel( + outChannel, + CONSTANTS.WARNING.INVALID_BAUD_RATE, + true + ); return; } if (!this._serialPortControl) { - logToOutputChannel(outChannel, CONSTANTS.WARNING.SERIAL_MONITOR_NOT_STARTED, true); + logToOutputChannel( + outChannel, + CONSTANTS.WARNING.SERIAL_MONITOR_NOT_STARTED, + true + ); return; } @@ -195,19 +269,25 @@ export class SerialMonitor implements vscode.Disposable { this.updatePortStatus(false); return result; } else if (!port && showWarning) { - logToOutputChannel(outChannel, CONSTANTS.WARNING.SERIAL_PORT_NOT_STARTED, true); + logToOutputChannel( + outChannel, + CONSTANTS.WARNING.SERIAL_PORT_NOT_STARTED, + true + ); return false; } } private updatePortStatus(isOpened: boolean) { if (isOpened) { - this._openPortStatusBar.command = "deviceSimulatorExpress.closeSerialMonitor"; + this._openPortStatusBar.command = + "deviceSimulatorExpress.closeSerialMonitor"; this._openPortStatusBar.text = `$(x)`; this._openPortStatusBar.tooltip = "Close Serial Monitor"; this._baudRateStatusBar.show(); } else { - this._openPortStatusBar.command = "deviceSimulatorExpress.openSerialMonitor"; + this._openPortStatusBar.command = + "deviceSimulatorExpress.openSerialMonitor"; this._openPortStatusBar.text = `$(plug)`; this._openPortStatusBar.tooltip = "Open Serial Monitor"; this._baudRateStatusBar.hide(); diff --git a/src/serialPortControl.ts b/src/serialPortControl.ts index dc76bd0b0..cbc79c3bb 100644 --- a/src/serialPortControl.ts +++ b/src/serialPortControl.ts @@ -18,20 +18,22 @@ interface ISerialPortDetail { export class SerialPortControl { public static get serialport(): any { if (!SerialPortControl._serialport) { - SerialPortControl._serialport = require("../vendor/node-usb-native").SerialPort + SerialPortControl._serialport = require("../vendor/node-usb-native").SerialPort; } return SerialPortControl._serialport; } public static list(): Promise { return new Promise((resolve, reject) => { - SerialPortControl.serialport.list((error: any, ports: ISerialPortDetail[]) => { - if (error) { - reject(error); - } else { - resolve(ports); + SerialPortControl.serialport.list( + (error: any, ports: ISerialPortDetail[]) => { + if (error) { + reject(error); + } else { + resolve(ports); + } } - }); + ); }); } @@ -41,7 +43,11 @@ export class SerialPortControl { private _currentBaudRate: number; private _currentSerialPort: any; - public constructor(port: string, baudRate: number, private _outputChannel: OutputChannel) { + public constructor( + port: string, + baudRate: number, + private _outputChannel: OutputChannel + ) { this._currentPort = port; this._currentBaudRate = baudRate; } @@ -55,7 +61,10 @@ export class SerialPortControl { } public open(): Promise { - logToOutputChannel(this._outputChannel, CONSTANTS.INFO.OPENING_SERIAL_PORT(this._currentPort)); + logToOutputChannel( + this._outputChannel, + CONSTANTS.INFO.OPENING_SERIAL_PORT(this._currentPort) + ); return new Promise((resolve, reject) => { if (this._currentSerialPort && this._currentSerialPort.isOpen()) { this._currentSerialPort.close((err: any) => { @@ -63,26 +72,56 @@ export class SerialPortControl { return reject(err); } this._currentSerialPort = null; - return this.open().then(() => { - resolve(); - }, (error) => { - reject(error); - }); + return this.open().then( + () => { + resolve(); + }, + error => { + reject(error); + } + ); }); } else { - this._currentSerialPort = new SerialPortControl.serialport(this._currentPort, { baudRate: this._currentBaudRate }); + this._currentSerialPort = new SerialPortControl.serialport( + this._currentPort, + { baudRate: this._currentBaudRate } + ); this._outputChannel.show(); this._currentSerialPort.on("open", () => { - this._currentSerialPort.write(CONSTANTS.MISC.SERIAL_MONITOR_TEST_IF_OPEN, "Both NL & CR", (err: any) => { - if (err && !(err.message.indexOf(CONSTANTS.ERROR.COMPORT_UNKNOWN_ERROR) >= 0)) { - logToOutputChannel(this._outputChannel, CONSTANTS.ERROR.FAILED_TO_OPEN_SERIAL_PORT(this._currentPort)); - logToOutputChannel(this._outputChannel, CONSTANTS.ERROR.RECONNECT_DEVICE); - reject(err); - } else { - logToOutputChannel(this._outputChannel, CONSTANTS.INFO.OPENED_SERIAL_PORT(this._currentPort)); - resolve(); + this._currentSerialPort.write( + CONSTANTS.MISC.SERIAL_MONITOR_TEST_IF_OPEN, + "Both NL & CR", + (err: any) => { + if ( + err && + !( + err.message.indexOf( + CONSTANTS.ERROR.COMPORT_UNKNOWN_ERROR + ) >= 0 + ) + ) { + logToOutputChannel( + this._outputChannel, + CONSTANTS.ERROR.FAILED_TO_OPEN_SERIAL_PORT( + this._currentPort + ) + ); + logToOutputChannel( + this._outputChannel, + CONSTANTS.ERROR.RECONNECT_DEVICE + ); + reject(err); + } else { + logToOutputChannel( + this._outputChannel, + CONSTANTS.INFO.OPENED_SERIAL_PORT( + this._currentPort + ) + ); + resolve(); + } } - }); + ); }); } @@ -120,21 +159,24 @@ export class SerialPortControl { public stop(): Promise { return new Promise((resolve, reject) => { - if (!this._currentSerialPort || !this.isActive) { - resolve(false); - return; - } - this._currentSerialPort.close((error: any) => { - if (this._outputChannel) { - logToOutputChannel(this._outputChannel, CONSTANTS.INFO.CLOSED_SERIAL_PORT(this._currentPort)); - } - this._currentSerialPort = null; - if (error) { - reject(error); - } else { - resolve(true); + if (!this._currentSerialPort || !this.isActive) { + resolve(false); + return; } - }); + this._currentSerialPort.close((error: any) => { + if (this._outputChannel) { + logToOutputChannel( + this._outputChannel, + CONSTANTS.INFO.CLOSED_SERIAL_PORT(this._currentPort) + ); + } + this._currentSerialPort = null; + if (error) { + reject(error); + } else { + resolve(true); + } + }); }); } @@ -145,13 +187,16 @@ export class SerialPortControl { resolve(); return; } - this._currentSerialPort.update({ baudRate: this._currentBaudRate }, (error: any) => { - if (error) { - reject(error); - } else { - resolve(); + this._currentSerialPort.update( + { baudRate: this._currentBaudRate }, + (error: any) => { + if (error) { + reject(error); + } else { + resolve(); + } } - }); + ); }); } -} \ No newline at end of file +} diff --git a/src/simulatorDebugConfigurationProvider.ts b/src/simulatorDebugConfigurationProvider.ts index e1f028a8f..e0b711016 100644 --- a/src/simulatorDebugConfigurationProvider.ts +++ b/src/simulatorDebugConfigurationProvider.ts @@ -4,95 +4,103 @@ import * as vscode from "vscode"; import { CONSTANTS, DialogResponses } from "./constants"; import { - getServerPortConfig, - validCodeFileName + getServerPortConfig, + validCodeFileName, } from "./extension_utils/utils"; let shouldShowInvalidFileNamePopup: boolean = true; export class SimulatorDebugConfigurationProvider - implements vscode.DebugConfigurationProvider { - public deviceSimulatorExpressDebug: boolean; + implements vscode.DebugConfigurationProvider { + public deviceSimulatorExpressDebug: boolean; - constructor(private pathToScript: string) { - this.deviceSimulatorExpressDebug = false; - } + constructor(private pathToScript: string) { + this.deviceSimulatorExpressDebug = false; + } - /** - * Modify the debug configuration just before a debug session is being launched. - */ - public resolveDebugConfiguration( - folder: vscode.WorkspaceFolder | undefined, - config: vscode.DebugConfiguration, - token?: vscode.CancellationToken - ): vscode.ProviderResult { - const activeTextEditor = vscode.window.activeTextEditor; + /** + * Modify the debug configuration just before a debug session is being launched. + */ + public resolveDebugConfiguration( + folder: vscode.WorkspaceFolder | undefined, + config: vscode.DebugConfiguration, + token?: vscode.CancellationToken + ): vscode.ProviderResult { + const activeTextEditor = vscode.window.activeTextEditor; - // Create a configuration if no launch.json exists or if it's empty - if (!config.type && !config.request && !config.name) { - if ( - activeTextEditor && - activeTextEditor.document.languageId === "python" - ) { - config.type = "deviceSimulatorExpress"; - config.request = "launch"; - config.name = "Device Simulator Express Debugger"; - config.console = "integratedTerminal"; - } - } - // Check config type - if (config.type === CONSTANTS.DEBUG_CONFIGURATION_TYPE) { - this.deviceSimulatorExpressDebug = true; - if (activeTextEditor) { - const currentFilePath = activeTextEditor.document.fileName; + // Create a configuration if no launch.json exists or if it's empty + if (!config.type && !config.request && !config.name) { + if ( + activeTextEditor && + activeTextEditor.document.languageId === "python" + ) { + config.type = "deviceSimulatorExpress"; + config.request = "launch"; + config.name = "Device Simulator Express Debugger"; + config.console = "integratedTerminal"; + } + } + // Check config type + if (config.type === CONSTANTS.DEBUG_CONFIGURATION_TYPE) { + this.deviceSimulatorExpressDebug = true; + if (activeTextEditor) { + const currentFilePath = activeTextEditor.document.fileName; - // Check file type and name - if (!(activeTextEditor.document.languageId === "python")) { - return vscode.window - .showErrorMessage(CONSTANTS.ERROR.INVALID_FILE_EXTENSION_DEBUG) - .then(() => { - return undefined; // Abort launch - }); - } else if ( - !validCodeFileName(currentFilePath) && - shouldShowInvalidFileNamePopup - ) { - vscode.window - .showInformationMessage( - CONSTANTS.INFO.INVALID_FILE_NAME_DEBUG, - ...[DialogResponses.DONT_SHOW, DialogResponses.MESSAGE_UNDERSTOOD] - ) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.DONT_SHOW) { - shouldShowInvalidFileNamePopup = false; - } - }); + // Check file type and name + if (!(activeTextEditor.document.languageId === "python")) { + return vscode.window + .showErrorMessage( + CONSTANTS.ERROR.INVALID_FILE_EXTENSION_DEBUG + ) + .then(() => { + return undefined; // Abort launch + }); + } else if ( + !validCodeFileName(currentFilePath) && + shouldShowInvalidFileNamePopup + ) { + vscode.window + .showInformationMessage( + CONSTANTS.INFO.INVALID_FILE_NAME_DEBUG, + ...[ + DialogResponses.DONT_SHOW, + DialogResponses.MESSAGE_UNDERSTOOD, + ] + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.DONT_SHOW) { + shouldShowInvalidFileNamePopup = false; + } + }); + } + // Set the new configuration type so the python debugger can take over + config.type = "python"; + // Set process_user_code path as program + config.program = this.pathToScript; + // Set user's code path and server's port as args + config.args = [ + currentFilePath, + getServerPortConfig().toString(), + ]; + // Set rules + config.rules = [ + { path: this.pathToScript, include: false }, + { + module: "adafruit_circuitplayground", + include: false, + }, + { module: "playsound", include: false }, + ]; + } } - // Set the new configuration type so the python debugger can take over - config.type = "python"; - // Set process_user_code path as program - config.program = this.pathToScript; - // Set user's code path and server's port as args - config.args = [currentFilePath, getServerPortConfig().toString()]; - // Set rules - config.rules = [ - { path: this.pathToScript, include: false }, - { - module: "adafruit_circuitplayground", - include: false - }, - { module: "playsound", include: false } - ]; - } - } - // Abort / show error message if can't find process_user_code.py - if (!config.program) { - return vscode.window - .showErrorMessage(CONSTANTS.ERROR.NO_PROGRAM_FOUND_DEBUG) - .then(() => { - return undefined; // Abort launch - }); + // Abort / show error message if can't find process_user_code.py + if (!config.program) { + return vscode.window + .showErrorMessage(CONSTANTS.ERROR.NO_PROGRAM_FOUND_DEBUG) + .then(() => { + return undefined; // Abort launch + }); + } + return config; } - return config; - } } diff --git a/src/telemetry/getPackageInfo.ts b/src/telemetry/getPackageInfo.ts index e38d5c2ca..cd8d42ab7 100644 --- a/src/telemetry/getPackageInfo.ts +++ b/src/telemetry/getPackageInfo.ts @@ -1,6 +1,6 @@ -import * as fs from 'fs' -import * as path from 'path'; -import * as vscode from 'vscode'; +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; export interface IPackageJson { name?: string; @@ -14,35 +14,44 @@ const getPackagePath = (context: vscode.ExtensionContext) => { ); const packagePath = onDiskPath.with({ scheme: "vscode-resource" }); - return packagePath; -} + return packagePath; +}; -export default function getPackageInfo(context: vscode.ExtensionContext): { extensionName: string, extensionVersion: string, instrumentationKey: string } { - let packageJson: IPackageJson; - const packagePath = getPackagePath(context); - - try { - packageJson = JSON.parse(fs.readFileSync(packagePath.fsPath, "utf8")); - } catch (error) { - console.error(`Failed to read from package.json: ${error}`); - throw new Error(`Failed to read from package.json: ${error}`); - } - - const extensionName: string | undefined = packageJson.name; - const extensionVersion: string | undefined = packageJson.version; - const instrumentationKey: string | undefined = packageJson.instrumentationKey; - - if (!extensionName) { - throw new Error('Extension\'s package.json is missing instrumentation key.'); - } - - if (!extensionVersion) { - throw new Error('Extension\'s package.json is missing version.'); - } - - if (!extensionVersion) { - throw new Error('Extension\'s package.json is missing version.'); - } - - return { extensionName, extensionVersion, instrumentationKey }; -} \ No newline at end of file +export default function getPackageInfo( + context: vscode.ExtensionContext +): { + extensionName: string; + extensionVersion: string; + instrumentationKey: string; +} { + let packageJson: IPackageJson; + const packagePath = getPackagePath(context); + + try { + packageJson = JSON.parse(fs.readFileSync(packagePath.fsPath, "utf8")); + } catch (error) { + console.error(`Failed to read from package.json: ${error}`); + throw new Error(`Failed to read from package.json: ${error}`); + } + + const extensionName: string | undefined = packageJson.name; + const extensionVersion: string | undefined = packageJson.version; + const instrumentationKey: string | undefined = + packageJson.instrumentationKey; + + if (!extensionName) { + throw new Error( + "Extension's package.json is missing instrumentation key." + ); + } + + if (!extensionVersion) { + throw new Error("Extension's package.json is missing version."); + } + + if (!extensionVersion) { + throw new Error("Extension's package.json is missing version."); + } + + return { extensionName, extensionVersion, instrumentationKey }; +} diff --git a/src/telemetry/telemetryAI.ts b/src/telemetry/telemetryAI.ts index addcabffa..4dbf5ca41 100644 --- a/src/telemetry/telemetryAI.ts +++ b/src/telemetry/telemetryAI.ts @@ -4,78 +4,80 @@ import getPackageInfo from "./getPackageInfo"; // tslint:disable-next-line:export-name export default class TelemetryAI { - private static telemetryReporter: TelemetryReporter; - private static enableTelemetry: boolean | undefined; + private static telemetryReporter: TelemetryReporter; + private static enableTelemetry: boolean | undefined; - constructor(vscodeContext: vscode.ExtensionContext) { - TelemetryAI.telemetryReporter = this.createTelemetryReporter(vscodeContext); - TelemetryAI.enableTelemetry = vscode.workspace - .getConfiguration() - .get("telemetry.enableTelemetry"); - if (TelemetryAI.enableTelemetry === undefined) { - TelemetryAI.enableTelemetry = true; + constructor(vscodeContext: vscode.ExtensionContext) { + TelemetryAI.telemetryReporter = this.createTelemetryReporter( + vscodeContext + ); + TelemetryAI.enableTelemetry = vscode.workspace + .getConfiguration() + .get("telemetry.enableTelemetry"); + if (TelemetryAI.enableTelemetry === undefined) { + TelemetryAI.enableTelemetry = true; + } } - } - public getExtensionName(context: vscode.ExtensionContext): string { - const { extensionName } = getPackageInfo(context); - return extensionName; - } + public getExtensionName(context: vscode.ExtensionContext): string { + const { extensionName } = getPackageInfo(context); + return extensionName; + } - public getExtensionVersionNumber(context: vscode.ExtensionContext): string { - const { extensionVersion } = getPackageInfo(context); - return extensionVersion; - } + public getExtensionVersionNumber(context: vscode.ExtensionContext): string { + const { extensionVersion } = getPackageInfo(context); + return extensionVersion; + } - public sendTelemetryIfEnabled( - eventName: string, - properties?: { [key: string]: string }, - measurements?: { [key: string]: number } - ) { - if (TelemetryAI.enableTelemetry) { - TelemetryAI.telemetryReporter.sendTelemetryEvent( - eventName, - properties, - measurements - ); + public sendTelemetryIfEnabled( + eventName: string, + properties?: { [key: string]: string }, + measurements?: { [key: string]: number } + ) { + if (TelemetryAI.enableTelemetry) { + TelemetryAI.telemetryReporter.sendTelemetryEvent( + eventName, + properties, + measurements + ); + } } - } - public trackFeatureUsage( - eventName: string, - eventProperties?: { [key: string]: string } - ) { - this.sendTelemetryIfEnabled(eventName, eventProperties); - } + public trackFeatureUsage( + eventName: string, + eventProperties?: { [key: string]: string } + ) { + this.sendTelemetryIfEnabled(eventName, eventProperties); + } - public runWithLatencyMeasure( - functionToRun: () => void, - eventName: string - ): void { - const numberOfNanosecondsInSecond: number = 1000000000; - const startTime: number = Number(process.hrtime.bigint()); - functionToRun(); - const latency: number = Number(process.hrtime.bigint()) - startTime; - const measurement = { - duration: latency / numberOfNanosecondsInSecond - }; - this.sendTelemetryIfEnabled(eventName, {}, measurement); - } + public runWithLatencyMeasure( + functionToRun: () => void, + eventName: string + ): void { + const numberOfNanosecondsInSecond: number = 1000000000; + const startTime: number = Number(process.hrtime.bigint()); + functionToRun(); + const latency: number = Number(process.hrtime.bigint()) - startTime; + const measurement = { + duration: latency / numberOfNanosecondsInSecond, + }; + this.sendTelemetryIfEnabled(eventName, {}, measurement); + } - private createTelemetryReporter( - context: vscode.ExtensionContext - ): TelemetryReporter { - const { - extensionName, - extensionVersion, - instrumentationKey - } = getPackageInfo(context); - const reporter: TelemetryReporter = new TelemetryReporter( - extensionName, - extensionVersion, - instrumentationKey - ); - context.subscriptions.push(reporter); - return reporter; - } + private createTelemetryReporter( + context: vscode.ExtensionContext + ): TelemetryReporter { + const { + extensionName, + extensionVersion, + instrumentationKey, + } = getPackageInfo(context); + const reporter: TelemetryReporter = new TelemetryReporter( + extensionName, + extensionVersion, + instrumentationKey + ); + context.subscriptions.push(reporter); + return reporter; + } } diff --git a/src/test/runTest.ts b/src/test/runTest.ts index a3e10548d..6d54aa78b 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -1,23 +1,23 @@ -import * as path from 'path'; +import * as path from "path"; -import { runTests } from 'vscode-test'; +import { runTests } from "vscode-test"; async function main() { - try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); - // The path to the extension test script - // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, './suite/index'); + // The path to the extension test script + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); - } catch (err) { - console.error('Failed to run tests'); - process.exit(1); - } + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath }); + } catch (err) { + console.error("Failed to run tests"); + process.exit(1); + } } main(); diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 60293e737..5413ee907 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -4,7 +4,7 @@ // // The module 'assert' provides assertion methods from node -import * as assert from 'assert'; +import * as assert from "assert"; // You can import and use all API from the 'vscode' module // as well as import your extension to test it @@ -12,9 +12,8 @@ import * as assert from 'assert'; // import * as myExtension from '../extension'; // Defines a Mocha test suite to group tests of similar kind together -suite('Extension Test Suite', () => { - - test('Sample test', () => { +suite("Extension Test Suite", () => { + test("Sample test", () => { assert.equal(-1, [1, 2, 3].indexOf(5)); assert.equal(-1, [1, 2, 3].indexOf(0)); }); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts index 61aac7d28..c223a6f0d 100644 --- a/src/test/suite/index.ts +++ b/src/test/suite/index.ts @@ -10,22 +10,22 @@ // to report the results back to the caller. When the tests are finished, return // a possible error to the callback or null if none. -import * as glob from 'glob'; -import * as Mocha from 'mocha'; -import * as path from 'path'; +import * as glob from "glob"; +import * as Mocha from "mocha"; +import * as path from "path"; // tslint:disable-next-line: export-name export function run(): Promise { // Create the mocha test const mocha = new Mocha({ - ui: 'tdd', + ui: "tdd", }); mocha.useColors(true); - const testsRoot = path.resolve(__dirname, '..'); + const testsRoot = path.resolve(__dirname, ".."); return new Promise((c, e) => { - glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { if (err) { return e(err); } @@ -47,4 +47,4 @@ export function run(): Promise { } }); }); -} \ No newline at end of file +} diff --git a/src/test_code/code.py b/src/test_code/code.py index 369561cc5..abeaff3b1 100644 --- a/src/test_code/code.py +++ b/src/test_code/code.py @@ -16,4 +16,3 @@ else: cpx.pixels[7] = (0, 0, 0) cpx.pixels.show() - diff --git a/src/test_code/control.py b/src/test_code/control.py index 1aceb880b..ad8f6c0e5 100644 --- a/src/test_code/control.py +++ b/src/test_code/control.py @@ -15,24 +15,24 @@ def main(): # lines = read_in() openCmd = { - 'pixels': [ - (0, 0, 255), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - (0, 0, 0), - ], - 'button_a': False, - 'button_b': False, + "pixels": [ + (0, 0, 255), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ], + "button_a": False, + "button_b": False, } print(json.dumps(openCmd)) # start process -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/usbDetector.ts b/src/usbDetector.ts index e4df1200f..ccff7a679 100644 --- a/src/usbDetector.ts +++ b/src/usbDetector.ts @@ -27,7 +27,7 @@ export class UsbDetector { private _extensionRoot: string = null; - private constructor() { } + private constructor() {} public initialize(extensionRoot: string) { this._extensionRoot = extensionRoot; @@ -35,7 +35,9 @@ export class UsbDetector { public async startListening() { const workspaceConfig = vscode.workspace.getConfiguration(); - const enableUSBDetection = workspaceConfig.get(CONFIG_KEYS.ENABLE_USB_DETECTION); + const enableUSBDetection = workspaceConfig.get( + CONFIG_KEYS.ENABLE_USB_DETECTION + ); if (os.platform() === "linux" || !enableUSBDetection) { return; @@ -78,22 +80,33 @@ export class UsbDetector { } } - private getUsbDeviceDescriptor(vendorId: string, productId: string, extensionRoot: string): any { + private getUsbDeviceDescriptor( + vendorId: string, + productId: string, + extensionRoot: string + ): any { if (!this._boardDescriptors) { this._boardDescriptors = []; - const fileContent = fs.readFileSync(path.join(extensionRoot, "misc", "usbmapping.json"), "utf8"); + const fileContent = fs.readFileSync( + path.join(extensionRoot, "misc", "usbmapping.json"), + "utf8" + ); const boardIndexes: [] = JSON.parse(fileContent); boardIndexes.forEach((boardIndex: any) => { boardIndex.boards.forEach((board: any) => { - board.indexFile = boardIndex.index_file + board.indexFile = boardIndex.index_file; }); - this._boardDescriptors = this._boardDescriptors.concat(boardIndex.boards); + this._boardDescriptors = this._boardDescriptors.concat( + boardIndex.boards + ); }); } return this._boardDescriptors.find((obj: any) => { - return obj.vid === vendorId && - (obj.pid === productId || - (obj.pid.indexOf && obj.pid.indexOf(productId) >= 0)); + return ( + obj.vid === vendorId && + (obj.pid === productId || + (obj.pid.indexOf && obj.pid.indexOf(productId) >= 0)) + ); }); } -} \ No newline at end of file +} diff --git a/src/view/App.css b/src/view/App.css index 10158ee73..413e9fcbc 100644 --- a/src/view/App.css +++ b/src/view/App.css @@ -1,16 +1,16 @@ .App { - text-align: center; - display: flex; - justify-content: space-between; + text-align: center; + display: flex; + justify-content: space-between; } .App-main { - background-color: var(--vscode-editor-background); - padding: 0px 0.75em 0px 0.75em; - min-height: 100vh; - width: 100%; - margin-top: 24px; - margin-bottom: 53px; - max-height: 400px; - overflow: scroll; + background-color: var(--vscode-editor-background); + padding: 0px 0.75em 0px 0.75em; + min-height: 100vh; + width: 100%; + margin-top: 24px; + margin-bottom: 53px; + max-height: 400px; + overflow: scroll; } diff --git a/src/view/App.tsx b/src/view/App.tsx index e59eb8ed5..b52993907 100644 --- a/src/view/App.tsx +++ b/src/view/App.tsx @@ -4,18 +4,18 @@ "use strict"; import * as React from "react"; import "./App.css"; -import Device from "./container/device/Device" +import Device from "./container/device/Device"; class App extends React.Component { - render() { - return ( -
-
- -
-
- ); - } + render() { + return ( +
+
+ +
+
+ ); + } } export default App; diff --git a/src/view/components/Button.tsx b/src/view/components/Button.tsx index c613b5910..db5034b9d 100644 --- a/src/view/components/Button.tsx +++ b/src/view/components/Button.tsx @@ -2,33 +2,33 @@ import * as React from "react"; import "../styles/Button.css"; export interface IButtonProps { - label: string; - image: any; - focusable: boolean; - styleLabel: string; - width: number; - onClick: (event: React.MouseEvent) => void; + label: string; + image: any; + focusable: boolean; + styleLabel: string; + width: number; + onClick: (event: React.MouseEvent) => void; } // Functional Component render const Button: React.FC = props => { - const iconSvg: SVGElement = props.image as SVGElement; - const buttonStyle = { width: props.width }; - const tabIndex = props.focusable ? 0 : -1; + const iconSvg: SVGElement = props.image as SVGElement; + const buttonStyle = { width: props.width }; + const tabIndex = props.focusable ? 0 : -1; - return ( - - ); + return ( + + ); }; export default Button; diff --git a/src/view/components/Dropdown.tsx b/src/view/components/Dropdown.tsx index 44832c137..f4c995923 100644 --- a/src/view/components/Dropdown.tsx +++ b/src/view/components/Dropdown.tsx @@ -7,54 +7,54 @@ import { CONSTANTS } from "../constants"; import "../styles/Dropdown.css"; export interface IDropdownProps { - label: string; - textOptions: string[]; - lastChosen: string; - styleLabel: string; - width: number; - onBlur: (event: React.FocusEvent) => void; + label: string; + textOptions: string[]; + lastChosen: string; + styleLabel: string; + width: number; + onBlur: (event: React.FocusEvent) => void; } const Dropdown: React.FC = props => { - const parsedPath = parsePath(props.lastChosen); - const defaultText = - props.lastChosen !== "" - ? CONSTANTS.CURRENTLY_RUNNING(parsedPath[1]) - : CONSTANTS.NO_FILES_AVAILABLE; - return ( -
- -
- ); + const parsedPath = parsePath(props.lastChosen); + const defaultText = + props.lastChosen !== "" + ? CONSTANTS.CURRENTLY_RUNNING(parsedPath[1]) + : CONSTANTS.NO_FILES_AVAILABLE; + return ( +
+ +
+ ); }; const renderOptions = (options: string[]) => { - return options.map((name, index) => { - const key = `option-${index}`; - const parsedPath = parsePath(name); - return ( - - ); - }); + return options.map((name, index) => { + const key = `option-${index}`; + const parsedPath = parsePath(name); + return ( + + ); + }); }; const parsePath = (filePath: string) => { - const lastSlash = - filePath.lastIndexOf("/") !== -1 - ? filePath.lastIndexOf("/") - : filePath.lastIndexOf("\\"); - return [filePath.slice(0, lastSlash), filePath.substr(lastSlash + 1)]; + const lastSlash = + filePath.lastIndexOf("/") !== -1 + ? filePath.lastIndexOf("/") + : filePath.lastIndexOf("\\"); + return [filePath.slice(0, lastSlash), filePath.substr(lastSlash + 1)]; }; export default Dropdown; diff --git a/src/view/components/Simulator.tsx b/src/view/components/Simulator.tsx index ca3f11aa1..a1ddee6b6 100644 --- a/src/view/components/Simulator.tsx +++ b/src/view/components/Simulator.tsx @@ -9,356 +9,393 @@ import StopLogo from "../svgs/stop_svg"; import { BUTTON_NEUTRAL, BUTTON_PRESSED } from "./cpx/Cpx_svg_style"; import CpxImage, { updatePinTouch, updateSwitch } from "./cpx/CpxImage"; import Dropdown from "./Dropdown"; -import ActionBar from "./simulator/ActionBar" +import ActionBar from "./simulator/ActionBar"; interface ICpxState { - pixels: number[][]; - brightness: number; - red_led: boolean; - button_a: boolean; - button_b: boolean; - switch: boolean; - touch: boolean[]; - shake: boolean; + pixels: number[][]; + brightness: number; + red_led: boolean; + button_a: boolean; + button_b: boolean; + switch: boolean; + touch: boolean[]; + shake: boolean; } interface IState { - active_editors: string[]; - running_file: string; - selected_file: string; - cpx: ICpxState; - play_button: boolean; + active_editors: string[]; + running_file: string; + selected_file: string; + cpx: ICpxState; + play_button: boolean; } interface IMyProps { - children?: any; + children?: any; } const DEFAULT_CPX_STATE: ICpxState = { - brightness: 1.0, - button_a: false, - button_b: false, - pixels: [ - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0] - ], - red_led: false, - switch: false, - touch: [false, false, false, false, false, false, false], - shake: false + brightness: 1.0, + button_a: false, + button_b: false, + pixels: [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + red_led: false, + switch: false, + touch: [false, false, false, false, false, false, false], + shake: false, }; interface vscode { - postMessage(message: any): void; + postMessage(message: any): void; } declare const vscode: vscode; const sendMessage = (type: string, state: any) => { - vscode.postMessage({ command: type, text: state }); + vscode.postMessage({ command: type, text: state }); }; class Simulator extends React.Component { - constructor(props: IMyProps) { - super(props); - this.state = { - active_editors: [], - cpx: DEFAULT_CPX_STATE, - play_button: false, - running_file: "", - selected_file: "" + constructor(props: IMyProps) { + super(props); + this.state = { + active_editors: [], + cpx: DEFAULT_CPX_STATE, + play_button: false, + running_file: "", + selected_file: "", + }; + + this.handleClick = this.handleClick.bind(this); + this.onKeyEvent = this.onKeyEvent.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + this.onMouseLeave = this.onMouseLeave.bind(this); + this.togglePlayClick = this.togglePlayClick.bind(this); + this.refreshSimulatorClick = this.refreshSimulatorClick.bind(this); + this.onSelectBlur = this.onSelectBlur.bind(this); + } + + handleMessage = (event: any): void => { + const message = event.data; // The JSON data our extension sent + switch (message.command) { + case "reset-state": + console.log("Clearing the state"); + this.setState({ + ...this.state, + cpx: DEFAULT_CPX_STATE, + play_button: false, + }); + break; + case "set-state": + console.log( + "Setting the state: " + JSON.stringify(message.state) + ); + this.setState({ + ...this.state, + cpx: message.state, + play_button: true, + }); + break; + case "activate-play": + this.setState({ + ...this.state, + play_button: !this.state.play_button, + }); + break; + case "visible-editors": + console.log( + "Setting active editors", + message.state.activePythonEditors + ); + this.setState({ + ...this.state, + active_editors: message.state.activePythonEditors, + }); + break; + case "current-file": + console.log("Setting current file", message.state.running_file); + this.setState({ + ...this.state, + running_file: message.state.running_file, + }); + break; + default: + console.log("Invalid message received from the extension."); + this.setState({ ...this.state, cpx: DEFAULT_CPX_STATE }); + break; + } }; - this.handleClick = this.handleClick.bind(this); - this.onKeyEvent = this.onKeyEvent.bind(this); - this.onMouseDown = this.onMouseDown.bind(this); - this.onMouseUp = this.onMouseUp.bind(this); - this.onMouseLeave = this.onMouseLeave.bind(this); - this.togglePlayClick = this.togglePlayClick.bind(this); - this.refreshSimulatorClick = this.refreshSimulatorClick.bind(this); - this.onSelectBlur = this.onSelectBlur.bind(this); - } - - handleMessage = (event: any): void => { - const message = event.data; // The JSON data our extension sent - switch (message.command) { - case "reset-state": - console.log("Clearing the state"); - this.setState({ - ...this.state, - cpx: DEFAULT_CPX_STATE, - play_button: false - }); - break; - case "set-state": - console.log("Setting the state: " + JSON.stringify(message.state)); - this.setState({ ...this.state, cpx: message.state, play_button: true }); - break; - case "activate-play": - this.setState({ ...this.state, play_button: !this.state.play_button }); - break; - case "visible-editors": - console.log( - "Setting active editors", - message.state.activePythonEditors + componentDidMount() { + console.log("Mounted"); + window.addEventListener("message", this.handleMessage); + } + + componentWillUnmount() { + // Make sure to remove the DOM listener when the component is unmounted. + window.removeEventListener("message", this.handleMessage); + } + + render() { + const playStopImage = this.state.play_button ? StopLogo : PlayLogo; + return ( +
+
+ +
+
+ +
+ +
); - this.setState({ - ...this.state, - active_editors: message.state.activePythonEditors + } + + protected togglePlayClick() { + sendMessage("play-simulator", { + selected_file: this.state.selected_file, + state: !this.state.play_button, }); - break; - case "current-file": - console.log("Setting current file", message.state.running_file); + const button = + window.document.getElementById(CONSTANTS.ID_NAME.PLAY_BUTTON) || + window.document.getElementById(CONSTANTS.ID_NAME.STOP_BUTTON); + if (button) { + button.focus(); + } + } + + protected refreshSimulatorClick() { + sendMessage("refresh-simulator", true); + const button = window.document.getElementById( + CONSTANTS.ID_NAME.REFRESH_BUTTON + ); + if (button) { + button.focus(); + } + } + + protected onSelectBlur(event: React.FocusEvent) { this.setState({ - ...this.state, - running_file: message.state.running_file + ...this.state, + selected_file: event.currentTarget.value, }); - break; - default: - console.log("Invalid message received from the extension."); - this.setState({ ...this.state, cpx: DEFAULT_CPX_STATE }); - break; } - }; - - componentDidMount() { - console.log("Mounted"); - window.addEventListener("message", this.handleMessage); - } - - componentWillUnmount() { - // Make sure to remove the DOM listener when the component is unmounted. - window.removeEventListener("message", this.handleMessage); - } - - render() { - const playStopImage = this.state.play_button ? StopLogo : PlayLogo; - return ( -
-
- -
-
- -
- -
- ); - } - - protected togglePlayClick() { - sendMessage("play-simulator", { - selected_file: this.state.selected_file, - state: !this.state.play_button - }); - const button = - window.document.getElementById(CONSTANTS.ID_NAME.PLAY_BUTTON) || - window.document.getElementById(CONSTANTS.ID_NAME.STOP_BUTTON); - if (button) { - button.focus(); + protected onKeyEvent(event: KeyboardEvent, active: boolean) { + let element; + const target = event.target as SVGElement; + // Guard Clause + if (target === undefined) { + return; + } + + if ([event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.ENTER)) { + element = window.document.getElementById(target.id); + } else if ( + [event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.A) + ) { + element = window.document.getElementById( + CONSTANTS.ID_NAME.BUTTON_A + ); + } else if ( + [event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.B) + ) { + element = window.document.getElementById( + CONSTANTS.ID_NAME.BUTTON_B + ); + } else if ( + [event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.S) + ) { + element = window.document.getElementById(CONSTANTS.ID_NAME.SWITCH); + } else if (event.key === CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) { + this.togglePlayClick(); + } else if (event.key === CONSTANTS.KEYBOARD_KEYS.CAPITAL_R) { + this.refreshSimulatorClick(); + } else { + if (event.shiftKey) { + switch (event.code) { + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_ONE: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A1 + ); + break; + + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_TWO: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A2 + ); + break; + + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_THREE: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A3 + ); + break; + + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_FOUR: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A4 + ); + break; + + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_FIVE: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A5 + ); + break; + + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_SIX: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A6 + ); + break; + + case CONSTANTS.KEYBOARD_KEYS.NUMERIC_SEVEN: + element = window.document.getElementById( + CONSTANTS.ID_NAME.PIN_A7 + ); + break; + } + } + } + if (element) { + event.preventDefault(); + this.handleClick(element, active); + element.focus(); + } } - } - - protected refreshSimulatorClick() { - sendMessage("refresh-simulator", true); - const button = window.document.getElementById( - CONSTANTS.ID_NAME.REFRESH_BUTTON - ); - if (button) { - button.focus(); + protected onMouseDown(button: HTMLElement, event: Event) { + event.preventDefault(); + this.handleClick(button, true); + button.focus(); } - } - - protected onSelectBlur(event: React.FocusEvent) { - this.setState({ ...this.state, selected_file: event.currentTarget.value }); - } - protected onKeyEvent(event: KeyboardEvent, active: boolean) { - let element; - const target = event.target as SVGElement; - // Guard Clause - if (target === undefined) { - return; + + protected onMouseUp(button: HTMLElement, event: Event) { + event.preventDefault(); + this.handleClick(button, false); } - if ([event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.ENTER)) { - element = window.document.getElementById(target.id); - } else if ([event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.A)) { - element = window.document.getElementById(CONSTANTS.ID_NAME.BUTTON_A); - } else if ([event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.B)) { - element = window.document.getElementById(CONSTANTS.ID_NAME.BUTTON_B); - } else if ([event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.S)) { - element = window.document.getElementById(CONSTANTS.ID_NAME.SWITCH); - } else if (event.key === CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) { - this.togglePlayClick(); - } else if (event.key === CONSTANTS.KEYBOARD_KEYS.CAPITAL_R) { - this.refreshSimulatorClick(); - } else { - if (event.shiftKey) { - switch (event.code) { - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_ONE: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A1); - break; - - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_TWO: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A2); - break; - - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_THREE: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A3); - break; - - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_FOUR: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A4); - break; - - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_FIVE: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A5); - break; - - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_SIX: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A6); - break; - - case CONSTANTS.KEYBOARD_KEYS.NUMERIC_SEVEN: - element = window.document.getElementById(CONSTANTS.ID_NAME.PIN_A7); - break; + protected onMouseLeave(button: HTMLElement, event: Event) { + event.preventDefault(); + + if (button.getAttribute("pressed") === "true") { + this.handleClick(button, false); } - } - } - if (element) { - event.preventDefault(); - this.handleClick(element, active); - element.focus(); } - } - protected onMouseDown(button: HTMLElement, event: Event) { - event.preventDefault(); - this.handleClick(button, true); - button.focus(); - } - - protected onMouseUp(button: HTMLElement, event: Event) { - event.preventDefault(); - this.handleClick(button, false); - } - - protected onMouseLeave(button: HTMLElement, event: Event) { - event.preventDefault(); - - if (button.getAttribute("pressed") === "true") { - this.handleClick(button, false); - } - } - - private handleClick(element: HTMLElement, active: boolean) { - let newState; - let message; - if (element.id.includes("BTN")) { - newState = this.handleButtonClick(element, active); - message = "button-press"; - } else if (element.id.includes("SWITCH")) { - newState = this.handleSwitchClick(); - message = "button-press"; - } else if (element.id.includes("PIN")) { - newState = this.handleTouchPinClick(element, active); - message = "sensor-changed"; - } else { - return; + + private handleClick(element: HTMLElement, active: boolean) { + let newState; + let message; + if (element.id.includes("BTN")) { + newState = this.handleButtonClick(element, active); + message = "button-press"; + } else if (element.id.includes("SWITCH")) { + newState = this.handleSwitchClick(); + message = "button-press"; + } else if (element.id.includes("PIN")) { + newState = this.handleTouchPinClick(element, active); + message = "sensor-changed"; + } else { + return; + } + + if (newState && message) { + sendMessage(message, newState); + } } - if (newState && message) { - sendMessage(message, newState); + private handleButtonClick(button: HTMLElement, active: boolean) { + const ButtonA: boolean = button.id.match(/BTN_A/) !== null; + const ButtonB: boolean = button.id.match(/BTN_B/) !== null; + const ButtonAB: boolean = button.id.match(/BTN_AB/) !== null; + let innerButton; + let newState; + + if (ButtonAB) { + innerButton = window.document.getElementById("BTN_AB_INNER"); + newState = { + button_a: active, + button_b: active, + }; + this.setState({ ...this.state, ...newState }); + } else if (ButtonA) { + innerButton = window.document.getElementById("BTN_A_INNER"); + newState = { + button_a: active, + }; + this.setState({ ...this.state, ...newState }); + } else if (ButtonB) { + innerButton = window.document.getElementById("BTN_B_INNER"); + newState = { + button_b: active, + }; + this.setState({ ...this.state, ...newState }); + } + + if (innerButton) { + innerButton.style.fill = this.getButtonColor(active); + } + + button.setAttribute("pressed", `${active}`); + return newState; } - } - - private handleButtonClick(button: HTMLElement, active: boolean) { - const ButtonA: boolean = button.id.match(/BTN_A/) !== null; - const ButtonB: boolean = button.id.match(/BTN_B/) !== null; - const ButtonAB: boolean = button.id.match(/BTN_AB/) !== null; - let innerButton; - let newState; - - if (ButtonAB) { - innerButton = window.document.getElementById("BTN_AB_INNER"); - newState = { - button_a: active, - button_b: active - }; - this.setState({ ...this.state, ...newState }); - } else if (ButtonA) { - innerButton = window.document.getElementById("BTN_A_INNER"); - newState = { - button_a: active - }; - this.setState({ ...this.state, ...newState }); - } else if (ButtonB) { - innerButton = window.document.getElementById("BTN_B_INNER"); - newState = { - button_b: active - }; - this.setState({ ...this.state, ...newState }); + + private getButtonColor(pressed: boolean) { + const buttonUps = BUTTON_NEUTRAL; + const buttonDown = BUTTON_PRESSED; + return pressed ? buttonDown : buttonUps; } - if (innerButton) { - innerButton.style.fill = this.getButtonColor(active); + private handleSwitchClick() { + let cpxState = this.state.cpx; + const switchIsOn: boolean = !this.state.cpx.switch; + updateSwitch(switchIsOn); + cpxState = { ...cpxState, switch: switchIsOn }; + this.setState({ ...this.state, ...cpxState }); + return { switch: switchIsOn }; } - button.setAttribute("pressed", `${active}`); - return newState; - } - - private getButtonColor(pressed: boolean) { - const buttonUps = BUTTON_NEUTRAL; - const buttonDown = BUTTON_PRESSED; - return pressed ? buttonDown : buttonUps; - } - - private handleSwitchClick() { - let cpxState = this.state.cpx; - const switchIsOn: boolean = !this.state.cpx.switch; - updateSwitch(switchIsOn); - cpxState = { ...cpxState, switch: switchIsOn }; - this.setState({ ...this.state, ...cpxState }); - return { switch: switchIsOn }; - } - - private handleTouchPinClick(pin: HTMLElement, active: boolean): any { - let cpxState = this.state.cpx; - const pinIndex = parseInt(pin.id.charAt(pin.id.length - 1)) - 1; - const pinState = cpxState.touch; - pinState[pinIndex] = active; - cpxState = { ...cpxState, touch: pinState }; - this.setState({ ...this.state, ...cpxState }); - updatePinTouch(active, pin.id); - return { touch: pinState }; - } + private handleTouchPinClick(pin: HTMLElement, active: boolean): any { + let cpxState = this.state.cpx; + const pinIndex = parseInt(pin.id.charAt(pin.id.length - 1)) - 1; + const pinState = cpxState.touch; + pinState[pinIndex] = active; + cpxState = { ...cpxState, touch: pinState }; + this.setState({ ...this.state, ...cpxState }); + updatePinTouch(active, pin.id); + return { touch: pinState }; + } } export default Simulator; diff --git a/src/view/components/cpx/Accessibility_utils.ts b/src/view/components/cpx/Accessibility_utils.ts index 0ea83d1b2..afdadb2ac 100644 --- a/src/view/components/cpx/Accessibility_utils.ts +++ b/src/view/components/cpx/Accessibility_utils.ts @@ -3,20 +3,24 @@ // Helpers designed to help to make a simulator accessible. namespace accessibility { - export function makeFocusable(elem: SVGElement): void { - elem.setAttribute("focusable", "true"); - elem.setAttribute("tabindex", "0"); - } - - export function setAria(elem: Element, role?: string, label?: string): void { - if (role && !elem.hasAttribute("role")) { - elem.setAttribute("role", role); + export function makeFocusable(elem: SVGElement): void { + elem.setAttribute("focusable", "true"); + elem.setAttribute("tabindex", "0"); } - if (label && !elem.hasAttribute("aria-label")) { - elem.setAttribute("aria-label", label); + export function setAria( + elem: Element, + role?: string, + label?: string + ): void { + if (role && !elem.hasAttribute("role")) { + elem.setAttribute("role", role); + } + + if (label && !elem.hasAttribute("aria-label")) { + elem.setAttribute("aria-label", label); + } } - } } export default accessibility; diff --git a/src/view/components/cpx/CpxImage.tsx b/src/view/components/cpx/CpxImage.tsx index 7f7a1137c..0c74dd936 100644 --- a/src/view/components/cpx/CpxImage.tsx +++ b/src/view/components/cpx/CpxImage.tsx @@ -9,381 +9,383 @@ import * as SvgStyle from "./Cpx_svg_style"; import svg from "./Svg_utils"; interface IProps { - pixels: number[][]; - red_led: boolean; - brightness: number; - switch: boolean; - on: boolean; - onKeyEvent: (event: KeyboardEvent, active: boolean) => void; - onMouseUp: (button: HTMLElement, event: Event) => void; - onMouseDown: (button: HTMLElement, event: Event) => void; - onMouseLeave: (button: HTMLElement, event: Event) => void; + pixels: number[][]; + red_led: boolean; + brightness: number; + switch: boolean; + on: boolean; + onKeyEvent: (event: KeyboardEvent, active: boolean) => void; + onMouseUp: (button: HTMLElement, event: Event) => void; + onMouseDown: (button: HTMLElement, event: Event) => void; + onMouseLeave: (button: HTMLElement, event: Event) => void; } let firstTime = true; // Functional Component render const CpxImage: React.FC = props => { - const svgElement = window.document.getElementById("cpx_svg"); - - if (svgElement) { - if (firstTime) { - initSvgStyle(svgElement, props.brightness); - setupButtons(props); - setupPins(props); - setupKeyPresses(props.onKeyEvent); - setupSwitch(props); - firstTime = false; + const svgElement = window.document.getElementById("cpx_svg"); + + if (svgElement) { + if (firstTime) { + initSvgStyle(svgElement, props.brightness); + setupButtons(props); + setupPins(props); + setupKeyPresses(props.onKeyEvent); + setupSwitch(props); + firstTime = false; + } + // Update Neopixels and red LED state + updateNeopixels(props); + updateRedLED(props.red_led); + updatePowerLED(props.on); + updateSwitch(props.switch); } - // Update Neopixels and red LED state - updateNeopixels(props); - updateRedLED(props.red_led); - updatePowerLED(props.on); - updateSwitch(props.switch); - } - - return CPX_SVG; + + return CPX_SVG; }; const makeButton = ( - g: SVGElement, - left: number, - top: number, - id: string + g: SVGElement, + left: number, + top: number, + id: string ): { outer: SVGElement; inner: SVGElement } => { - const buttonCornerRadius = SvgStyle.BUTTON_CORNER_RADIUS; - const buttonWidth = SvgStyle.BUTTON_WIDTH; - const buttonCircleRadius = SvgStyle.BUTTON_CIRCLE_RADIUS; - const btng = svg.child(g, "g", { class: "sim-button-group" }); - svg.child(btng, "rect", { - fill: SvgStyle.BUTTON_OUTER, - height: buttonWidth, - id: id + "_OUTER", - rx: buttonCornerRadius, - ry: buttonCornerRadius, - width: buttonWidth, - x: left, - y: top - }); - - const outer = btng; - const inner = svg.child(btng, "circle", { - id: id + "_INNER", - cx: left + buttonWidth / 2, - cy: top + buttonWidth / 2, - r: buttonCircleRadius, - fill: SvgStyle.BUTTON_NEUTRAL - }); - - return { outer, inner }; + const buttonCornerRadius = SvgStyle.BUTTON_CORNER_RADIUS; + const buttonWidth = SvgStyle.BUTTON_WIDTH; + const buttonCircleRadius = SvgStyle.BUTTON_CIRCLE_RADIUS; + const btng = svg.child(g, "g", { class: "sim-button-group" }); + svg.child(btng, "rect", { + fill: SvgStyle.BUTTON_OUTER, + height: buttonWidth, + id: id + "_OUTER", + rx: buttonCornerRadius, + ry: buttonCornerRadius, + width: buttonWidth, + x: left, + y: top, + }); + + const outer = btng; + const inner = svg.child(btng, "circle", { + id: id + "_INNER", + cx: left + buttonWidth / 2, + cy: top + buttonWidth / 2, + r: buttonCircleRadius, + fill: SvgStyle.BUTTON_NEUTRAL, + }); + + return { outer, inner }; }; const initSvgStyle = (svgElement: HTMLElement, brightness: number): void => { - const style: SVGStyleElement = svg.child( - svgElement, - "style", - {} - ) as SVGStyleElement; - style.textContent = SvgStyle.SVG_STYLE; - - // Filters for the glow effect (Adapted from : https://github.com/microsoft/pxt-adafruit/blob/master/sim/visuals/board.ts) - const defs: SVGDefsElement = svg.child( - svgElement, - "defs", - {} - ) as SVGDefsElement; - - const g = svg.createElement("g") as SVGElement; - svgElement.appendChild(g); - - const glow = svg.child(defs, "filter", { - height: "120%", - id: "filterglow", - width: "120%", - x: "-5%", - y: "-5%" - }); - svg.child(glow, "feGaussianBlur", { stdDeviation: "5", result: "glow" }); - const merge = svg.child(glow, "feMerge", {}); - for (let i = 0; i < 3; ++i) { - svg.child(merge, "feMergeNode", { in: "glow" }); - } - - const neopixelglow = svg.child(defs, "filter", { - height: "600%", - id: "neopixelglow", - width: "600%", - x: "-300%", - y: "-300%" - }); - svg.child(neopixelglow, "feGaussianBlur", { - result: "coloredBlur", - stdDeviation: "4.3" - }); - const neopixelmerge = svg.child(neopixelglow, "feMerge", {}); - svg.child(neopixelmerge, "feMergeNode", { in: "coloredBlur" }); - svg.child(neopixelmerge, "feMergeNode", { in: "coloredBlur" }); - svg.child(neopixelmerge, "feMergeNode", { in: "SourceGraphic" }); - - // Brightness - const neopixelfeComponentTransfer = svg.child( - neopixelglow, - "feComponentTransfer", - {} - ); - svg.child(neopixelfeComponentTransfer, "feFuncR", { - id: "brightnessFilterR", - type: "linear", - slope: brightness - }); - svg.child(neopixelfeComponentTransfer, "feFuncG", { - id: "brightnessFilterG", - slope: brightness, - type: "linear" - }); - svg.child(neopixelfeComponentTransfer, "feFuncB", { - id: "brightnessFilterB", - slope: brightness, - type: "linear" - }); - - // BTN A+B - const outerBtn = (left: number, top: number, label: string) => { - return makeButton(g, left, top, "BTN_AB"); - }; - - const ab = outerBtn(165, SvgStyle.MB_HEIGHT - 15, "A+B"); - const abtext = svg.child(ab.outer, "text", { - class: "sim-text", - x: SvgStyle.BUTTON_TEXT_BASELINE, - y: SvgStyle.MB_HEIGHT - 18 - }) as SVGTextElement; - abtext.textContent = "A+B"; + const style: SVGStyleElement = svg.child( + svgElement, + "style", + {} + ) as SVGStyleElement; + style.textContent = SvgStyle.SVG_STYLE; + + // Filters for the glow effect (Adapted from : https://github.com/microsoft/pxt-adafruit/blob/master/sim/visuals/board.ts) + const defs: SVGDefsElement = svg.child( + svgElement, + "defs", + {} + ) as SVGDefsElement; + + const g = svg.createElement("g") as SVGElement; + svgElement.appendChild(g); + + const glow = svg.child(defs, "filter", { + height: "120%", + id: "filterglow", + width: "120%", + x: "-5%", + y: "-5%", + }); + svg.child(glow, "feGaussianBlur", { stdDeviation: "5", result: "glow" }); + const merge = svg.child(glow, "feMerge", {}); + for (let i = 0; i < 3; ++i) { + svg.child(merge, "feMergeNode", { in: "glow" }); + } + + const neopixelglow = svg.child(defs, "filter", { + height: "600%", + id: "neopixelglow", + width: "600%", + x: "-300%", + y: "-300%", + }); + svg.child(neopixelglow, "feGaussianBlur", { + result: "coloredBlur", + stdDeviation: "4.3", + }); + const neopixelmerge = svg.child(neopixelglow, "feMerge", {}); + svg.child(neopixelmerge, "feMergeNode", { in: "coloredBlur" }); + svg.child(neopixelmerge, "feMergeNode", { in: "coloredBlur" }); + svg.child(neopixelmerge, "feMergeNode", { in: "SourceGraphic" }); + + // Brightness + const neopixelfeComponentTransfer = svg.child( + neopixelglow, + "feComponentTransfer", + {} + ); + svg.child(neopixelfeComponentTransfer, "feFuncR", { + id: "brightnessFilterR", + type: "linear", + slope: brightness, + }); + svg.child(neopixelfeComponentTransfer, "feFuncG", { + id: "brightnessFilterG", + slope: brightness, + type: "linear", + }); + svg.child(neopixelfeComponentTransfer, "feFuncB", { + id: "brightnessFilterB", + slope: brightness, + type: "linear", + }); + + // BTN A+B + const outerBtn = (left: number, top: number, label: string) => { + return makeButton(g, left, top, "BTN_AB"); + }; + + const ab = outerBtn(165, SvgStyle.MB_HEIGHT - 15, "A+B"); + const abtext = svg.child(ab.outer, "text", { + class: "sim-text", + x: SvgStyle.BUTTON_TEXT_BASELINE, + y: SvgStyle.MB_HEIGHT - 18, + }) as SVGTextElement; + abtext.textContent = "A+B"; }; const updateNeopixels = (props: IProps): void => { - for (let i = 0; i < props.pixels.length; i++) { - const led = window.document.getElementById(`NEOPIXEL_${i}`); - if (led) { - setNeopixel(led, props.pixels[i], props.brightness); + for (let i = 0; i < props.pixels.length; i++) { + const led = window.document.getElementById(`NEOPIXEL_${i}`); + if (led) { + setNeopixel(led, props.pixels[i], props.brightness); + } } - } }; const updateRedLED = (propsRedLED: boolean): void => { - const redLED = window.document.getElementById("SERIAL_LED"); - if (redLED) { - redLED.style.fill = propsRedLED - ? SvgStyle.RED_LED_ON - : SvgStyle.RED_LED_OFF; - } + const redLED = window.document.getElementById("SERIAL_LED"); + if (redLED) { + redLED.style.fill = propsRedLED + ? SvgStyle.RED_LED_ON + : SvgStyle.RED_LED_OFF; + } }; const updatePowerLED = (propsPowerLED: boolean): void => { - const powerLED = window.document.getElementById("PWR_LED"); - if (powerLED) { - powerLED.style.fill = propsPowerLED - ? SvgStyle.POWER_LED_ON - : SvgStyle.POWER_LED_OFF; - } + const powerLED = window.document.getElementById("PWR_LED"); + if (powerLED) { + powerLED.style.fill = propsPowerLED + ? SvgStyle.POWER_LED_ON + : SvgStyle.POWER_LED_OFF; + } }; const setNeopixel = ( - led: HTMLElement, - pixValue: number[], - brightness: number + led: HTMLElement, + pixValue: number[], + brightness: number ): void => { - if (isLightOn(pixValue) && brightness > 0) { - // Neopixels style (Adapted from : https://github.com/microsoft/pxt-adafruit/blob/master/sim/visuals/board.ts) - changeBrightness("brightnessFilterR", brightness); - changeBrightness("brightnessFilterG", brightness); - changeBrightness("brightnessFilterB", brightness); - - let [hue, sat, lum] = SvgStyle.rgbToHsl([ - pixValue[0], - pixValue[1], - pixValue[2] - ]); - const innerLum = Math.max( - lum * SvgStyle.INTENSITY_FACTOR, - SvgStyle.MIN_INNER_LUM - ); - lum = (lum * 90) / 100 + 10; // at least 10% luminosity for the stroke - - led.style.filter = `url(#neopixelglow)`; - led.style.fill = `hsl(${hue}, ${sat}%, ${innerLum}%)`; - led.style.stroke = `hsl(${hue}, ${sat}%, ${Math.min( - lum * 3, - SvgStyle.MAX_STROKE_LUM - )}%)`; - led.style.strokeWidth = `1.5`; - } else { - led.style.fill = SvgStyle.OFF_COLOR; - led.style.filter = `none`; - led.style.stroke = `none`; - } + if (isLightOn(pixValue) && brightness > 0) { + // Neopixels style (Adapted from : https://github.com/microsoft/pxt-adafruit/blob/master/sim/visuals/board.ts) + changeBrightness("brightnessFilterR", brightness); + changeBrightness("brightnessFilterG", brightness); + changeBrightness("brightnessFilterB", brightness); + + let [hue, sat, lum] = SvgStyle.rgbToHsl([ + pixValue[0], + pixValue[1], + pixValue[2], + ]); + const innerLum = Math.max( + lum * SvgStyle.INTENSITY_FACTOR, + SvgStyle.MIN_INNER_LUM + ); + lum = (lum * 90) / 100 + 10; // at least 10% luminosity for the stroke + + led.style.filter = `url(#neopixelglow)`; + led.style.fill = `hsl(${hue}, ${sat}%, ${innerLum}%)`; + led.style.stroke = `hsl(${hue}, ${sat}%, ${Math.min( + lum * 3, + SvgStyle.MAX_STROKE_LUM + )}%)`; + led.style.strokeWidth = `1.5`; + } else { + led.style.fill = SvgStyle.OFF_COLOR; + led.style.filter = `none`; + led.style.stroke = `none`; + } }; const isLightOn = (pixValue: number[]): boolean => { - return !pixValue.every(val => { - return val === 0; - }); + return !pixValue.every(val => { + return val === 0; + }); }; const changeBrightness = (filterID: string, brightness: number): void => { - const brightnessFilter: HTMLElement | null = window.document.getElementById( - filterID - ); - if (brightnessFilter) { - brightnessFilter.setAttribute("slope", brightness.toString()); - } + const brightnessFilter: HTMLElement | null = window.document.getElementById( + filterID + ); + if (brightnessFilter) { + brightnessFilter.setAttribute("slope", brightness.toString()); + } }; const setupButtons = (props: IProps): void => { - const outButtons = ["A_OUTER", "B_OUTER", "AB_OUTER"]; - const inButtons = ["A_INNER", "B_INNER", "AB_INNER"]; - outButtons.forEach(buttonName => { - const button = window.document.getElementById("BTN_" + buttonName); - - if (button) { - setupButton(button, "sim-button-outer", props); - } - }); - inButtons.forEach(buttonName => { - const button = window.document.getElementById("BTN_" + buttonName); - if (button) { - setupButton(button, "sim-button", props); - } - }); + const outButtons = ["A_OUTER", "B_OUTER", "AB_OUTER"]; + const inButtons = ["A_INNER", "B_INNER", "AB_INNER"]; + outButtons.forEach(buttonName => { + const button = window.document.getElementById("BTN_" + buttonName); + + if (button) { + setupButton(button, "sim-button-outer", props); + } + }); + inButtons.forEach(buttonName => { + const button = window.document.getElementById("BTN_" + buttonName); + if (button) { + setupButton(button, "sim-button", props); + } + }); }; const setupPins = (props: IProps): void => { - const pins = [ - "PIN_A1", - "PIN_A2", - "PIN_A3", - "PIN_A4", - "PIN_A5", - "PIN_A6", - "PIN_A7" - ]; - pins.forEach(pinName => { - const pin = window.document.getElementById(pinName); - - if (pin) { - const svgPin = (pin as unknown) as SVGElement; - svg.addClass(svgPin, `sim-${pinName}-touch`); - accessibility.makeFocusable(svgPin); - svgPin.onmouseup = e => props.onMouseUp(pin, e); - svgPin.onkeyup = e => props.onKeyEvent(e, false); - svgPin.onmousedown = e => props.onMouseDown(pin, e); - svgPin.onkeydown = e => props.onKeyEvent(e, true); - accessibility.setAria( - svgPin, - "Pin", - `Touch pin ${pinName.substr(pinName.length - 2)}` - ); - } - }); + const pins = [ + "PIN_A1", + "PIN_A2", + "PIN_A3", + "PIN_A4", + "PIN_A5", + "PIN_A6", + "PIN_A7", + ]; + pins.forEach(pinName => { + const pin = window.document.getElementById(pinName); + + if (pin) { + const svgPin = (pin as unknown) as SVGElement; + svg.addClass(svgPin, `sim-${pinName}-touch`); + accessibility.makeFocusable(svgPin); + svgPin.onmouseup = e => props.onMouseUp(pin, e); + svgPin.onkeyup = e => props.onKeyEvent(e, false); + svgPin.onmousedown = e => props.onMouseDown(pin, e); + svgPin.onkeydown = e => props.onKeyEvent(e, true); + accessibility.setAria( + svgPin, + "Pin", + `Touch pin ${pinName.substr(pinName.length - 2)}` + ); + } + }); }; const addButtonLabels = (button: HTMLElement) => { - let label = ""; - if (button.id.match(/AB/) !== null) { - label = "a+b"; - } else if (button.id.match(/A/) !== null) { - label = "a"; - } else if (button.id.match(/B/) !== null) { - label = "b"; - } - accessibility.setAria(button, "button", label); + let label = ""; + if (button.id.match(/AB/) !== null) { + label = "a+b"; + } else if (button.id.match(/A/) !== null) { + label = "a"; + } else if (button.id.match(/B/) !== null) { + label = "b"; + } + accessibility.setAria(button, "button", label); }; const setupButton = (button: HTMLElement, className: string, props: IProps) => { - const svgButton = (button as unknown) as SVGElement; - svg.addClass(svgButton, className); - addButtonLabels(button); - if (className.match(/outer/) !== null) { - accessibility.makeFocusable(svgButton); - } - svgButton.onmousedown = e => props.onMouseDown(button, e); - svgButton.onmouseup = e => props.onMouseUp(button, e); - svgButton.onkeydown = e => props.onKeyEvent(e, true); - svgButton.onkeyup = e => props.onKeyEvent(e, false); - svgButton.onmouseleave = e => props.onMouseLeave(button, e); + const svgButton = (button as unknown) as SVGElement; + svg.addClass(svgButton, className); + addButtonLabels(button); + if (className.match(/outer/) !== null) { + accessibility.makeFocusable(svgButton); + } + svgButton.onmousedown = e => props.onMouseDown(button, e); + svgButton.onmouseup = e => props.onMouseUp(button, e); + svgButton.onkeydown = e => props.onKeyEvent(e, true); + svgButton.onkeyup = e => props.onKeyEvent(e, false); + svgButton.onmouseleave = e => props.onMouseLeave(button, e); }; const setupKeyPresses = ( - onKeyEvent: (event: KeyboardEvent, active: boolean) => void + onKeyEvent: (event: KeyboardEvent, active: boolean) => void ) => { - window.document.addEventListener("keydown", event => { - const keyEvents = [event.key, event.code]; - // Don't listen to keydown events for the switch, run button and enter key - if ( - !( - keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.S) || - keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) || - keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.ENTER) - ) - ) { - onKeyEvent(event, true); - } - }); - window.document.addEventListener("keyup", event => onKeyEvent(event, false)); + window.document.addEventListener("keydown", event => { + const keyEvents = [event.key, event.code]; + // Don't listen to keydown events for the switch, run button and enter key + if ( + !( + keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.S) || + keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) || + keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.ENTER) + ) + ) { + onKeyEvent(event, true); + } + }); + window.document.addEventListener("keyup", event => + onKeyEvent(event, false) + ); }; const setupSwitch = (props: IProps): void => { - const switchElement = window.document.getElementById("SWITCH"); - const swInnerElement = window.document.getElementById("SWITCH_INNER"); - const swHousingElement = window.document.getElementById("SWITCH_HOUSING"); + const switchElement = window.document.getElementById("SWITCH"); + const swInnerElement = window.document.getElementById("SWITCH_INNER"); + const swHousingElement = window.document.getElementById("SWITCH_HOUSING"); - if (switchElement && swInnerElement && swHousingElement) { - const svgSwitch: SVGElement = (switchElement as unknown) as SVGElement; - const svgSwitchInner: SVGElement = (swInnerElement as unknown) as SVGElement; - const svgSwitchHousing: SVGElement = (swHousingElement as unknown) as SVGElement; + if (switchElement && swInnerElement && swHousingElement) { + const svgSwitch: SVGElement = (switchElement as unknown) as SVGElement; + const svgSwitchInner: SVGElement = (swInnerElement as unknown) as SVGElement; + const svgSwitchHousing: SVGElement = (swHousingElement as unknown) as SVGElement; - svg.addClass(svgSwitch, "sim-slide-switch"); + svg.addClass(svgSwitch, "sim-slide-switch"); - svgSwitch.onmouseup = e => props.onMouseUp(switchElement, e); - svgSwitchInner.onmouseup = e => props.onMouseUp(swInnerElement, e); - svgSwitchHousing.onmouseup = e => props.onMouseUp(swHousingElement, e); - svgSwitch.onkeyup = e => props.onKeyEvent(e, false); + svgSwitch.onmouseup = e => props.onMouseUp(switchElement, e); + svgSwitchInner.onmouseup = e => props.onMouseUp(swInnerElement, e); + svgSwitchHousing.onmouseup = e => props.onMouseUp(swHousingElement, e); + svgSwitch.onkeyup = e => props.onKeyEvent(e, false); - accessibility.makeFocusable(svgSwitch); - accessibility.setAria(svgSwitch, "button", "On/Off Switch"); - } + accessibility.makeFocusable(svgSwitch); + accessibility.setAria(svgSwitch, "button", "On/Off Switch"); + } }; export const updateSwitch = (switchState: boolean): void => { - const switchElement = window.document.getElementById("SWITCH"); - const switchInner = (window.document.getElementById( - "SWITCH_INNER" - ) as unknown) as SVGElement; - - if (switchElement && switchInner) { - svg.addClass(switchInner, "sim-slide-switch-inner"); - - if (switchState) { - svg.addClass(switchInner, "on"); - switchInner.setAttribute("transform", "translate(-5,0)"); - } else { - svg.removeClass(switchInner, "on"); - switchInner.removeAttribute("transform"); + const switchElement = window.document.getElementById("SWITCH"); + const switchInner = (window.document.getElementById( + "SWITCH_INNER" + ) as unknown) as SVGElement; + + if (switchElement && switchInner) { + svg.addClass(switchInner, "sim-slide-switch-inner"); + + if (switchState) { + svg.addClass(switchInner, "on"); + switchInner.setAttribute("transform", "translate(-5,0)"); + } else { + svg.removeClass(switchInner, "on"); + switchInner.removeAttribute("transform"); + } + switchElement.setAttribute("aria-pressed", switchState.toString()); } - switchElement.setAttribute("aria-pressed", switchState.toString()); - } }; export const updatePinTouch = (pinState: boolean, id: string): void => { - console.log(`updating ${id} with ${pinState}`); - const pinElement = window.document.getElementById(id); - const pinSvg: SVGElement = (pinElement as unknown) as SVGElement; - - if (pinElement && pinSvg) { - pinElement.setAttribute("aria-pressed", pinState.toString()); - pinState - ? svg.addClass(pinSvg, "pin-pressed") - : svg.removeClass(pinSvg, "pin-pressed"); - } + console.log(`updating ${id} with ${pinState}`); + const pinElement = window.document.getElementById(id); + const pinSvg: SVGElement = (pinElement as unknown) as SVGElement; + + if (pinElement && pinSvg) { + pinElement.setAttribute("aria-pressed", pinState.toString()); + pinState + ? svg.addClass(pinSvg, "pin-pressed") + : svg.removeClass(pinSvg, "pin-pressed"); + } }; export default CpxImage; diff --git a/src/view/components/cpx/Cpx_svg.tsx b/src/view/components/cpx/Cpx_svg.tsx index a1f8cf973..55864d460 100644 --- a/src/view/components/cpx/Cpx_svg.tsx +++ b/src/view/components/cpx/Cpx_svg.tsx @@ -6,2964 +6,2997 @@ import * as React from "react"; export const CPX_SVG = ( - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + ); export default CPX_SVG; diff --git a/src/view/components/cpx/Cpx_svg_style.tsx b/src/view/components/cpx/Cpx_svg_style.tsx index 4be0439c7..72ba0e18a 100644 --- a/src/view/components/cpx/Cpx_svg_style.tsx +++ b/src/view/components/cpx/Cpx_svg_style.tsx @@ -57,7 +57,9 @@ export function rgbToHsl( } // Saturation - l > 50 ? s = 100 * (cDelta / (2 - maxAndMin)) : s = 100 * (cDelta / maxAndMin); + l > 50 + ? (s = 100 * (cDelta / (2 - maxAndMin))) + : (s = 100 * (cDelta / maxAndMin)); } return [Math.floor(h), Math.floor(s), Math.floor(l)]; diff --git a/src/view/components/cpx/Svg_utils.tsx b/src/view/components/cpx/Svg_utils.tsx index faff4c682..b80e66165 100644 --- a/src/view/components/cpx/Svg_utils.tsx +++ b/src/view/components/cpx/Svg_utils.tsx @@ -4,70 +4,70 @@ // Adapted from : https://github.com/microsoft/pxt/blob/master/pxtsim/svg.ts namespace svg { - export function addClass(el: SVGElement, cls: string) { - if (el.classList) el.classList.add(cls); - else if (el.className.baseVal.indexOf(cls) < 0) - el.className.baseVal += " " + cls; - } + export function addClass(el: SVGElement, cls: string) { + if (el.classList) el.classList.add(cls); + else if (el.className.baseVal.indexOf(cls) < 0) + el.className.baseVal += " " + cls; + } - export function removeClass(el: SVGElement, cls: string) { - if (el.classList) el.classList.remove(cls); - else - el.className.baseVal = el.className.baseVal - .replace(cls, "") - .replace(/\s{2,}/, " "); - } + export function removeClass(el: SVGElement, cls: string) { + if (el.classList) el.classList.remove(cls); + else + el.className.baseVal = el.className.baseVal + .replace(cls, "") + .replace(/\s{2,}/, " "); + } - export function hydrate(el: SVGElement, props: any) { - for (let k in props) { - if (k == "title") { - svg.title(el, props[k]); - } else el.setAttributeNS(null, k, props[k]); + export function hydrate(el: SVGElement, props: any) { + for (let k in props) { + if (k == "title") { + svg.title(el, props[k]); + } else el.setAttributeNS(null, k, props[k]); + } } - } - export function createElement(name: string, props?: any): SVGElement { - let newElement = document.createElementNS( - "http://www.w3.org/2000/svg", - name - ); - if (props) svg.hydrate(newElement, props); - return newElement; - } + export function createElement(name: string, props?: any): SVGElement { + let newElement = document.createElementNS( + "http://www.w3.org/2000/svg", + name + ); + if (props) svg.hydrate(newElement, props); + return newElement; + } - export function child( - parent: Element, - name: string, - props?: any - ): SVGElement { - let childElement = svg.createElement(name, props); - parent.appendChild(childElement); - return childElement; - } + export function child( + parent: Element, + name: string, + props?: any + ): SVGElement { + let childElement = svg.createElement(name, props); + parent.appendChild(childElement); + return childElement; + } - export function fill(el: SVGElement, c: string) { - el.style.fill = c; - } + export function fill(el: SVGElement, c: string) { + el.style.fill = c; + } - export function filter(el: SVGElement, c: string) { - el.style.filter = c; - } + export function filter(el: SVGElement, c: string) { + el.style.filter = c; + } - export function fills(els: SVGElement[], c: string) { - els.forEach(el => (el.style.fill = c)); - } + export function fills(els: SVGElement[], c: string) { + els.forEach(el => (el.style.fill = c)); + } - export function mkTitle(txt: string): SVGTitleElement { - let t = svg.createElement("title") as SVGTitleElement; - t.textContent = txt; - return t; - } + export function mkTitle(txt: string): SVGTitleElement { + let t = svg.createElement("title") as SVGTitleElement; + t.textContent = txt; + return t; + } - export function title(el: SVGElement, txt: string): SVGTitleElement { - let t = mkTitle(txt); - el.appendChild(t); - return t; - } + export function title(el: SVGElement, txt: string): SVGTitleElement { + let t = mkTitle(txt); + el.appendChild(t); + return t; + } } export default svg; diff --git a/src/view/components/simulator/ActionBar.tsx b/src/view/components/simulator/ActionBar.tsx index 35b181277..2188555b2 100644 --- a/src/view/components/simulator/ActionBar.tsx +++ b/src/view/components/simulator/ActionBar.tsx @@ -2,41 +2,41 @@ // Licensed under the MIT license. import * as React from "react"; -import {CONSTANTS} from "../../constants" +import { CONSTANTS } from "../../constants"; import RefreshLogo from "../../svgs/refresh_svg"; import Button from "../Button"; -interface IProps{ - onTogglePlay: (event: React.MouseEvent) => void, - onToggleRefresh: (event: React.MouseEvent) => void, - playStopImage: JSX.Element +interface IProps { + onTogglePlay: (event: React.MouseEvent) => void; + onToggleRefresh: (event: React.MouseEvent) => void; + playStopImage: JSX.Element; } // Component including the actions done on the Simulator (play/stop, refresh) -class ActionBar extends React.Component{ - public render(){ - const {onTogglePlay,onToggleRefresh,playStopImage}=this.props; - return( +class ActionBar extends React.Component { + public render() { + const { onTogglePlay, onToggleRefresh, playStopImage } = this.props; + return (
-
- ) + - ); + return ( + + ); }; export default SensorButton; diff --git a/src/view/components/toolbar/SensorModalUtils.tsx b/src/view/components/toolbar/SensorModalUtils.tsx index c10d6b8c0..134e7dcc1 100644 --- a/src/view/components/toolbar/SensorModalUtils.tsx +++ b/src/view/components/toolbar/SensorModalUtils.tsx @@ -9,189 +9,189 @@ import MotionSensorBar from "./MotionSensorBar"; import TemperatureSensorBar from "./TemperatureSensorBar"; export const TRY_IT_MAKE_CODE = ( - + ); export const TOOLBAR_ICON_LABEL = { - GPIO: "GPIO", - IR: "IR", - LEFT_EDGE: "left-edge", - LIGHT: "Light sensor", - MOTION: "Motion Sensor", - NEO_PIXEL: "Neo Pixels", - PUSH_BUTTON: "Push Button", - RED_LED: "Red LED", - RIGHT_EDGE: "right-edge", - SOUND: "Sound Sensor", - SPEAKER: "Speaker", - SWITCH: "Switch", - TAG_INPUT: "Tag Input", - TAG_OUTPUT: "Tag Output", - TEMPERATURE: "Temperature Sensor" + GPIO: "GPIO", + IR: "IR", + LEFT_EDGE: "left-edge", + LIGHT: "Light sensor", + MOTION: "Motion Sensor", + NEO_PIXEL: "Neo Pixels", + PUSH_BUTTON: "Push Button", + RED_LED: "Red LED", + RIGHT_EDGE: "right-edge", + SOUND: "Sound Sensor", + SPEAKER: "Speaker", + SWITCH: "Switch", + TAG_INPUT: "Tag Input", + TAG_OUTPUT: "Tag Output", + TEMPERATURE: "Temperature Sensor", }; export const TOOLBAR_ICON_ID = { - GPIO: "toolbar-gpio", - IR: "toolbar-ir-sensor", - LEFT_EDGE: "left-edge", - LIGHT: "toolbar-light-sensor", - MOTION: "toolbar-motion-sensor", - NEO_PIXEL: "toolbar-neo-pixels", - PUSH_BUTTON: "toolbar-push-button", - RED_LED: "toolbar-red-led", - RIGHT_EDGE: "right-edge", - SOUND: "toolbar-sound-sensor", - SPEAKER: "toolbar-speaker", - SWITCH: "toolbar-slider-switch", - TEMPERATURE: "toolbar-temperature-sensor" + GPIO: "toolbar-gpio", + IR: "toolbar-ir-sensor", + LEFT_EDGE: "left-edge", + LIGHT: "toolbar-light-sensor", + MOTION: "toolbar-motion-sensor", + NEO_PIXEL: "toolbar-neo-pixels", + PUSH_BUTTON: "toolbar-push-button", + RED_LED: "toolbar-red-led", + RIGHT_EDGE: "right-edge", + SOUND: "toolbar-sound-sensor", + SPEAKER: "toolbar-speaker", + SWITCH: "toolbar-slider-switch", + TEMPERATURE: "toolbar-temperature-sensor", }; export interface IModalContent { - component: any; - descriptionText: string; - descriptionTitle: string; - id: string; - tagInput: any; - tagOutput: any; - tryItDescription: string; - tryItTitle: string; + component: any; + descriptionText: string; + descriptionTitle: string; + id: string; + tagInput: any; + tagOutput: any; + tryItDescription: string; + tryItTitle: string; } export const DEFAULT_MODAL_CONTENT: IModalContent = { - descriptionTitle: "default", - tagInput: undefined, - tagOutput: undefined, - descriptionText: "none", - tryItTitle: "none", - tryItDescription: "none", - component: undefined, - id: "none" + descriptionTitle: "default", + tagInput: undefined, + tagOutput: undefined, + descriptionText: "none", + tryItTitle: "none", + tryItDescription: "none", + component: undefined, + id: "none", }; export const GPIO_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-gpio.title", - tagInput: TAG_INPUT_SVG, - tagOutput: TAG_OUTPUT_SVG, - descriptionText: "toolbar-gpio.description", - tryItTitle: "Simulation Coming Soon!", - tryItDescription: "toolbar-gpio.tryItDescription", - component: undefined, - id: "GPIO" + descriptionTitle: "toolbar-gpio.title", + tagInput: TAG_INPUT_SVG, + tagOutput: TAG_OUTPUT_SVG, + descriptionText: "toolbar-gpio.description", + tryItTitle: "Simulation Coming Soon!", + tryItDescription: "toolbar-gpio.tryItDescription", + component: undefined, + id: "GPIO", }; export const IR_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-ir-sensor.title", - tagInput: TAG_INPUT_SVG, - tagOutput: TAG_OUTPUT_SVG, - descriptionText: "toolbar-ir-sensor.description", - tryItTitle: "Simulation Coming Soon!", - tryItDescription: "toolbar-ir-sensor.tryItDescription", - component: TRY_IT_MAKE_CODE, - id: "IR" + descriptionTitle: "toolbar-ir-sensor.title", + tagInput: TAG_INPUT_SVG, + tagOutput: TAG_OUTPUT_SVG, + descriptionText: "toolbar-ir-sensor.description", + tryItTitle: "Simulation Coming Soon!", + tryItDescription: "toolbar-ir-sensor.tryItDescription", + component: TRY_IT_MAKE_CODE, + id: "IR", }; export const LIGHT_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-light-sensor.title", - tagInput: TAG_INPUT_SVG, - tagOutput: undefined, - descriptionText: "toolbar-light-sensor.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-light-sensor.tryItDescription", - component: , - id: "light_sensor" + descriptionTitle: "toolbar-light-sensor.title", + tagInput: TAG_INPUT_SVG, + tagOutput: undefined, + descriptionText: "toolbar-light-sensor.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-light-sensor.tryItDescription", + component: , + id: "light_sensor", }; export const MOTION_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-motion-sensor.title", - tagInput: TAG_INPUT_SVG, - tagOutput: undefined, - descriptionText: "toolbar-motion-sensor.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-motion-sensor.tryItDescription", - component: , - id: "motion_sensor" + descriptionTitle: "toolbar-motion-sensor.title", + tagInput: TAG_INPUT_SVG, + tagOutput: undefined, + descriptionText: "toolbar-motion-sensor.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-motion-sensor.tryItDescription", + component: , + id: "motion_sensor", }; export const NEOP_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-neo-pixels.title", - tagInput: undefined, - tagOutput: TAG_OUTPUT_SVG, - descriptionText: "toolbar-neo-pixels.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-neo-pixels.tryItDescription", - component: undefined, - id: "neon_pixel" + descriptionTitle: "toolbar-neo-pixels.title", + tagInput: undefined, + tagOutput: TAG_OUTPUT_SVG, + descriptionText: "toolbar-neo-pixels.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-neo-pixels.tryItDescription", + component: undefined, + id: "neon_pixel", }; export const PUSHB_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-push-button.title", - tagInput: TAG_INPUT_SVG, - tagOutput: undefined, - descriptionText: "toolbar-push-button.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-push-button.tryItDescription", - component: undefined, - id: "push_btn" + descriptionTitle: "toolbar-push-button.title", + tagInput: TAG_INPUT_SVG, + tagOutput: undefined, + descriptionText: "toolbar-push-button.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-push-button.tryItDescription", + component: undefined, + id: "push_btn", }; export const RED_LED_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-red-led.title", - tagInput: undefined, - tagOutput: TAG_OUTPUT_SVG, - descriptionText: "toolbar-red-led.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-red-led.tryItDescription", - component: undefined, - id: "red_LED" + descriptionTitle: "toolbar-red-led.title", + tagInput: undefined, + tagOutput: TAG_OUTPUT_SVG, + descriptionText: "toolbar-red-led.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-red-led.tryItDescription", + component: undefined, + id: "red_LED", }; export const SOUND_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-sound-sensor.title", - tagInput: TAG_INPUT_SVG, - tagOutput: undefined, - descriptionText: "toolbar-sound-sensor.description", - tryItTitle: "Simulation Coming Soon!", - tryItDescription: "toolbar-sound-sensor.tryItDescription", - component: TRY_IT_MAKE_CODE, - id: "sound_sensor" + descriptionTitle: "toolbar-sound-sensor.title", + tagInput: TAG_INPUT_SVG, + tagOutput: undefined, + descriptionText: "toolbar-sound-sensor.description", + tryItTitle: "Simulation Coming Soon!", + tryItDescription: "toolbar-sound-sensor.tryItDescription", + component: TRY_IT_MAKE_CODE, + id: "sound_sensor", }; export const SWITCH_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-slider-switch.title", - tagInput: TAG_INPUT_SVG, - tagOutput: undefined, - descriptionText: "toolbar-slider-switch.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-slider-switch.tryItDescription", - component: undefined, - id: "slider_switch" + descriptionTitle: "toolbar-slider-switch.title", + tagInput: TAG_INPUT_SVG, + tagOutput: undefined, + descriptionText: "toolbar-slider-switch.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-slider-switch.tryItDescription", + component: undefined, + id: "slider_switch", }; export const SPEAKER_MODAL_CONTENT: IModalContent = { - descriptionTitle: "toolbar-speaker.title", - tagInput: undefined, - tagOutput: TAG_OUTPUT_SVG, - descriptionText: "toolbar-speaker.description", - tryItTitle: "Try it on the Simulator!", - tryItDescription: "toolbar-speaker.tryItDescription", - component: undefined, - id: "speaker" + descriptionTitle: "toolbar-speaker.title", + tagInput: undefined, + tagOutput: TAG_OUTPUT_SVG, + descriptionText: "toolbar-speaker.description", + tryItTitle: "Try it on the Simulator!", + tryItDescription: "toolbar-speaker.tryItDescription", + component: undefined, + id: "speaker", }; export const TEMPERATURE_MODAL_CONTENT: IModalContent = { - component: , - descriptionText: "toolbar-temperature-sensor.description", - descriptionTitle: "toolbar-temperature-sensor.title", - id: "temperature", - tagInput: TAG_INPUT_SVG, - tagOutput: undefined, - tryItDescription: "toolbar-temperature-sensor.tryItDescription", - tryItTitle: "Try it on the Simulator!" + component: , + descriptionText: "toolbar-temperature-sensor.description", + descriptionTitle: "toolbar-temperature-sensor.title", + id: "temperature", + tagInput: TAG_INPUT_SVG, + tagOutput: undefined, + tryItDescription: "toolbar-temperature-sensor.tryItDescription", + tryItTitle: "Try it on the Simulator!", }; export const LABEL_TO_MODAL_CONTENT = new Map([ - [TOOLBAR_ICON_ID.GPIO, GPIO_MODAL_CONTENT], - [TOOLBAR_ICON_ID.IR, IR_MODAL_CONTENT], - [TOOLBAR_ICON_ID.LIGHT, LIGHT_MODAL_CONTENT], - [TOOLBAR_ICON_ID.MOTION, MOTION_MODAL_CONTENT], - [TOOLBAR_ICON_ID.NEO_PIXEL, NEOP_MODAL_CONTENT], - [TOOLBAR_ICON_ID.PUSH_BUTTON, PUSHB_MODAL_CONTENT], - [TOOLBAR_ICON_ID.RED_LED, RED_LED_MODAL_CONTENT], - [TOOLBAR_ICON_ID.SOUND, SOUND_MODAL_CONTENT], - [TOOLBAR_ICON_ID.SPEAKER, SPEAKER_MODAL_CONTENT], - [TOOLBAR_ICON_ID.SWITCH, SWITCH_MODAL_CONTENT], - [TOOLBAR_ICON_ID.TEMPERATURE, TEMPERATURE_MODAL_CONTENT] + [TOOLBAR_ICON_ID.GPIO, GPIO_MODAL_CONTENT], + [TOOLBAR_ICON_ID.IR, IR_MODAL_CONTENT], + [TOOLBAR_ICON_ID.LIGHT, LIGHT_MODAL_CONTENT], + [TOOLBAR_ICON_ID.MOTION, MOTION_MODAL_CONTENT], + [TOOLBAR_ICON_ID.NEO_PIXEL, NEOP_MODAL_CONTENT], + [TOOLBAR_ICON_ID.PUSH_BUTTON, PUSHB_MODAL_CONTENT], + [TOOLBAR_ICON_ID.RED_LED, RED_LED_MODAL_CONTENT], + [TOOLBAR_ICON_ID.SOUND, SOUND_MODAL_CONTENT], + [TOOLBAR_ICON_ID.SPEAKER, SPEAKER_MODAL_CONTENT], + [TOOLBAR_ICON_ID.SWITCH, SWITCH_MODAL_CONTENT], + [TOOLBAR_ICON_ID.TEMPERATURE, TEMPERATURE_MODAL_CONTENT], ]); diff --git a/src/view/components/toolbar/TemperatureSensorBar.tsx b/src/view/components/toolbar/TemperatureSensorBar.tsx index db4687ee4..2c396494f 100644 --- a/src/view/components/toolbar/TemperatureSensorBar.tsx +++ b/src/view/components/toolbar/TemperatureSensorBar.tsx @@ -7,48 +7,62 @@ import { ISensorProps, ISliderProps, X_SLIDER_INDEX } from "../../viewUtils"; import InputSlider from "./InputSlider"; const TEMPERATURE_SLIDER_PROPS: ISliderProps = { - axisLabel: " ", - maxLabel: "Hot", - maxValue: 125, - minLabel: "Cold", - minValue: -55, - type: "temperature" + axisLabel: " ", + maxLabel: "Hot", + maxValue: 125, + minLabel: "Cold", + minValue: -55, + type: "temperature", }; const TEMPERATURE_SENSOR_PROPERTIES: ISensorProps = { - LABEL: "Temperature sensor", - sliderProps: [TEMPERATURE_SLIDER_PROPS], - unitLabel: "°C" + LABEL: "Temperature sensor", + sliderProps: [TEMPERATURE_SLIDER_PROPS], + unitLabel: "°C", }; class TemperatureSensorBar extends React.Component { - constructor(props: any) { - super(props); - } + constructor(props: any) { + super(props); + } - render() { - return ( -
- -
- ); - } + render() { + return ( +
+ +
+ ); + } } export default TemperatureSensorBar; diff --git a/src/view/components/toolbar/ToolBar.tsx b/src/view/components/toolbar/ToolBar.tsx index 0e755506c..88b064261 100644 --- a/src/view/components/toolbar/ToolBar.tsx +++ b/src/view/components/toolbar/ToolBar.tsx @@ -1,147 +1,159 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import * as React from "react"; -import { FormattedMessage, injectIntl, WrappedComponentProps } from "react-intl"; +import { + FormattedMessage, + injectIntl, + WrappedComponentProps, +} from "react-intl"; import "../../styles/ToolBar.css"; import Button from "../Button"; import { - DEFAULT_MODAL_CONTENT, - IModalContent, - LABEL_TO_MODAL_CONTENT, + DEFAULT_MODAL_CONTENT, + IModalContent, + LABEL_TO_MODAL_CONTENT, } from "./SensorModalUtils"; interface IToolbarState { - currentOpenedId: string; - showModal: boolean; + currentOpenedId: string; + showModal: boolean; } interface IProps extends WrappedComponentProps { - buttonList: Array<{ - label: any, - image: any - }> + buttonList: Array<{ + label: any; + image: any; + }>; } class ToolBar extends React.Component { - private readonly TOOLBAR_BUTTON_WIDTH: number = 32; - - constructor(props: IProps) { - super(props); - this.state = { - currentOpenedId: "", - showModal: false - }; - } + private readonly TOOLBAR_BUTTON_WIDTH: number = 32; - public render() { - const { buttonList } = this.props - return ( -
-
-
- {buttonList.map((currrentButton: any, index: number) => { - return ( -
- {this.getIconModal()} -
-
- ); - } - - private handleOnClick = ( - event: React.MouseEvent, - label: string - ) => { - if ( - !this.state.showModal && - this.state.currentOpenedId === "" && - this.state.currentOpenedId !== label - ) { - this.openModal(label); - } else { - this.closeCurrentModal(); - if (this.state.currentOpenedId !== label) { - this.openModal(label); - } + constructor(props: IProps) { + super(props); + this.state = { + currentOpenedId: "", + showModal: false, + }; } - }; - private changePressedState(id: string, pressed: boolean) { - const elt = window.document.getElementById(`${id}-button`); - if (elt) { - pressed - ? elt.classList.add("button-pressed") - : elt.classList.remove("button-pressed"); + public render() { + const { buttonList } = this.props; + return ( +
+
+
+ {buttonList.map( + (currrentButton: any, index: number) => { + return ( +
+ {this.getIconModal()} +
+
+ ); } - } - private closeCurrentModal = () => { - this.changePressedState(this.state.currentOpenedId, false); - this.setState({ - currentOpenedId: "", - showModal: false - }); - }; - private openModal = (label: string) => { - this.setState({ - currentOpenedId: label, - showModal: true - }); - this.changePressedState(label, true); - }; + private handleOnClick = ( + event: React.MouseEvent, + label: string + ) => { + if ( + !this.state.showModal && + this.state.currentOpenedId === "" && + this.state.currentOpenedId !== label + ) { + this.openModal(label); + } else { + this.closeCurrentModal(); + if (this.state.currentOpenedId !== label) { + this.openModal(label); + } + } + }; - private getIconModal() { - if ( - !this.state.showModal || - !LABEL_TO_MODAL_CONTENT.get(this.state.currentOpenedId) - ) { - return null; + private changePressedState(id: string, pressed: boolean) { + const elt = window.document.getElementById(`${id}-button`); + if (elt) { + pressed + ? elt.classList.add("button-pressed") + : elt.classList.remove("button-pressed"); + } } + private closeCurrentModal = () => { + this.changePressedState(this.state.currentOpenedId, false); + this.setState({ + currentOpenedId: "", + showModal: false, + }); + }; + + private openModal = (label: string) => { + this.setState({ + currentOpenedId: label, + showModal: true, + }); + this.changePressedState(label, true); + }; - const content = LABEL_TO_MODAL_CONTENT.get( - this.state.currentOpenedId - ) as IModalContent; + private getIconModal() { + if ( + !this.state.showModal || + !LABEL_TO_MODAL_CONTENT.get(this.state.currentOpenedId) + ) { + return null; + } - const component = content - ? content.component - : DEFAULT_MODAL_CONTENT.component; - return ( -
-
- - - {content.tagInput} - {content.tagOutput} - -
-
-
- -
-
-
- - - -
+ const content = LABEL_TO_MODAL_CONTENT.get( + this.state.currentOpenedId + ) as IModalContent; -
{component}
-
-
- ); - } + const component = content + ? content.component + : DEFAULT_MODAL_CONTENT.component; + return ( +
+
+ + + {content.tagInput} + {content.tagOutput} + +
+
+
+ +
+
+
+ + + +
+ +
{component}
+
+
+ ); + } } export default injectIntl(ToolBar); diff --git a/src/view/constants.ts b/src/view/constants.ts index 372d77cbf..64f6a71f6 100644 --- a/src/view/constants.ts +++ b/src/view/constants.ts @@ -3,43 +3,43 @@ // Key events export const CONSTANTS = { - CURRENTLY_RUNNING: (file: string) => { - return `Currently running: ${file}` - }, - ID_NAME: { - BUTTON_A: "BTN_A_OUTER", - BUTTON_AB: "BTN_AB_OUTER", - BUTTON_B: "BTN_B_OUTER", - PIN_A1: "PIN_A1", - PIN_A2: "PIN_A2", - PIN_A3: "PIN_A3", - PIN_A4: "PIN_A4", - PIN_A5: "PIN_A5", - PIN_A6: "PIN_A6", - PIN_A7: "PIN_A7", - PLAY_BUTTON: "play-button", - REFRESH_BUTTON: "refresh-button", - STOP_BUTTON: "stop-button", - SWITCH: "SWITCH" - }, - KEYBOARD_KEYS: { - A: "KeyA", - B: "KeyB", - CAPITAL_R: "R", - CAPITAL_F: "F", - ENTER: "Enter", - S: "KeyS", - NUMERIC_ONE: "Digit1", - NUMERIC_TWO: "Digit2", - NUMERIC_THREE: "Digit3", - NUMERIC_FOUR: "Digit4", - NUMERIC_FIVE: "Digit5", - NUMERIC_SIX: "Digit6", - NUMERIC_SEVEN: "Digit7" - }, - NO_FILES_AVAILABLE: "Choose a .py file to run on the Simulator", - SIMULATOR_BUTTON_WIDTH: 60, - TOOLBAR_INFO: `Explore what's on the board:`, + CURRENTLY_RUNNING: (file: string) => { + return `Currently running: ${file}`; + }, + ID_NAME: { + BUTTON_A: "BTN_A_OUTER", + BUTTON_AB: "BTN_AB_OUTER", + BUTTON_B: "BTN_B_OUTER", + PIN_A1: "PIN_A1", + PIN_A2: "PIN_A2", + PIN_A3: "PIN_A3", + PIN_A4: "PIN_A4", + PIN_A5: "PIN_A5", + PIN_A6: "PIN_A6", + PIN_A7: "PIN_A7", + PLAY_BUTTON: "play-button", + REFRESH_BUTTON: "refresh-button", + STOP_BUTTON: "stop-button", + SWITCH: "SWITCH", + }, + KEYBOARD_KEYS: { + A: "KeyA", + B: "KeyB", + CAPITAL_R: "R", + CAPITAL_F: "F", + ENTER: "Enter", + S: "KeyS", + NUMERIC_ONE: "Digit1", + NUMERIC_TWO: "Digit2", + NUMERIC_THREE: "Digit3", + NUMERIC_FOUR: "Digit4", + NUMERIC_FIVE: "Digit5", + NUMERIC_SIX: "Digit6", + NUMERIC_SEVEN: "Digit7", + }, + NO_FILES_AVAILABLE: "Choose a .py file to run on the Simulator", + SIMULATOR_BUTTON_WIDTH: 60, + TOOLBAR_INFO: `Explore what's on the board:`, }; export default CONSTANTS; diff --git a/src/view/container/device/Device.tsx b/src/view/container/device/Device.tsx index 7b88550b1..1a66b9b2f 100644 --- a/src/view/container/device/Device.tsx +++ b/src/view/container/device/Device.tsx @@ -3,46 +3,56 @@ import * as React from "react"; import Simulator from "../../components/Simulator"; -import { - TOOLBAR_ICON_ID -} from "../../components/toolbar/SensorModalUtils"; +import { TOOLBAR_ICON_ID } from "../../components/toolbar/SensorModalUtils"; import ToolBar from "../../components/toolbar/ToolBar"; import * as TOOLBAR_SVG from "../../svgs/toolbar_svg"; -const CPX_TOOLBAR_BUTTONS: Array<{ label: any, image: any }> = [{ - image: TOOLBAR_SVG.SLIDER_SWITCH_SVG, - label: TOOLBAR_ICON_ID.SWITCH, -},{ - image: TOOLBAR_SVG.PUSH_BUTTON_SVG, - label: TOOLBAR_ICON_ID.PUSH_BUTTON, -},{ - image: TOOLBAR_SVG.RED_LED_SVG, - label: TOOLBAR_ICON_ID.RED_LED, -},{ - image: TOOLBAR_SVG.SOUND_SVG, - label: TOOLBAR_ICON_ID.SOUND, -},{ - image: TOOLBAR_SVG.TEMPERATURE_SVG, - label: TOOLBAR_ICON_ID.TEMPERATURE, -},{ - image: TOOLBAR_SVG.LIGHT_SVG, - label: TOOLBAR_ICON_ID.LIGHT, -},{ - image: TOOLBAR_SVG.NEO_PIXEL_SVG, - label: TOOLBAR_ICON_ID.NEO_PIXEL, -},{ - image: TOOLBAR_SVG.SPEAKER_SVG, - label: TOOLBAR_ICON_ID.SPEAKER, -},{ - image: TOOLBAR_SVG.MOTION_SVG, - label: TOOLBAR_ICON_ID.MOTION, -},{ - image: TOOLBAR_SVG.IR_SVG, - label: TOOLBAR_ICON_ID.IR, -},{ - image: TOOLBAR_SVG.GPIO_SVG, - label: TOOLBAR_ICON_ID.GPIO, -}] +const CPX_TOOLBAR_BUTTONS: Array<{ label: any; image: any }> = [ + { + image: TOOLBAR_SVG.SLIDER_SWITCH_SVG, + label: TOOLBAR_ICON_ID.SWITCH, + }, + { + image: TOOLBAR_SVG.PUSH_BUTTON_SVG, + label: TOOLBAR_ICON_ID.PUSH_BUTTON, + }, + { + image: TOOLBAR_SVG.RED_LED_SVG, + label: TOOLBAR_ICON_ID.RED_LED, + }, + { + image: TOOLBAR_SVG.SOUND_SVG, + label: TOOLBAR_ICON_ID.SOUND, + }, + { + image: TOOLBAR_SVG.TEMPERATURE_SVG, + label: TOOLBAR_ICON_ID.TEMPERATURE, + }, + { + image: TOOLBAR_SVG.LIGHT_SVG, + label: TOOLBAR_ICON_ID.LIGHT, + }, + { + image: TOOLBAR_SVG.NEO_PIXEL_SVG, + label: TOOLBAR_ICON_ID.NEO_PIXEL, + }, + { + image: TOOLBAR_SVG.SPEAKER_SVG, + label: TOOLBAR_ICON_ID.SPEAKER, + }, + { + image: TOOLBAR_SVG.MOTION_SVG, + label: TOOLBAR_ICON_ID.MOTION, + }, + { + image: TOOLBAR_SVG.IR_SVG, + label: TOOLBAR_ICON_ID.IR, + }, + { + image: TOOLBAR_SVG.GPIO_SVG, + label: TOOLBAR_ICON_ID.GPIO, + }, +]; // Container to switch between multiple devices @@ -53,7 +63,7 @@ class Device extends React.Component { - ) + ); } } -export default Device; \ No newline at end of file +export default Device; diff --git a/src/view/index.css b/src/view/index.css index 4c477d278..918c31f9e 100644 --- a/src/view/index.css +++ b/src/view/index.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css?family=Joti+One'); +@import url("https://fonts.googleapis.com/css?family=Joti+One"); body { margin: 0; @@ -6,7 +6,8 @@ body { font-family: sans-serif; } -html, body { +html, +body { overflow: hidden; height: 100%; -} \ No newline at end of file +} diff --git a/src/view/index.tsx b/src/view/index.tsx index 7bac57e1e..9b0fc44a2 100644 --- a/src/view/index.tsx +++ b/src/view/index.tsx @@ -12,12 +12,12 @@ const messageEn = require("./translations/en.json"); const locale = "en"; const message = { - en: messageEn + en: messageEn, }; ReactDOM.render( - - - , - document.getElementById("root") + + + , + document.getElementById("root") ); diff --git a/src/view/styles/Button.css b/src/view/styles/Button.css index bd2b1c8fe..78c8372a5 100644 --- a/src/view/styles/Button.css +++ b/src/view/styles/Button.css @@ -1,49 +1,49 @@ .button { - height: 32px; - background: var(--vscode-debugToolBar-background); - border-color: var(--vscode-debugToolBar-background); + height: 32px; + background: var(--vscode-debugToolBar-background); + border-color: var(--vscode-debugToolBar-background); } .button-icon { - fill: var(--vscode-badgeForegroundOverride); + fill: var(--vscode-badgeForegroundOverride); } .button-rectangle { - stroke: var(--vscode-badgeForegroundOverride); + stroke: var(--vscode-badgeForegroundOverride); } .play-button { - border-radius: 8px 0px 0px 8px; - border-color: var(--vscode-highContrastButtonBorderOverride-color); + border-radius: 8px 0px 0px 8px; + border-color: var(--vscode-highContrastButtonBorderOverride-color); } .refresh-button { - border-radius: 0px 8px 8px 0px; - border-color: var(--vscode-highContrastButtonBorderOverride-color); + border-radius: 0px 8px 8px 0px; + border-color: var(--vscode-highContrastButtonBorderOverride-color); } .button:focus, .button:hover { - background-color: var(--vscode-terminal-selectionBackground); + background-color: var(--vscode-terminal-selectionBackground); } .button:active { - background-color: var(--vscode-editor-selectionHighlightBackground); + background-color: var(--vscode-editor-selectionHighlightBackground); } .toolbar-button { - border: none; + border: none; } .toolbar-button:hover { - outline: none; + outline: none; } .edge-button { - pointer-events: none; - border: none; + pointer-events: none; + border: none; } .button-pressed { - background-color: var(--vscode-button-background); - outline: none; + background-color: var(--vscode-button-background); + outline: none; } diff --git a/src/view/styles/Dropdown.css b/src/view/styles/Dropdown.css index c1b355d26..3d6243fc3 100644 --- a/src/view/styles/Dropdown.css +++ b/src/view/styles/Dropdown.css @@ -1,27 +1,27 @@ .dropdown { - background: var(--vscode-debugToolBar-background); - border-color: var(--vscode-highContrastButtonBorderOverride-color); - border-radius: 2px; - max-width: 300px; - min-width: 240px; - box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.22); - color: var(--vscode-foreground); - height: 32px; - padding-left: 8px; - padding-right: 4px; + background: var(--vscode-debugToolBar-background); + border-color: var(--vscode-highContrastButtonBorderOverride-color); + border-radius: 2px; + max-width: 300px; + min-width: 240px; + box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.22); + color: var(--vscode-foreground); + height: 32px; + padding-left: 8px; + padding-right: 4px; } select.dropdown:hover, select.dropdown:focus, select.dropdown:active { - outline: 1px solid var(--vscode-button-background); - outline-offset: 1px; + outline: 1px solid var(--vscode-button-background); + outline-offset: 1px; } option { - height: 32px; - background: var(--vscode-debugToolBar-background); - align-items: center; - font-size: 14px; - color: var(--vscode-foreground); + height: 32px; + background: var(--vscode-debugToolBar-background); + align-items: center; + font-size: 14px; + color: var(--vscode-foreground); } diff --git a/src/view/styles/InputSlider.css b/src/view/styles/InputSlider.css index 8bd87a83c..b51dae8f3 100644 --- a/src/view/styles/InputSlider.css +++ b/src/view/styles/InputSlider.css @@ -1,103 +1,103 @@ :root { - --slider-gray-color: #cccccc; - --slider-width: 240px; + --slider-gray-color: #cccccc; + --slider-width: 240px; } .inputSlider { - height: 48px; - margin-bottom: 60px; - display: table-cell; + height: 48px; + margin-bottom: 60px; + display: table-cell; } .sliderValue { - -webkit-appearance: none; - text-align: center; - width: 48px; - height: 32px; - background-color: var(--vscode-editor-background); - margin-right: 15px; - margin-top: auto; - margin-bottom: auto; - margin-left: 5px; - color: var(--badgeForegroundOverride); - border-radius: 2px; - font-size: 16px; - font-weight: bold; - border-width: 1px; - border-radius: 2px; - border-color: var(--vscode-highContrastButtonBorderOverride-color); + -webkit-appearance: none; + text-align: center; + width: 48px; + height: 32px; + background-color: var(--vscode-editor-background); + margin-right: 15px; + margin-top: auto; + margin-bottom: auto; + margin-left: 5px; + color: var(--badgeForegroundOverride); + border-radius: 2px; + font-size: 16px; + font-weight: bold; + border-width: 1px; + border-radius: 2px; + border-color: var(--vscode-highContrastButtonBorderOverride-color); } .slider { - -webkit-appearance: none; - background-color: var(--slider-gray-color); - height: 1px; - width: var(--slider-width); - vertical-align: middle; + -webkit-appearance: none; + background-color: var(--slider-gray-color); + height: 1px; + width: var(--slider-width); + vertical-align: middle; } .slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--vscode-textLink-activeForeground); - cursor: pointer; + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--vscode-textLink-activeForeground); + cursor: pointer; } .slider::-webkit-slider-runnable-track:focus, .inputSlider:focus, .slider:focus { - outline: none; + outline: none; } .sliderValue:focus, .sliderValue:hover { - -webkit-appearance: none; - color: var(--vscode-textLink-activeForeground); - outline: 1px solid var(--vscode-textLink-activeForeground); + -webkit-appearance: none; + color: var(--vscode-textLink-activeForeground); + outline: 1px solid var(--vscode-textLink-activeForeground); } .maxLabel, .minLabel { - display: inline-block; - position: absolute; - vertical-align: top; + display: inline-block; + position: absolute; + vertical-align: top; } .maxLabel { - right: 0; + right: 0; } .minLabel { - left: 0; + left: 0; } .sliderArea, .sliderValue { - display: inline-block; - vertical-align: middle; + display: inline-block; + vertical-align: middle; } .sliderArea { - width: var(--slider-width); - height: 49px; + width: var(--slider-width); + height: 49px; } .downLabelArea { - width: var(--slider-width); - height: 15px; - margin-top: 10px; - position: relative; - font-size: 14px; + width: var(--slider-width); + height: 15px; + margin-top: 10px; + position: relative; + font-size: 14px; } .upLabelArea { - width: var(--slider-width); - height: 15px; - margin-bottom: 10px; - position: relative; - font-weight: bolder; - font-size: 16px; + width: var(--slider-width); + height: 15px; + margin-bottom: 10px; + position: relative; + font-weight: bolder; + font-size: 16px; } .slider, .upLabelArea, .downLabelArea { - display: block; + display: block; } diff --git a/src/view/styles/LightSensorBar.css b/src/view/styles/LightSensorBar.css index 2d2a84c5e..5d0abac42 100644 --- a/src/view/styles/LightSensorBar.css +++ b/src/view/styles/LightSensorBar.css @@ -1,20 +1,20 @@ .title { - font-size: 14px; - text-align: left; - font-weight: bold; + font-size: 14px; + text-align: left; + font-weight: bold; } .header { - -webkit-appearance: none; - height: 30px; - margin-bottom: 2px; + -webkit-appearance: none; + height: 30px; + margin-bottom: 2px; } .lightSensorBar { - -webkit-appearance: none; - margin-top: 10px; - width: 400px; - margin-left: auto; - margin-right: auto; - padding-bottom: 16px; + -webkit-appearance: none; + margin-top: 10px; + width: 400px; + margin-left: auto; + margin-right: auto; + padding-bottom: 16px; } diff --git a/src/view/styles/MotionSensorBar.css b/src/view/styles/MotionSensorBar.css index 552e1ddcd..76674dbe8 100644 --- a/src/view/styles/MotionSensorBar.css +++ b/src/view/styles/MotionSensorBar.css @@ -1,16 +1,16 @@ .title { - font-size: 14px; - text-align: left; + font-size: 14px; + text-align: left; } .header { - -webkit-appearance: none; - height: 30px; - margin-bottom: 2px; + -webkit-appearance: none; + height: 30px; + margin-bottom: 2px; } .MotionSensorBar { - width: 440px; - margin-left: auto; - margin-right: auto; + width: 440px; + margin-left: auto; + margin-right: auto; } diff --git a/src/view/styles/SensorButton.css b/src/view/styles/SensorButton.css index df85bcef2..8f49d1842 100644 --- a/src/view/styles/SensorButton.css +++ b/src/view/styles/SensorButton.css @@ -1,26 +1,26 @@ .sensor-button { - color: var(--vscode-badgeForegroundOverride); - text-align: center; - background-color: var(--vscode-button-background); - width: 320px; - height: 32px; - font-weight: bolder; - float: left; - padding-left: 20px; - margin-bottom: 20px; - margin-top: 20px; - border-color: var(--vscode-highContrastButtonBorderOverride-color); - border-width: 1px; - border-style: solid; + color: var(--vscode-badgeForegroundOverride); + text-align: center; + background-color: var(--vscode-button-background); + width: 320px; + height: 32px; + font-weight: bolder; + float: left; + padding-left: 20px; + margin-bottom: 20px; + margin-top: 20px; + border-color: var(--vscode-highContrastButtonBorderOverride-color); + border-width: 1px; + border-style: solid; } .sensor-button:focus, .sensor-button:active { - outline-width: thick; - outline-offset: 4px; - outline: 2px solid var(--vscode-focusBorder); - background-color: var(--vscode-button-hoverBackground); + outline-width: thick; + outline-offset: 4px; + outline: 2px solid var(--vscode-focusBorder); + background-color: var(--vscode-button-hoverBackground); } .sensor-button:hover { - background-color: var(--vscode-button-hoverBackground); + background-color: var(--vscode-button-hoverBackground); } diff --git a/src/view/styles/Simulator.css b/src/view/styles/Simulator.css index 5a428910b..2d43b428a 100644 --- a/src/view/styles/Simulator.css +++ b/src/view/styles/Simulator.css @@ -1,20 +1,20 @@ .simulator { - display: flex; - flex-direction: column; - justify-content: center; - max-width: 700px; - max-height: 700px; - margin-left: auto; - margin-right: auto; + display: flex; + flex-direction: column; + justify-content: center; + max-width: 700px; + max-height: 700px; + margin-left: auto; + margin-right: auto; } .buttons { - display: flex; - flex-direction: row; - padding-top: 20px; - justify-content: center; + display: flex; + flex-direction: row; + padding-top: 20px; + justify-content: center; } .file-selector { - padding: 20px; + padding: 20px; } diff --git a/src/view/styles/TemperatureSensorBar.css b/src/view/styles/TemperatureSensorBar.css index e09ab9b44..dfab0b7cd 100644 --- a/src/view/styles/TemperatureSensorBar.css +++ b/src/view/styles/TemperatureSensorBar.css @@ -1,18 +1,18 @@ .title { - font-size: 14px; - text-align: left; - font-weight: bold; + font-size: 14px; + text-align: left; + font-weight: bold; } .header { - -webkit-appearance: none; - height: 30px; - margin-bottom: 2px; + -webkit-appearance: none; + height: 30px; + margin-bottom: 2px; } .temperatureSensorBar { - margin-top: 10px; - width: 440px; - margin-left: auto; - margin-right: auto; - padding-bottom: 16px; + margin-top: 10px; + width: 440px; + margin-left: auto; + margin-right: auto; + padding-bottom: 16px; } diff --git a/src/view/styles/ToolBar.css b/src/view/styles/ToolBar.css index c9087130e..b2cb2bc34 100644 --- a/src/view/styles/ToolBar.css +++ b/src/view/styles/ToolBar.css @@ -1,113 +1,113 @@ .toolbar-parent { - background: var(--vscode-debugToolBar-background); - width: fit-content; - border-radius: 2px; - height: fit-content; - margin-left: auto; - margin-right: auto; - margin-top: 24px; - margin-bottom: 50px; + background: var(--vscode-debugToolBar-background); + width: fit-content; + border-radius: 2px; + height: fit-content; + margin-left: auto; + margin-right: auto; + margin-top: 24px; + margin-bottom: 50px; } -.toolbar{ - box-shadow: 0px 0px 20px rgba(0, 0, 0, 30%); - border-color: var(--vscode-highContrastButtonBorderOverride-color); - border-width: 1px; - border-style: solid; +.toolbar { + box-shadow: 0px 0px 20px rgba(0, 0, 0, 30%); + border-color: var(--vscode-highContrastButtonBorderOverride-color); + border-width: 1px; + border-style: solid; } .toolbar-icon { - box-shadow: 0px 0px 20px rgba(0, 0, 0, 30%); - border-color: var(--vscode-highContrastButtonBorderOverride-color); - border-width: 1px; - border-style: solid; - padding: 0px 8px 0px 8px; + box-shadow: 0px 0px 20px rgba(0, 0, 0, 30%); + border-color: var(--vscode-highContrastButtonBorderOverride-color); + border-width: 1px; + border-style: solid; + padding: 0px 8px 0px 8px; } .tag, .title { - display: inline; + display: inline; } .sensor_modal { - vertical-align: middle; - width: 360px; - max-height: 240px; - overflow-y: scroll; - overflow-x: hidden; - position: relative; - height: fit-content; - padding-left: 16px; - box-shadow: none; - background: var(--vscode-debugToolBar-background); - margin-left: 1px; + vertical-align: middle; + width: 360px; + max-height: 240px; + overflow-y: scroll; + overflow-x: hidden; + position: relative; + height: fit-content; + padding-left: 16px; + box-shadow: none; + background: var(--vscode-debugToolBar-background); + margin-left: 1px; } .title { - -webkit-appearance: none; - font-size: 16px; - font-weight: bolder; - color: var(--vscode-badgeForegroundOverride); - text-align: left; - margin-right: 40px; - position: absolute; - left: 0; - padding-left: inherit; - width: 320px; + -webkit-appearance: none; + font-size: 16px; + font-weight: bolder; + color: var(--vscode-badgeForegroundOverride); + text-align: left; + margin-right: 40px; + position: absolute; + left: 0; + padding-left: inherit; + width: 320px; } .tag { - position: absolute; - left: 200px; + position: absolute; + left: 200px; } .description { - -webkit-appearance: none; - font-size: 14px; - color: var(--vscode-foreground); - word-wrap: break-word; - width: 320px; - margin-top: 15px; - text-align: left; - line-height: 17px; - font-weight: 100; - opacity: 50%; + -webkit-appearance: none; + font-size: 14px; + color: var(--vscode-foreground); + word-wrap: break-word; + width: 320px; + margin-top: 15px; + text-align: left; + line-height: 17px; + font-weight: 100; + opacity: 50%; } .title_group { - margin-top: 20px; - width: fit-content; - padding-left: 16px; + margin-top: 20px; + width: fit-content; + padding-left: 16px; } .try_area { - position: relative; - padding-bottom: 30px; - text-align: left; - line-height: 17px; - font-weight: 100; - opacity: 50%; - font-size: 14px; - word-wrap: break-word; + position: relative; + padding-bottom: 30px; + text-align: left; + line-height: 17px; + font-weight: 100; + opacity: 50%; + font-size: 14px; + word-wrap: break-word; } .link-parent { - padding-top: 12px; - -webkit-appearance: none; - padding-left: 150px; - color: var(--vscode-textLink-activeForeground); - text-align: right; - text-decoration: none; - font-weight: bold; + padding-top: 12px; + -webkit-appearance: none; + padding-left: 150px; + color: var(--vscode-textLink-activeForeground); + text-align: right; + text-decoration: none; + font-weight: bold; } .link-parent:hover { - -webkit-appearance: none; - padding-left: 150px; - color: var(--vscode-textLink-activeForeground); - text-decoration: none; + -webkit-appearance: none; + padding-left: 150px; + color: var(--vscode-textLink-activeForeground); + text-decoration: none; } .link { - -webkit-appearance: none; - text-decoration: none; + -webkit-appearance: none; + text-decoration: none; } diff --git a/src/view/svgs/arrow_right_svg.tsx b/src/view/svgs/arrow_right_svg.tsx index a482812a5..3d7e2f1ab 100644 --- a/src/view/svgs/arrow_right_svg.tsx +++ b/src/view/svgs/arrow_right_svg.tsx @@ -1,20 +1,20 @@ import * as React from "react"; export const ARROW_RIGHT_SVG = ( - - - + + + ); export default ARROW_RIGHT_SVG; diff --git a/src/view/svgs/close_svg.tsx b/src/view/svgs/close_svg.tsx index a37425c79..ac05bedfc 100644 --- a/src/view/svgs/close_svg.tsx +++ b/src/view/svgs/close_svg.tsx @@ -1,26 +1,26 @@ import * as React from "react"; export const CLOSE_SVG = ( - - - - + + + + ); export default CLOSE_SVG; diff --git a/src/view/svgs/play_svg.tsx b/src/view/svgs/play_svg.tsx index ee4ceb487..0da21aa9d 100644 --- a/src/view/svgs/play_svg.tsx +++ b/src/view/svgs/play_svg.tsx @@ -1,19 +1,19 @@ import * as React from "react"; export const PLAY_SVG = ( - - - + + + ); export default PLAY_SVG; diff --git a/src/view/svgs/refresh_svg.tsx b/src/view/svgs/refresh_svg.tsx index 368d64860..340ad03f8 100644 --- a/src/view/svgs/refresh_svg.tsx +++ b/src/view/svgs/refresh_svg.tsx @@ -2,20 +2,20 @@ import * as React from "react"; import "../styles/Button.css"; export const REFRESH_SVG = ( - - - + + + ); export default REFRESH_SVG; diff --git a/src/view/svgs/stop_svg.tsx b/src/view/svgs/stop_svg.tsx index bdcf04660..a89175f66 100644 --- a/src/view/svgs/stop_svg.tsx +++ b/src/view/svgs/stop_svg.tsx @@ -1,15 +1,21 @@ import * as React from "react"; export const STOP_SVG = ( - - - + + + ); export default STOP_SVG; diff --git a/src/view/svgs/tag_input_svg.tsx b/src/view/svgs/tag_input_svg.tsx index ca02485a8..13a507504 100644 --- a/src/view/svgs/tag_input_svg.tsx +++ b/src/view/svgs/tag_input_svg.tsx @@ -1,27 +1,27 @@ import * as React from "react"; export const TAG_INPUT_SVG = ( - - - - + + + + ); export default TAG_INPUT_SVG; diff --git a/src/view/svgs/tag_output_svg.tsx b/src/view/svgs/tag_output_svg.tsx index 3273d2a37..745175db5 100644 --- a/src/view/svgs/tag_output_svg.tsx +++ b/src/view/svgs/tag_output_svg.tsx @@ -1,19 +1,26 @@ import * as React from "react"; export const TAG_OUTPUT_SVG = ( - - - - + + + + ); export default TAG_OUTPUT_SVG; diff --git a/src/view/svgs/toolbar_svg.tsx b/src/view/svgs/toolbar_svg.tsx index 482260990..fccd752f3 100644 --- a/src/view/svgs/toolbar_svg.tsx +++ b/src/view/svgs/toolbar_svg.tsx @@ -3,445 +3,477 @@ import { TOOLBAR_ICON_LABEL } from "../components/toolbar/SensorModalUtils"; import "../styles/Button.css"; export const LEFT_EDGE_SVG = ( - - - - - - - - - - + + + + + + + + + + ); export const RIGHT_EDGE_SVG = ( - - - - - - - - - - + + + + + + + + + + ); export const GPIO_SVG = ( - - GPIO - Created with Sketch. - - - - - - + + GPIO + Created with Sketch. + + + + + + ); export const IR_SVG = ( - - IR - Created with Sketch. - - - - + IR + Created with Sketch. + + stroke="none" + stroke-width="1" + fill="none" + fill-rule="evenodd" + > + + + + + - - - + ); export const LIGHT_SVG = ( - - {TOOLBAR_ICON_LABEL.LIGHT} - - - - - - - + {TOOLBAR_ICON_LABEL.LIGHT} + + + + + + + + + - - - + ); export const MOTION_SVG = ( - - - - - - - + + + + + + + + - - - + ); export const NEO_PIXEL_SVG = ( - - neon_pixel - Created with Sketch. - - - - - - - - - - - + neon_pixel + Created with Sketch. + + + + + + + + + + + + + - - - + ); export const PUSH_BUTTON_SVG = ( - - {TOOLBAR_ICON_LABEL.PUSH_BUTTON} - Created with Sketch. - - - - - + {TOOLBAR_ICON_LABEL.PUSH_BUTTON} + Created with Sketch. + + + + + + + - - - + ); export const RED_LED_SVG = ( - - {TOOLBAR_ICON_LABEL.RED_LED} - Created with Sketch. - - - - - + {TOOLBAR_ICON_LABEL.RED_LED} + Created with Sketch. + + + + + + + - - - + ); export const SLIDER_SWITCH_SVG = ( - - {TOOLBAR_ICON_LABEL.SWITCH} - Created with Sketch. - - - - - + {TOOLBAR_ICON_LABEL.SWITCH} + Created with Sketch. + + + + + + + - - - + ); export const SOUND_SVG = ( - - {TOOLBAR_ICON_LABEL.SOUND} - Created with Sketch. - - - - - - - + {TOOLBAR_ICON_LABEL.SOUND} + Created with Sketch. + + + + + + + + + - - - + ); export const SPEAKER_SVG = ( - - {TOOLBAR_ICON_LABEL.SPEAKER} - Created with Sketch. - - - - + {TOOLBAR_ICON_LABEL.SPEAKER} + Created with Sketch. + + + + + + - - - + ); export const TEMPERATURE_SVG = ( - - {TOOLBAR_ICON_LABEL.TEMPERATURE} - - - - - - - - - + {TOOLBAR_ICON_LABEL.TEMPERATURE} + + + + + + + + + ); export default LEFT_EDGE_SVG; diff --git a/src/view/viewUtils.tsx b/src/view/viewUtils.tsx index c2caa4fe5..2459cbf05 100644 --- a/src/view/viewUtils.tsx +++ b/src/view/viewUtils.tsx @@ -1,27 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. export interface ISliderProps { - minValue: number; - maxValue: number; - maxLabel: string; - minLabel: string; - type: string; - axisLabel: string; + minValue: number; + maxValue: number; + maxLabel: string; + minLabel: string; + type: string; + axisLabel: string; } export interface ISensorButtonProps { - label: string; - type: string; - onMouseUp: () => void; - onMouseDown: () => void; - onKeyUp: (event: React.KeyboardEvent) => void; - onKeyDown: (event: React.KeyboardEvent) => void; + label: string; + type: string; + onMouseUp: () => void; + onMouseDown: () => void; + onKeyUp: (event: React.KeyboardEvent) => void; + onKeyDown: (event: React.KeyboardEvent) => void; } export interface ISensorProps { - LABEL: string; - sliderProps: ISliderProps[]; - unitLabel: string; + LABEL: string; + sliderProps: ISliderProps[]; + unitLabel: string; } export const X_SLIDER_INDEX = 0; diff --git a/tslint.json b/tslint.json index 5d5a3f741..4b199b36e 100644 --- a/tslint.json +++ b/tslint.json @@ -21,10 +21,12 @@ "object-literal-sort-keys": true, "react-hooks-nesting": "error", "ordered-imports": true, - "import-name":false, - "member-access":false, - "no-console":false, - "jsx-boolean-value":false + "import-name": false, + "member-access": false, + "no-console": false, + "jsx-boolean-value": false, + "no-unnecessary-semicolons": false, + "no-http-string": false }, "defaultSeverity": "warning" } From 9ffc0413a83cf988f811a1acc3d6d34aaa259070 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 23 Jan 2020 15:40:08 -0800 Subject: [PATCH 2/4] Updated docs for formatting --- docs/developers-setup.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/developers-setup.md b/docs/developers-setup.md index 448cdc9ee..6a539b727 100644 --- a/docs/developers-setup.md +++ b/docs/developers-setup.md @@ -52,6 +52,13 @@ listed as 'Device Simulator Express : ...' - If you change some files you'll need to run the 'npm run compile' command again and restart debugging +## Formatting + +- We use prettier to format the Typescript and CSS files, and we use black to format the Python files. + - You will need to install them, if they are not installed already. This can be done by running the command: `npm install prettier` and `pip install black` respectively. +- To check that your files are formatted correctly, run the command: `npm run check`. +- To format your files correctly, run the command: `npm run format`. + ## Repository Structure (important files) - src From d5ab0390beec30c5656b25b27345ebdf71cbc9a1 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 23 Jan 2020 15:46:03 -0800 Subject: [PATCH 3/4] updated PR template --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 46be84230..ef9bbe450 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,6 +25,7 @@ Please describe the tests that you ran to verify your changes. Provide instructi # Checklist: - [ ] My code follows the style guidelines of this project +- [ ] My code has been formatted with `npm run format` and passes the checks in `npm run check` - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation From beffe24954d8879484cad3921db3752058eb559c Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 23 Jan 2020 15:51:26 -0800 Subject: [PATCH 4/4] updated prettier check script command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e78adf6a..39d395983 100644 --- a/package.json +++ b/package.json @@ -286,7 +286,7 @@ "format:ts": "prettier --write src/**/*.{css,ts,tsx}", "format:python": "black src", "check": "npm-run-all check:*", - "check:ts": "tslint-config-prettier-check ./tslint.json", + "check:ts": "prettier --check src/**/*.{css,ts,tsx}", "check:python": "black src --check", "package": "vsce package" },