Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fecfdc4c11 |
No files matched your search
@@ -1,40 +0,0 @@
|
|||||||
name: pxt-buildmain
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'master'
|
|
||||||
- 'main'
|
|
||||||
create:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: npm install
|
|
||||||
run: |
|
|
||||||
sudo apt-get install xvfb
|
|
||||||
sudo npm install -g pxt
|
|
||||||
npm install
|
|
||||||
- name: pxt ci
|
|
||||||
run: |
|
|
||||||
pxt ci
|
|
||||||
env:
|
|
||||||
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
|
|
||||||
PXT_ACCESS_TOKEN: ${{ secrets.PXT_ACCESS_TOKEN }}
|
|
||||||
PXT_RELEASE_REPO: ${{ secrets.PXT_RELEASE_REPO }}
|
|
||||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
|
||||||
CHROME_BIN: chromium-browser
|
|
||||||
DISPLAY: :99.0
|
|
||||||
CI: true
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
name: pxt-buildpr
|
|
||||||
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: npm install
|
|
||||||
run: |
|
|
||||||
sudo apt-get install xvfb
|
|
||||||
sudo npm install -g pxt
|
|
||||||
npm install
|
|
||||||
- name: pxt ci
|
|
||||||
run: |
|
|
||||||
pxt ci
|
|
||||||
env:
|
|
||||||
CHROME_BIN: chromium-browser
|
|
||||||
DISPLAY: :99.0
|
|
||||||
CI: true
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
name: pxt-buildpush
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# main/master has its own build that includes the crowdin key
|
|
||||||
branches-ignore:
|
|
||||||
- 'main'
|
|
||||||
- 'master'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: npm install
|
|
||||||
run: |
|
|
||||||
sudo apt-get install xvfb
|
|
||||||
sudo npm install -g pxt
|
|
||||||
npm install
|
|
||||||
- name: pxt ci
|
|
||||||
run: |
|
|
||||||
pxt ci
|
|
||||||
env:
|
|
||||||
PXT_ACCESS_TOKEN: ${{ secrets.PXT_ACCESS_TOKEN }}
|
|
||||||
PXT_RELEASE_REPO: ${{ secrets.PXT_RELEASE_REPO }}
|
|
||||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
|
||||||
CHROME_BIN: chromium-browser
|
|
||||||
DISPLAY: :99.0
|
|
||||||
CI: true
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "8.9.0"
|
||||||
|
script:
|
||||||
|
- "node node_modules/pxt-core/built/pxt.js travis"
|
||||||
|
sudo: false
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
|
||||||
|
|
||||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
|
||||||
|
|
||||||
## Reporting Security Issues
|
|
||||||
|
|
||||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
||||||
|
|
||||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
|
||||||
|
|
||||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
|
||||||
|
|
||||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
|
||||||
|
|
||||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
|
||||||
|
|
||||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
|
||||||
* Full paths of source file(s) related to the manifestation of the issue
|
|
||||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
|
||||||
* Any special configuration required to reproduce the issue
|
|
||||||
* Step-by-step instructions to reproduce the issue
|
|
||||||
* Proof-of-concept or exploit code (if possible)
|
|
||||||
* Impact of the issue, including how an attacker might exploit the issue
|
|
||||||
|
|
||||||
This information will help us triage your report more quickly.
|
|
||||||
|
|
||||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
|
||||||
|
|
||||||
## Preferred Languages
|
|
||||||
|
|
||||||
We prefer all communications to be in English.
|
|
||||||
|
|
||||||
## Policy
|
|
||||||
|
|
||||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
|
||||||
|
|
||||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"appref": "v1.4"
|
"appref": "v"
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ while (true) {
|
|||||||
music.playSoundEffectUntilDone(sounds.mechanicalMotorStart)
|
music.playSoundEffectUntilDone(sounds.mechanicalMotorStart)
|
||||||
music.playSoundEffectUntilDone(sounds.mechanicalMotorIdle);
|
music.playSoundEffectUntilDone(sounds.mechanicalMotorIdle);
|
||||||
}
|
}
|
||||||
|
pause(1);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,5 +43,6 @@ while (true) {
|
|||||||
music.playSoundEffectUntilDone(sounds.mechanicalMotorStart)
|
music.playSoundEffectUntilDone(sounds.mechanicalMotorStart)
|
||||||
music.playSoundEffectUntilDone(sounds.mechanicalMotorIdle);
|
music.playSoundEffectUntilDone(sounds.mechanicalMotorIdle);
|
||||||
}
|
}
|
||||||
|
pause(1);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"appref": "v1.2.31"
|
"appref": "v1.2.27"
|
||||||
}
|
}
|
||||||
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
## Example #example
|
## Example #example
|
||||||
|
|
||||||
Use a wait and the timer to generate a number.
|
Use the a wait and the timer to generate a crazy number.
|
||||||
|
|
||||||
```blocks
|
```blocks
|
||||||
let something = 0
|
let crazy = 0
|
||||||
for (let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
control.waitMicros(100)
|
control.waitMicros(100)
|
||||||
something = control.millis()
|
crazy = control.millis()
|
||||||
something += control.deviceSerialNumber()
|
crazy += control.deviceSerialNumber()
|
||||||
if (something != 0) {
|
if (crazy != 0) {
|
||||||
something = something / 1000000
|
crazy = crazy / 1000000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -49,7 +49,7 @@ declare interface Serial extends EventTarget {
|
|||||||
requestPort(options: SerialPortRequestOptions): Promise<SerialPort>;
|
requestPort(options: SerialPortRequestOptions): Promise<SerialPort>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebSerialPackageIO implements pxt.packetio.PacketIO {
|
class WebSerialPackageIO implements pxt.HF2.PacketIO {
|
||||||
onData: (v: Uint8Array) => void;
|
onData: (v: Uint8Array) => void;
|
||||||
onError: (e: Error) => void;
|
onError: (e: Error) => void;
|
||||||
onEvent: (v: Uint8Array) => void;
|
onEvent: (v: Uint8Array) => void;
|
||||||
@@ -87,7 +87,7 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static portIos: WebSerialPackageIO[] = [];
|
static portIos: WebSerialPackageIO[] = [];
|
||||||
static async mkPacketIOAsync(): Promise<pxt.packetio.PacketIO> {
|
static async mkPacketIOAsync(): Promise<pxt.HF2.PacketIO> {
|
||||||
const serial = (<any>navigator).serial;
|
const serial = (<any>navigator).serial;
|
||||||
if (serial) {
|
if (serial) {
|
||||||
try {
|
try {
|
||||||
@@ -130,7 +130,7 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
|
|||||||
|
|
||||||
private async closeAsync() {
|
private async closeAsync() {
|
||||||
// don't close port
|
// don't close port
|
||||||
return pxt.U.delay(500);
|
return Promise.delay(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnectAsync(): Promise<void> {
|
reconnectAsync(): Promise<void> {
|
||||||
@@ -146,33 +146,11 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
|
|||||||
this._writer = this.port.writable.getWriter();
|
this._writer = this.port.writable.getWriter();
|
||||||
return this._writer.write(pkt);
|
return this._writer.write(pkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeviceConnectionChanged(connect: boolean) {
|
|
||||||
throw new Error("onDeviceConnectionChanged not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnectionChanged() {
|
|
||||||
throw new Error("onConnectionChanged not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnecting() {
|
|
||||||
throw new Error("isConnecting not implemented");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected() {
|
|
||||||
throw new Error("isConnected not implemented");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
disposeAsync() {
|
|
||||||
return Promise.reject("disposeAsync not implemented")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hf2Async() {
|
function hf2Async() {
|
||||||
const pktIOAsync: Promise<pxt.packetio.PacketIO> = useWebSerial
|
const pktIOAsync: Promise<pxt.HF2.PacketIO> = useWebSerial
|
||||||
? WebSerialPackageIO.mkPacketIOAsync() : pxt.packetio.mkPacketIOAsync()
|
? WebSerialPackageIO.mkPacketIOAsync() : pxt.HF2.mkPacketIOAsync()
|
||||||
return pktIOAsync.then(h => {
|
return pktIOAsync.then(h => {
|
||||||
let w = new Ev3Wrapper(h)
|
let w = new Ev3Wrapper(h)
|
||||||
ev3 = w
|
ev3 = w
|
||||||
@@ -212,19 +190,14 @@ export function enableWebSerialAsync() {
|
|||||||
else return Promise.resolve();
|
else return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupAsync() {
|
function cleanupAsync() {
|
||||||
if (ev3) {
|
if (ev3) {
|
||||||
console.log('cleanup previous port')
|
console.log('cleanup previous port')
|
||||||
try {
|
return ev3.disconnectAsync()
|
||||||
await ev3.disconnectAsync()
|
.catch(e => { })
|
||||||
}
|
.finally(() => { ev3 = undefined; });
|
||||||
catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
ev3 = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
let initPromise: Promise<Ev3Wrapper>
|
let initPromise: Promise<Ev3Wrapper>
|
||||||
@@ -234,7 +207,7 @@ function initHidAsync() { // needs to run within a click handler
|
|||||||
if (useHID) {
|
if (useHID) {
|
||||||
initPromise = cleanupAsync()
|
initPromise = cleanupAsync()
|
||||||
.then(() => hf2Async())
|
.then(() => hf2Async())
|
||||||
.catch((err: any) => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
initPromise = null
|
initPromise = null
|
||||||
useHID = false;
|
useHID = false;
|
||||||
@@ -311,7 +284,7 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
|
|||||||
.catch(e => {
|
.catch(e => {
|
||||||
// user easily forgets to stop robot
|
// user easily forgets to stop robot
|
||||||
bluetoothTryAgainAsync().then(() => w.disconnectAsync())
|
bluetoothTryAgainAsync().then(() => w.disconnectAsync())
|
||||||
.then(() => pxt.U.delay(1000))
|
.then(() => Promise.delay(1000))
|
||||||
.then(() => w.reconnectAsync());
|
.then(() => w.reconnectAsync());
|
||||||
|
|
||||||
// nothing we can do
|
// nothing we can do
|
||||||
@@ -323,7 +296,7 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
|
|||||||
.then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
|
.then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
|
||||||
.then(() => w.flashAsync(rbfPath, rbfBIN))
|
.then(() => w.flashAsync(rbfPath, rbfBIN))
|
||||||
.then(() => w.runAsync(rbfPath))
|
.then(() => w.runAsync(rbfPath))
|
||||||
.then(() => pxt.U.delay(500))
|
.then(() => Promise.delay(500))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
pxt.tickEvent("webserial.success");
|
pxt.tickEvent("webserial.success");
|
||||||
return w.disconnectAsync()
|
return w.disconnectAsync()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { canUseWebSerial, enableWebSerialAsync } from "./deploy";
|
import { canUseWebSerial, enableWebSerialAsync } from "./deploy";
|
||||||
import { projectView } from "./extension";
|
import { projectView } from "./extension";
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ export function bluetoothTryAgainAsync(): Promise<void> {
|
|||||||
|
|
||||||
function enableWebSerialAndCompileAsync() {
|
function enableWebSerialAndCompileAsync() {
|
||||||
return enableWebSerialAsync()
|
return enableWebSerialAsync()
|
||||||
.then(() => pxt.U.delay(500))
|
.then(() => Promise.delay(500))
|
||||||
.then(() => projectView.compile());
|
.then(() => projectView.compile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,12 +52,11 @@ function explainWebSerialPairingAsync(): Promise<void> {
|
|||||||
|
|
||||||
export function showUploadDialogAsync(fn: string, url: string, _confirmAsync: (options: any) => Promise<number>): Promise<void> {
|
export function showUploadDialogAsync(fn: string, url: string, _confirmAsync: (options: any) => Promise<number>): Promise<void> {
|
||||||
confirmAsync = _confirmAsync;
|
confirmAsync = _confirmAsync;
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/cc848897.aspx
|
// https://msdn.microsoft.com/en-us/library/cc848897.aspx
|
||||||
// "For security reasons, data URIs are restricted to downloaded resources.
|
// "For security reasons, data URIs are restricted to downloaded resources.
|
||||||
// Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
|
// Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
|
||||||
const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
|
const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
|
||||||
const docUrl = (pxt.appTarget.appTheme.usbDocs ? pxt.appTarget.appTheme.usbDocs : false);
|
const docUrl = pxt.appTarget.appTheme.usbDocs;
|
||||||
|
|
||||||
const jsx =
|
const jsx =
|
||||||
<div className="ui grid stackable">
|
<div className="ui grid stackable">
|
||||||
@@ -120,7 +118,7 @@ export function showUploadDialogAsync(fn: string, url: string, _confirmAsync: (o
|
|||||||
hideAgree: false,
|
hideAgree: false,
|
||||||
agreeLbl: lf("I got it"),
|
agreeLbl: lf("I got it"),
|
||||||
className: 'downloaddialog',
|
className: 'downloaddialog',
|
||||||
buttons: [canUseWebSerial() && {
|
buttons: [canUseWebSerial() ? {
|
||||||
label: lf("Bluetooth"),
|
label: lf("Bluetooth"),
|
||||||
icon: "bluetooth",
|
icon: "bluetooth",
|
||||||
className: "bluetooth focused",
|
className: "bluetooth focused",
|
||||||
@@ -128,19 +126,20 @@ export function showUploadDialogAsync(fn: string, url: string, _confirmAsync: (o
|
|||||||
pxt.tickEvent("bluetooth.enable");
|
pxt.tickEvent("bluetooth.enable");
|
||||||
explainWebSerialPairingAsync()
|
explainWebSerialPairingAsync()
|
||||||
.then(() => enableWebSerialAndCompileAsync())
|
.then(() => enableWebSerialAndCompileAsync())
|
||||||
|
.done();
|
||||||
}
|
}
|
||||||
}, downloadAgain && {
|
} : undefined, downloadAgain ? {
|
||||||
label: fn,
|
label: fn,
|
||||||
icon: "download",
|
icon: "download",
|
||||||
className: "lightgrey focused",
|
className: "lightgrey focused",
|
||||||
url,
|
url,
|
||||||
fileName: fn
|
fileName: fn
|
||||||
}, docUrl && {
|
} : undefined, docUrl ? {
|
||||||
label: lf("Help"),
|
label: lf("Help"),
|
||||||
icon: "help",
|
icon: "help",
|
||||||
className: "lightgrey",
|
className: "lightgrey",
|
||||||
url: docUrl
|
url: docUrl
|
||||||
}]
|
} : undefined]
|
||||||
//timeout: 20000
|
//timeout: 20000
|
||||||
}).then(() => { });
|
}).then(() => { });
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es5",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class Ev3Wrapper {
|
|||||||
isStreaming = false;
|
isStreaming = false;
|
||||||
dataDump = /talkdbg=1/.test(window.location.href);
|
dataDump = /talkdbg=1/.test(window.location.href);
|
||||||
|
|
||||||
constructor(public io: pxt.packetio.PacketIO) {
|
constructor(public io: pxt.HF2.PacketIO) {
|
||||||
io.onData = buf => {
|
io.onData = buf => {
|
||||||
buf = buf.slice(0, HF2.read16(buf, 0) + 2)
|
buf = buf.slice(0, HF2.read16(buf, 0) + 2)
|
||||||
if (HF2.read16(buf, 4) == usbMagic) {
|
if (HF2.read16(buf, 4) == usbMagic) {
|
||||||
@@ -81,7 +81,7 @@ export class Ev3Wrapper {
|
|||||||
log(`stopping PXT app`)
|
log(`stopping PXT app`)
|
||||||
let buf = this.allocCustom(2)
|
let buf = this.allocCustom(2)
|
||||||
return this.justSendAsync(buf)
|
return this.justSendAsync(buf)
|
||||||
.then(() => pxt.U.delay(500))
|
.then(() => Promise.delay(500))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export class Ev3Wrapper {
|
|||||||
if (this.dataDump)
|
if (this.dataDump)
|
||||||
log("TALK: " + U.toHex(buf))
|
log("TALK: " + U.toHex(buf))
|
||||||
return this.io.sendPacketAsync(buf)
|
return this.io.sendPacketAsync(buf)
|
||||||
.then(() => this.msgs.shiftAsync(5000))
|
.then(() => this.msgs.shiftAsync(1000))
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp[2] != buf[2] || resp[3] != buf[3])
|
if (resp[2] != buf[2] || resp[3] != buf[3])
|
||||||
U.userError("msg count de-sync")
|
U.userError("msg count de-sync")
|
||||||
@@ -236,7 +236,7 @@ export class Ev3Wrapper {
|
|||||||
let contFileReq = this.allocSystem(1 + 2, 0x97)
|
let contFileReq = this.allocSystem(1 + 2, 0x97)
|
||||||
HF2.write16(contFileReq, 7, 1000) // maxRead
|
HF2.write16(contFileReq, 7, 1000) // maxRead
|
||||||
contFileReq[6] = handle
|
contFileReq[6] = handle
|
||||||
return pxt.U.delay(data.length > 0 ? 0 : 500)
|
return Promise.delay(data.length > 0 ? 0 : 500)
|
||||||
.then(() => this.talkAsync(contFileReq, -1))
|
.then(() => this.talkAsync(contFileReq, -1))
|
||||||
.then(resp)
|
.then(resp)
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ export class Ev3Wrapper {
|
|||||||
let loop = (): Promise<void> =>
|
let loop = (): Promise<void> =>
|
||||||
this.lock.enqueue("file", () =>
|
this.lock.enqueue("file", () =>
|
||||||
this.streamFileOnceAsync(path, cb))
|
this.streamFileOnceAsync(path, cb))
|
||||||
.then(() => pxt.U.delay(500))
|
.then(() => Promise.delay(500))
|
||||||
.then(loop)
|
.then(loop)
|
||||||
return loop()
|
return loop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,17 +120,20 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
|
|||||||
|
|
||||||
Blockly.DropDownDiv.setColour('#ffffff', '#dddddd');
|
Blockly.DropDownDiv.setColour('#ffffff', '#dddddd');
|
||||||
|
|
||||||
// Position based on the field position.
|
// Calculate positioning based on the field position.
|
||||||
Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this));
|
let scale = (<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).scale;
|
||||||
|
let bBox = { width: this.size_.width, height: this.size_.height };
|
||||||
// Update colour to look selected.
|
bBox.width *= scale;
|
||||||
let source = this.sourceBlock_ as Blockly.BlockSvg;
|
bBox.height *= scale;
|
||||||
this.savedPrimary_ = source?.getColour();
|
let position = this.fieldGroup_.getBoundingClientRect();
|
||||||
if (source?.isShadow()) {
|
let primaryX = position.left + bBox.width / 2;
|
||||||
source.setColour(source.getColourTertiary());
|
let primaryY = position.top + bBox.height;
|
||||||
} else if (this.borderRect_) {
|
let secondaryX = primaryX;
|
||||||
this.borderRect_.setAttribute('fill', (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary());
|
let secondaryY = position.top;
|
||||||
}
|
// Set bounds to workspace; show the drop-down.
|
||||||
|
(Blockly.DropDownDiv as any).setBoundsElement((<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).getParentSvg().parentNode);
|
||||||
|
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
|
||||||
|
this.onHide_.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,12 +157,5 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
|
|||||||
content.removeAttribute('aria-haspopup');
|
content.removeAttribute('aria-haspopup');
|
||||||
content.removeAttribute('aria-activedescendant');
|
content.removeAttribute('aria-activedescendant');
|
||||||
(content as HTMLElement).style.width = '';
|
(content as HTMLElement).style.width = '';
|
||||||
// Update color (deselect) on dropdown hide
|
|
||||||
let source = this.sourceBlock_ as Blockly.BlockSvg;
|
|
||||||
if (source?.isShadow()) {
|
|
||||||
source.setColour(this.savedPrimary_);
|
|
||||||
} else if (this.borderRect_) {
|
|
||||||
this.borderRect_.setAttribute('fill', this.savedPrimary_);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,10 @@ export interface FieldColorEnumOptions extends pxtblockly.FieldColourNumberOptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Blockly.FieldCustom {
|
export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Blockly.FieldCustom {
|
||||||
|
|
||||||
public isFieldCustom_ = true;
|
public isFieldCustom_ = true;
|
||||||
private paramsData: any[];
|
|
||||||
|
|
||||||
constructor(text: string, params: FieldColorEnumOptions, opt_validator?: Function) {
|
constructor(text: string, params: FieldColorEnumOptions, opt_validator?: Function) {
|
||||||
super(text, params, opt_validator);
|
super(text, params, opt_validator);
|
||||||
|
|
||||||
this.paramsData = params["data"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mapColour(enumString: string) {
|
mapColour(enumString: string) {
|
||||||
@@ -37,21 +33,11 @@ export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Block
|
|||||||
case 'ColorSensorColor.Red': return '#f12a21';
|
case 'ColorSensorColor.Red': return '#f12a21';
|
||||||
case 'ColorSensorColor.White': return '#ffffff';
|
case 'ColorSensorColor.White': return '#ffffff';
|
||||||
case 'ColorSensorColor.Brown': return '#6c2d00';
|
case 'ColorSensorColor.Brown': return '#6c2d00';
|
||||||
case 'ColorSensorColor.None': return '#dfe6e9';
|
case 'ColorSensorColor.None': return '#dfe6e9'; // Grey
|
||||||
default: return colorString;
|
default: return colorString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showEditor_() {
|
|
||||||
super.showEditor_();
|
|
||||||
const colorCells = document.querySelectorAll('.legoColorPicker td');
|
|
||||||
colorCells.forEach((cell) => {
|
|
||||||
const titleName = this.mapColour(cell.getAttribute("title"));
|
|
||||||
const index = this.paramsData.findIndex(item => item[1] === titleName);
|
|
||||||
cell.setAttribute("title", this.paramsData[index][0]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current colour.
|
* Return the current colour.
|
||||||
* @param {boolean} opt_asHex optional field if the returned value should be a hex
|
* @param {boolean} opt_asHex optional field if the returned value should be a hex
|
||||||
@@ -78,7 +64,7 @@ export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Block
|
|||||||
}
|
}
|
||||||
this.value_ = colour;
|
this.value_ = colour;
|
||||||
if (this.sourceBlock_) {
|
if (this.sourceBlock_) {
|
||||||
this.sourceBlock_.setColour(colour);
|
this.sourceBlock_.setColour(colour, colour, colour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,407 @@
|
|||||||
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
|
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
|
||||||
/// <reference path="../node_modules/pxt-core/built/pxtblocks.d.ts"/>
|
|
||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||||
|
|
||||||
export interface FieldMotorsOptions extends pxtblockly.FieldImagesOptions {
|
export interface FieldMotorsOptions extends Blockly.FieldCustomDropdownOptions {
|
||||||
columns?: string;
|
|
||||||
width?: string;
|
|
||||||
//sort?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldMotors extends pxtblockly.FieldImages implements Blockly.FieldCustom {
|
export class FieldMotors extends Blockly.FieldDropdown implements Blockly.FieldCustom {
|
||||||
public isFieldCustom_ = true;
|
public isFieldCustom_ = true;
|
||||||
//public shouldSort_: boolean;
|
|
||||||
|
private box2_: SVGRectElement;
|
||||||
|
private textElement2_: SVGTextElement;
|
||||||
|
private arrow2_: SVGImageElement;
|
||||||
|
|
||||||
|
// Width in pixels
|
||||||
|
protected itemWidth_: number;
|
||||||
|
|
||||||
|
// Number of rows to display (if there are extra rows, the picker will be scrollable)
|
||||||
|
protected maxRows_: number;
|
||||||
|
|
||||||
|
protected backgroundColour_: string;
|
||||||
|
protected itemColour_: string;
|
||||||
|
protected borderColour_: string;
|
||||||
|
|
||||||
|
private isFirst_: boolean; // which of the two dropdowns is selected
|
||||||
|
|
||||||
constructor(text: string, options: FieldMotorsOptions, validator?: Function) {
|
constructor(text: string, options: FieldMotorsOptions, validator?: Function) {
|
||||||
super(text, options, validator);
|
super(options.data, validator);
|
||||||
|
|
||||||
this.columns_ = parseInt(options.columns) || 4;
|
this.itemWidth_ = 75;
|
||||||
this.width_ = parseInt(options.width) || 400;
|
this.backgroundColour_ = pxtblockly.parseColour(options.colour);
|
||||||
//this.shouldSort_ = options.sort;
|
this.itemColour_ = "rgba(255, 255, 255, 0.6)";
|
||||||
this.addLabel_ = true;
|
this.borderColour_ = pxt.toolbox.fadeColor(this.backgroundColour_, 0.4, false);
|
||||||
|
|
||||||
this.renderSelectedImage_ = Blockly.FieldDropdown.prototype.renderSelectedText_;
|
|
||||||
this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
init() {
|
||||||
* Create a dropdown menu under the text.
|
if (this.fieldGroup_) {
|
||||||
* @private
|
// Field has already been initialized once.
|
||||||
*/
|
return;
|
||||||
public showEditor_() {
|
}
|
||||||
|
// Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL)
|
||||||
|
// Positioned on render, after text size is calculated.
|
||||||
|
/** @type {Number} */
|
||||||
|
(this as any).arrowSize_ = 12;
|
||||||
|
/** @type {Number} */
|
||||||
|
(this as any).arrowX_ = 0;
|
||||||
|
/** @type {Number} */
|
||||||
|
this.arrowY_ = 11;
|
||||||
|
this.arrow_ = Blockly.utils.dom.createSvgElement('image', {
|
||||||
|
'height': (this as any).arrowSize_ + 'px',
|
||||||
|
'width': (this as any).arrowSize_ + 'px'
|
||||||
|
}, null);
|
||||||
|
this.arrow_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||||
|
'xlink:href', (Blockly.FieldDropdown as any).DROPDOWN_SVG_DATAURI);
|
||||||
|
|
||||||
|
this.arrow2_ = <SVGImageElement>Blockly.utils.dom.createSvgElement('image', {
|
||||||
|
'height': (this as any).arrowSize_ + 'px',
|
||||||
|
'width': (this as any).arrowSize_ + 'px'
|
||||||
|
}, null);
|
||||||
|
this.arrow2_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||||
|
'xlink:href', (Blockly.FieldDropdown as any).DROPDOWN_SVG_DATAURI);
|
||||||
|
(this as any).className_ += ' blocklyDropdownText';
|
||||||
|
|
||||||
|
// Build the DOM.
|
||||||
|
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
|
||||||
|
if (!this.visible_) {
|
||||||
|
(this.fieldGroup_ as any).style.display = 'none';
|
||||||
|
}
|
||||||
|
// Adjust X to be flipped for RTL. Position is relative to horizontal start of source block.
|
||||||
|
let size = this.getSize();
|
||||||
|
let fieldX = (this.sourceBlock_.RTL) ? -size.width / 2 : size.width / 2;
|
||||||
|
/** @type {!Element} */
|
||||||
|
this.textElement_ = Blockly.utils.dom.createSvgElement('text',
|
||||||
|
{
|
||||||
|
'class': (this as any).className_,
|
||||||
|
'x': fieldX,
|
||||||
|
'dy': '0.7ex',
|
||||||
|
'y': size.height / 2
|
||||||
|
},
|
||||||
|
this.fieldGroup_);
|
||||||
|
fieldX += 10; // size of first group.
|
||||||
|
this.textElement2_ = <SVGTextElement>Blockly.utils.dom.createSvgElement('text',
|
||||||
|
{
|
||||||
|
'class': (this as any).className_,
|
||||||
|
'x': fieldX,
|
||||||
|
'dy': '0.7ex',
|
||||||
|
'y': this.size_.height / 2
|
||||||
|
},
|
||||||
|
this.fieldGroup_);
|
||||||
|
|
||||||
|
this.updateEditable();
|
||||||
|
(this.sourceBlock_ as Blockly.BlockSvg).getSvgRoot().appendChild(this.fieldGroup_);
|
||||||
|
// Force a render.
|
||||||
|
this.render_();
|
||||||
|
this.isDirty_ = true;
|
||||||
|
(this as any).mouseDownWrapper_ =
|
||||||
|
Blockly.bindEventWithChecks_((this as any).getClickTarget_(), 'mousedown', this,
|
||||||
|
(this as any).onMouseDown_);
|
||||||
|
|
||||||
|
// Add second dropdown
|
||||||
|
if (this.shouldShowRect_()) {
|
||||||
|
this.box_ = <SVGRectElement>Blockly.utils.dom.createSvgElement('rect', {
|
||||||
|
'rx': (Blockly.BlockSvg as any).CORNER_RADIUS,
|
||||||
|
'ry': (Blockly.BlockSvg as any).CORNER_RADIUS,
|
||||||
|
'x': 0,
|
||||||
|
'y': 0,
|
||||||
|
'width': this.size_.width,
|
||||||
|
'height': this.size_.height,
|
||||||
|
'stroke': this.sourceBlock_.getColourTertiary(),
|
||||||
|
'fill': this.sourceBlock_.getColour(),
|
||||||
|
'class': 'blocklyBlockBackground',
|
||||||
|
'fill-opacity': 1
|
||||||
|
}, null);
|
||||||
|
this.fieldGroup_.insertBefore(this.box_, this.textElement_);
|
||||||
|
this.box2_ = <SVGRectElement>Blockly.utils.dom.createSvgElement('rect', {
|
||||||
|
'rx': (Blockly.BlockSvg as any).CORNER_RADIUS,
|
||||||
|
'ry': (Blockly.BlockSvg as any).CORNER_RADIUS,
|
||||||
|
'x': 0,
|
||||||
|
'y': 0,
|
||||||
|
'width': this.size_.width,
|
||||||
|
'height': this.size_.height,
|
||||||
|
'stroke': this.sourceBlock_.getColourTertiary(),
|
||||||
|
'fill': this.sourceBlock_.getColour(),
|
||||||
|
'class': 'blocklyBlockBackground',
|
||||||
|
'fill-opacity': 1
|
||||||
|
}, null);
|
||||||
|
this.fieldGroup_.insertBefore(this.box2_, this.textElement2_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force a reset of the text to add the arrow.
|
||||||
|
let text = this.text_;
|
||||||
|
this.text_ = null;
|
||||||
|
this.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstValue(value: string) {
|
||||||
|
const typeValue = value.indexOf('large') != -1 ? 'large' : 'medium';
|
||||||
|
const portValue = this.getSecondValue(value);
|
||||||
|
const isDual = portValue.length > 1;
|
||||||
|
return `${typeValue} motor${isDual ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecondValue(value: string) {
|
||||||
|
return (value.indexOf('large') != -1) ?
|
||||||
|
value.substring(value.indexOf('large') + 5) :
|
||||||
|
value.substring(value.indexOf('medium') + 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstValueI11n(value: string) {
|
||||||
|
const firstValue = this.getFirstValue(value);
|
||||||
|
const motorOptions = {
|
||||||
|
'medium motor': lf("medium motor"),
|
||||||
|
'large motor': lf("large motor"),
|
||||||
|
'large motors': lf("large motors")
|
||||||
|
}
|
||||||
|
return motorOptions[firstValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeText_(text: string) {
|
||||||
|
if (!text) {
|
||||||
|
// Prevent the field from disappearing if empty.
|
||||||
|
return Blockly.Field.NBSP;
|
||||||
|
}
|
||||||
|
if (text.length > this.maxDisplayLength) {
|
||||||
|
// Truncate displayed string and add an ellipsis ('...').
|
||||||
|
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
|
||||||
|
}
|
||||||
|
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
|
||||||
|
text = text.replace(/\s/g, Blockly.Field.NBSP);
|
||||||
|
if (this.sourceBlock_.RTL) {
|
||||||
|
// The SVG is LTR, force text to be RTL.
|
||||||
|
text += '\u200F';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTextNode2_() {
|
||||||
|
if (!this.textElement2_) {
|
||||||
|
// Not rendered yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let text = this.text_;
|
||||||
|
if (text.length > this.maxDisplayLength) {
|
||||||
|
// Truncate displayed string and add an ellipsis ('...').
|
||||||
|
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
|
||||||
|
// Add special class for sizing font when truncated
|
||||||
|
this.textElement2_.setAttribute('class', (this as any).className_ + ' blocklyTextTruncated');
|
||||||
|
} else {
|
||||||
|
this.textElement2_.setAttribute('class', (this as any).className_);
|
||||||
|
}
|
||||||
|
// Empty the text element.
|
||||||
|
goog.dom.removeChildren(/** @type {!Element} */(this.textElement2_));
|
||||||
|
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
|
||||||
|
text = text.replace(/\s/g, Blockly.Field.NBSP);
|
||||||
|
if (this.sourceBlock_.RTL && text) {
|
||||||
|
// The SVG is LTR, force text to be RTL.
|
||||||
|
text += '\u200F';
|
||||||
|
}
|
||||||
|
if (!text) {
|
||||||
|
// Prevent the field from disappearing if empty.
|
||||||
|
text = Blockly.Field.NBSP;
|
||||||
|
}
|
||||||
|
let textNode = document.createTextNode(text);
|
||||||
|
this.textElement2_.appendChild(textNode);
|
||||||
|
|
||||||
|
// Cached width is obsolete. Clear it.
|
||||||
|
this.isDirty_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
patchDualMotorText(text: string) {
|
||||||
|
if (text === null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
if (text.indexOf('|') == -1) {
|
||||||
|
text = this.sourceBlock_.RTL ? `${text}|${lf("large motors")}` : `${lf("large motors")}|${text}`;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(text: string) {
|
||||||
|
if (text === null || text === this.text_) {
|
||||||
|
// No change if null.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text = this.patchDualMotorText(text);
|
||||||
|
this.text_ = text;
|
||||||
|
this.updateTextNode_();
|
||||||
|
this.updateTextNode2_();
|
||||||
|
|
||||||
|
if (this.textElement_) {
|
||||||
|
this.textElement_.parentNode.appendChild(this.arrow_);
|
||||||
|
}
|
||||||
|
if (this.textElement2_) {
|
||||||
|
this.textElement2_.parentNode.appendChild(this.arrow2_);
|
||||||
|
}
|
||||||
|
if (this.sourceBlock_ && (<Blockly.BlockSvg>this.sourceBlock_).rendered) {
|
||||||
|
(<Blockly.BlockSvg>this.sourceBlock_).render();
|
||||||
|
this.sourceBlock_.bumpNeighbours_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
positionArrow2(start: number, x: number) {
|
||||||
|
if (!this.arrow2_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let addedWidth = 0;
|
||||||
|
if (this.sourceBlock_.RTL) {
|
||||||
|
(this as any).arrow2X_ = (this as any).arrowSize_ - (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING;
|
||||||
|
addedWidth = (this as any).arrowSize_ + (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING;
|
||||||
|
} else {
|
||||||
|
(this as any).arrow2X_ = x + (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING / 2;
|
||||||
|
addedWidth = (this as any).arrowSize_ + (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING;
|
||||||
|
}
|
||||||
|
if (this.box_) {
|
||||||
|
// Bump positioning to the right for a box-type drop-down.
|
||||||
|
(this as any).arrow2X_ += Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||||
|
}
|
||||||
|
(this as any).arrow2X_ += start;
|
||||||
|
this.arrow2_.setAttribute('transform',
|
||||||
|
'translate(' + (this as any).arrow2X_ + ',' + this.arrowY_ + ')'
|
||||||
|
);
|
||||||
|
return addedWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSize_() {
|
||||||
|
// Calculate width of field
|
||||||
|
let width = Blockly.Field.getCachedWidth(this.textElement_);
|
||||||
|
let width2 = Blockly.Field.getCachedWidth(this.textElement2_);
|
||||||
|
|
||||||
|
// Add padding to left and right of text.
|
||||||
|
if (this.EDITABLE) {
|
||||||
|
width += Blockly.BlockSvg.EDITABLE_FIELD_PADDING;
|
||||||
|
width2 += Blockly.BlockSvg.EDITABLE_FIELD_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust width for drop-down arrow.
|
||||||
|
this.arrowWidth_ = 0;
|
||||||
|
if (this.positionArrow) {
|
||||||
|
this.arrowWidth_ = this.positionArrow(width);
|
||||||
|
width += this.arrowWidth_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add padding to any drawn box.
|
||||||
|
if (this.box_) {
|
||||||
|
width += 2 * Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust width for second drop-down arrow.
|
||||||
|
(this as any).arrowWidth2_ = 0;
|
||||||
|
if (this.positionArrow2) {
|
||||||
|
(this as any).arrowWidth2_ = this.positionArrow2(width + Blockly.BlockSvg.BOX_FIELD_PADDING, width2);
|
||||||
|
width2 += (this as any).arrowWidth2_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add padding to any drawn box.
|
||||||
|
if (this.box2_) {
|
||||||
|
width2 += 2 * Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set width of the field.
|
||||||
|
this.size_.width = width + Blockly.BlockSvg.BOX_FIELD_PADDING + width2;
|
||||||
|
(this as any).width1 = width;
|
||||||
|
(this as any).width2 = width2;
|
||||||
|
};
|
||||||
|
|
||||||
|
render_() {
|
||||||
|
if (this.visible_ && this.textElement_) {
|
||||||
|
goog.dom.removeChildren(/** @type {!Element} */(this.textElement_));
|
||||||
|
goog.dom.removeChildren(/** @type {!Element} */(this.textElement2_));
|
||||||
|
|
||||||
|
// First dropdown
|
||||||
|
// Use one of the following options, medium motor, large motor or large motors (translated)
|
||||||
|
const textNode1 = document.createTextNode(this.getFirstValueI11n(<string>this.value_));
|
||||||
|
this.textElement_.appendChild(textNode1);
|
||||||
|
|
||||||
|
// Second dropdown, no need to translate. Only port numbers
|
||||||
|
if (this.textElement2_) {
|
||||||
|
const textNode2 = document.createTextNode(this.getSecondValue(<string>this.value_));
|
||||||
|
this.textElement2_.appendChild(textNode2);
|
||||||
|
}
|
||||||
|
this.updateSize_();
|
||||||
|
|
||||||
|
// Update text centering, based on newly calculated width.
|
||||||
|
let centerTextX = ((this as any).width1 - this.arrowWidth_) / 2;
|
||||||
|
if (this.sourceBlock_.RTL) {
|
||||||
|
centerTextX += this.arrowWidth_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a text-editing shadow block's field,
|
||||||
|
// if half the text length is not at least center of
|
||||||
|
// visible field (FIELD_WIDTH), center it there instead,
|
||||||
|
// unless there is a drop-down arrow.
|
||||||
|
if (this.sourceBlock_.isShadow() && !this.positionArrow) {
|
||||||
|
let minOffset = (Blockly.BlockSvg as any).FIELD_WIDTH / 2;
|
||||||
|
if (this.sourceBlock_.RTL) {
|
||||||
|
// X position starts at the left edge of the block, in both RTL and LTR.
|
||||||
|
// First offset by the width of the block to move to the right edge,
|
||||||
|
// and then subtract to move to the same position as LTR.
|
||||||
|
let minCenter = (this as any).width1 - minOffset;
|
||||||
|
centerTextX = Math.min(minCenter, centerTextX);
|
||||||
|
} else {
|
||||||
|
// (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2
|
||||||
|
// if the text is longer.
|
||||||
|
centerTextX = Math.max(minOffset, centerTextX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply new text element x position.
|
||||||
|
let width = Blockly.Field.getCachedWidth(this.textElement_);
|
||||||
|
let newX = centerTextX - width / 2;
|
||||||
|
this.textElement_.setAttribute('x', `${newX}`);
|
||||||
|
|
||||||
|
// Update text centering, based on newly calculated width.
|
||||||
|
let centerTextX2 = ((this as any).width2 - (this as any).arrowWidth2_) / 2;
|
||||||
|
if (this.sourceBlock_.RTL) {
|
||||||
|
centerTextX2 += (this as any).arrowWidth2_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a text-editing shadow block's field,
|
||||||
|
// if half the text length is not at least center of
|
||||||
|
// visible field (FIELD_WIDTH), center it there instead,
|
||||||
|
// unless there is a drop-down arrow.
|
||||||
|
if (this.sourceBlock_.isShadow() && !this.positionArrow2) {
|
||||||
|
let minOffset = (Blockly.BlockSvg as any).FIELD_WIDTH / 2;
|
||||||
|
if (this.sourceBlock_.RTL) {
|
||||||
|
// X position starts at the left edge of the block, in both RTL and LTR.
|
||||||
|
// First offset by the width of the block to move to the right edge,
|
||||||
|
// and then subtract to move to the same position as LTR.
|
||||||
|
let minCenter = (this as any).width2 - minOffset;
|
||||||
|
centerTextX2 = Math.min(minCenter, centerTextX2);
|
||||||
|
} else {
|
||||||
|
// (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2
|
||||||
|
// if the text is longer.
|
||||||
|
centerTextX2 = Math.max(minOffset, centerTextX2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply new text element x position.
|
||||||
|
let width2 = Blockly.Field.getCachedWidth(this.textElement2_);
|
||||||
|
let newX2 = centerTextX2 - width2 / 2;
|
||||||
|
this.textElement2_.setAttribute('x', `${newX2 + (this as any).width1 + Blockly.BlockSvg.BOX_FIELD_PADDING}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update any drawn box to the correct width and height.
|
||||||
|
if (this.box_) {
|
||||||
|
this.box_.setAttribute('width', `${(this as any).width1}`);
|
||||||
|
this.box_.setAttribute('height', `${this.size_.height}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update any drawn box to the correct width and height.
|
||||||
|
if (this.box2_) {
|
||||||
|
this.box2_.setAttribute('x', `${(this as any).width1 + Blockly.BlockSvg.BOX_FIELD_PADDING}`);
|
||||||
|
this.box2_.setAttribute('width', `${(this as any).width2}`);
|
||||||
|
this.box2_.setAttribute('height', `${this.size_.height}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
showEditor_(e?: MouseEvent) {
|
||||||
// If there is an existing drop-down we own, this is a request to hide the drop-down.
|
// If there is an existing drop-down we own, this is a request to hide the drop-down.
|
||||||
if (Blockly.DropDownDiv.hideIfOwner(this)) {
|
if (Blockly.DropDownDiv.hideIfOwner(this)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let sourceBlock = this.sourceBlock_ as Blockly.BlockSvg;
|
this.isFirst_ = e.clientX - this.getScaledBBox_().left < ((this as any).width1 * (<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).scale);
|
||||||
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||||
Blockly.DropDownDiv.clearContent();
|
Blockly.DropDownDiv.clearContent();
|
||||||
@@ -43,11 +411,56 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
|
|||||||
// Accessibility properties
|
// Accessibility properties
|
||||||
contentDiv.setAttribute('role', 'menu');
|
contentDiv.setAttribute('role', 'menu');
|
||||||
contentDiv.setAttribute('aria-haspopup', 'true');
|
contentDiv.setAttribute('aria-haspopup', 'true');
|
||||||
const options = this.getOptions();
|
const foptions = this.getOptions(); // [img info, text]
|
||||||
//if (this.shouldSort_) options.sort();
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
let opts = {};
|
||||||
const content = (options[i] as any)[0]; // Human-readable text or image.
|
let conts = {};
|
||||||
const value = (options[i] as any)[1]; // Language-neutral value.
|
let vals = {};
|
||||||
|
// Go through all option values and split them into groups
|
||||||
|
for (let opt = 0; opt < foptions.length; opt++) {
|
||||||
|
const value = foptions[opt][1];
|
||||||
|
const motorValue = value.substring(value.indexOf('.') + 1);
|
||||||
|
const typeValue = motorValue.indexOf('large') == 0 ? 'large' : 'medium';
|
||||||
|
const portValue = motorValue.indexOf('large') == 0 ? motorValue.substring(5) : motorValue.substring(6);
|
||||||
|
const isDual = portValue.length > 1;
|
||||||
|
|
||||||
|
const text = `${typeValue} motor${isDual ? 's' : ''}|${portValue}`;
|
||||||
|
const key = `${typeValue} motor${isDual ? 's' : ''}`;
|
||||||
|
if (!opts[key]) opts[key] = [];
|
||||||
|
opts[key].push(portValue);
|
||||||
|
|
||||||
|
conts[text] = foptions[opt][0];
|
||||||
|
vals[text] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentFirst = this.getFirstValue(<string>this.value_);
|
||||||
|
//const currentSecond = this.getSecondValue(<string>this.value_);
|
||||||
|
|
||||||
|
let options: string[];
|
||||||
|
if (!this.isFirst_) {
|
||||||
|
options = opts[currentFirst];
|
||||||
|
} else {
|
||||||
|
options = Object.keys(opts);
|
||||||
|
// Flip the first and second options to make it sorted the way we want it (medium, large, dual)
|
||||||
|
if (options.length == 3) {
|
||||||
|
options = [lf("medium motor"), lf("large motor"), lf("large motors")];
|
||||||
|
} else {
|
||||||
|
options.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirstUrl = {
|
||||||
|
'large motors': FieldMotors.DUAL_MOTORS_DATAURI,
|
||||||
|
'large motor': FieldMotors.MOTORS_LARGE_DATAURI,
|
||||||
|
'medium motor': FieldMotors.MOTORS_MEDIUM_DATAURI
|
||||||
|
}
|
||||||
|
const columns = options.length;
|
||||||
|
|
||||||
|
for (let i = 0, option: any; option = options[i]; i++) {
|
||||||
|
let text = this.isFirst_ ? option + '|' + (option.indexOf('motors') != -1 ? 'BC' : 'A') : currentFirst + '|' + option;
|
||||||
|
text = text.replace(/\xA0/g, ' ');
|
||||||
|
const content: any = conts[text];
|
||||||
|
const value = vals[text];
|
||||||
// Icons with the type property placeholder take up space but don't have any functionality
|
// Icons with the type property placeholder take up space but don't have any functionality
|
||||||
// Use for special-case layouts
|
// Use for special-case layouts
|
||||||
if (content.type == 'placeholder') {
|
if (content.type == 'placeholder') {
|
||||||
@@ -62,23 +475,26 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
|
|||||||
button.setAttribute('id', ':' + i); // For aria-activedescendant
|
button.setAttribute('id', ':' + i); // For aria-activedescendant
|
||||||
button.setAttribute('role', 'menuitem');
|
button.setAttribute('role', 'menuitem');
|
||||||
button.setAttribute('class', 'blocklyDropDownButton');
|
button.setAttribute('class', 'blocklyDropDownButton');
|
||||||
button.title = content.alt;
|
button.title = this.isFirst_ ? this.getFirstValueI11n(value) : this.getSecondValue(value);
|
||||||
if ((this as any).columns_) {
|
button.style.width = ((this.itemWidth_) - 8) + 'px';
|
||||||
button.style.width = (((this as any).width_ / (this as any).columns_) - 8) + 'px';
|
button.style.height = ((this.itemWidth_) - 8) + 'px';
|
||||||
//button.style.height = ((this.width_ / this.columns_) - 8) + 'px';
|
let backgroundColor = this.backgroundColour_;
|
||||||
} else {
|
|
||||||
button.style.width = content.width + 'px';
|
|
||||||
button.style.height = content.height + 'px';
|
|
||||||
}
|
|
||||||
let backgroundColor = sourceBlock.getColour();
|
|
||||||
if (value == this.getValue()) {
|
if (value == this.getValue()) {
|
||||||
// This icon is selected, show it in a different colour
|
// This icon is selected, show it in a different colour
|
||||||
backgroundColor = sourceBlock.getColourTertiary();
|
backgroundColor = this.sourceBlock_.getColourTertiary();
|
||||||
button.setAttribute('aria-selected', 'true');
|
button.setAttribute('aria-selected', 'true');
|
||||||
}
|
}
|
||||||
button.style.backgroundColor = backgroundColor;
|
button.style.backgroundColor = backgroundColor;
|
||||||
button.style.borderColor = sourceBlock.getColourTertiary();
|
button.style.borderColor = this.borderColour_;
|
||||||
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
||||||
|
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
|
||||||
|
// These are applied manually instead of using the :hover pseudoclass
|
||||||
|
// because Android has a bad long press "helper" menu and green highlight
|
||||||
|
// that we must prevent with ontouchstart preventDefault
|
||||||
|
Blockly.bindEvent_(button, 'mousedown', button, function (e) {
|
||||||
|
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
Blockly.bindEvent_(button, 'mouseover', button, function () {
|
Blockly.bindEvent_(button, 'mouseover', button, function () {
|
||||||
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||||
contentDiv.setAttribute('aria-activedescendant', this.id);
|
contentDiv.setAttribute('aria-activedescendant', this.id);
|
||||||
@@ -88,46 +504,80 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
|
|||||||
contentDiv.removeAttribute('aria-activedescendant');
|
contentDiv.removeAttribute('aria-activedescendant');
|
||||||
});
|
});
|
||||||
let buttonImg = document.createElement('img');
|
let buttonImg = document.createElement('img');
|
||||||
buttonImg.src = content.src;
|
let imgSrc = content.src;
|
||||||
|
if (this.isFirst_) {
|
||||||
|
const motorValue = value.substring(value.indexOf('.') + 1);
|
||||||
|
// Find out what kind of motor this is based on it's value, possible values: mediumX, largeX, and largeXY
|
||||||
|
if (motorValue.indexOf('medium') == 0) imgSrc = isFirstUrl['medium motor'];
|
||||||
|
else if (motorValue.length == 6) imgSrc = isFirstUrl['large motor'];
|
||||||
|
else imgSrc = isFirstUrl['large motors'];
|
||||||
|
}
|
||||||
|
buttonImg.src = imgSrc;
|
||||||
//buttonImg.alt = icon.alt;
|
//buttonImg.alt = icon.alt;
|
||||||
// Upon click/touch, we will be able to get the clicked element as e.target
|
// Upon click/touch, we will be able to get the clicked element as e.target
|
||||||
// Store a data attribute on all possible click targets so we can match it to the icon.
|
// Store a data attribute on all possible click targets so we can match it to the icon.
|
||||||
button.setAttribute('data-value', value);
|
button.setAttribute('data-value', value);
|
||||||
buttonImg.setAttribute('data-value', value);
|
buttonImg.setAttribute('data-value', value);
|
||||||
button.appendChild(buttonImg);
|
button.appendChild(buttonImg);
|
||||||
if (this.addLabel_) {
|
|
||||||
const buttonText = this.createTextNode_(content.alt);
|
|
||||||
buttonText.setAttribute('data-value', value);
|
|
||||||
buttonText.style.whiteSpace = 'inherit';
|
|
||||||
buttonText.style.width = 'auto';
|
|
||||||
buttonText.style.padding = '0 10px';
|
|
||||||
button.appendChild(buttonText);
|
|
||||||
}
|
|
||||||
contentDiv.appendChild(button);
|
contentDiv.appendChild(button);
|
||||||
}
|
}
|
||||||
contentDiv.style.width = (this as any).width_ + 'px';
|
contentDiv.style.width = (this.itemWidth_ * columns) + 'px';
|
||||||
dropdownDiv.appendChild(contentDiv);
|
dropdownDiv.appendChild(contentDiv);
|
||||||
|
|
||||||
Blockly.DropDownDiv.setColour(sourceBlock.getColour(), sourceBlock.getColourTertiary());
|
Blockly.DropDownDiv.setColour(this.backgroundColour_, this.borderColour_);
|
||||||
|
|
||||||
// Position based on the field position.
|
// Calculate positioning based on the field position.
|
||||||
Blockly.DropDownDiv.showPositionedByField(this, this.onHideCallback.bind(this));
|
let scale = (<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).scale;
|
||||||
|
let width = this.isFirst_ ? (this as any).width1 : (this as any).width2;
|
||||||
|
let bBox = { width: this.size_.width, height: this.size_.height };
|
||||||
|
width *= scale;
|
||||||
|
bBox.height *= scale;
|
||||||
|
let position = this.fieldGroup_.getBoundingClientRect();
|
||||||
|
let leftPosition = this.isFirst_ ? position.left : position.left + (scale * (this as any).width1) + Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||||
|
let primaryX = leftPosition + width / 2;
|
||||||
|
let primaryY = position.top + bBox.height;
|
||||||
|
let secondaryX = primaryX;
|
||||||
|
let secondaryY = position.top;
|
||||||
|
// Set bounds to workspace; show the drop-down.
|
||||||
|
(Blockly.DropDownDiv as any).setBoundsElement((<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).getParentSvg().parentNode);
|
||||||
|
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
|
||||||
|
this.onHide_.bind(this));
|
||||||
|
|
||||||
// Update colour to look selected.
|
// Update colour to look selected.
|
||||||
this.savedPrimary_ = sourceBlock?.getColour();
|
if (this.isFirst_ && this.box_) {
|
||||||
if (sourceBlock?.isShadow()) {
|
this.box_.setAttribute('fill', this.sourceBlock_.getColourTertiary());
|
||||||
sourceBlock.setColour(sourceBlock.style.colourTertiary);
|
} else if (!this.isFirst_ && this.box2_) {
|
||||||
} else if (this.borderRect_) {
|
this.box2_.setAttribute('fill', this.sourceBlock_.getColourTertiary());
|
||||||
this.borderRect_.setAttribute('fill', sourceBlock.style.colourTertiary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trimOptions_() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected buttonClick_ = function (e: any) {
|
protected buttonClick_ = function (e: any) {
|
||||||
let value = e.target.getAttribute('data-value');
|
let value = e.target.getAttribute('data-value');
|
||||||
this.setValue(value);
|
this.setValue(value);
|
||||||
Blockly.DropDownDiv.hide();
|
Blockly.DropDownDiv.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the drop-down is hidden.
|
||||||
|
*/
|
||||||
|
protected onHide_() {
|
||||||
|
const content = Blockly.DropDownDiv.getContentDiv();
|
||||||
|
content.removeAttribute('role');
|
||||||
|
content.removeAttribute('aria-haspopup');
|
||||||
|
content.removeAttribute('aria-activedescendant');
|
||||||
|
(content as HTMLElement).style.width = '';
|
||||||
|
if (this.isFirst_ && this.box_) {
|
||||||
|
this.box_.setAttribute('fill', this.sourceBlock_.getColour());
|
||||||
|
} else if (!this.isFirst_ && this.box2_) {
|
||||||
|
this.box2_.setAttribute('fill', this.sourceBlock_.getColour());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MOTORS_MEDIUM_DATAURI = 'data:image/png;base64,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
Line truncated
|
||||||
|
|
||||||
|
|
||||||
|
static MOTORS_LARGE_DATAURI = 'data:image/png;base64,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
Line truncated
|
||||||
|
|
||||||
|
static DUAL_MOTORS_DATAURI = 'data:image/png;base64,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
Line truncated
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,6 @@ declare const pxtTargetBundle: any;
|
|||||||
|
|
||||||
let soundCache: any;
|
let soundCache: any;
|
||||||
let soundIconCache: any;
|
let soundIconCache: any;
|
||||||
let soundIconCacheArray: any;
|
|
||||||
|
|
||||||
export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldCustom {
|
export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldCustom {
|
||||||
public isFieldCustom_ = true;
|
public isFieldCustom_ = true;
|
||||||
@@ -24,17 +23,17 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
super(text, { blocksInfo: options.blocksInfo, sort: true, data: options.data }, validator);
|
super(text, { blocksInfo: options.blocksInfo, sort: true, data: options.data }, validator);
|
||||||
|
|
||||||
this.columns_ = parseInt(options.columns) || 4;
|
this.columns_ = parseInt(options.columns) || 4;
|
||||||
this.width_ = parseInt(options.width) || 450;
|
this.width_ = parseInt(options.width) || 380;
|
||||||
|
|
||||||
this.setText = Blockly.FieldDropdown.prototype.setText;
|
this.setText = Blockly.FieldDropdown.prototype.setText;
|
||||||
this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
|
this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
|
||||||
|
this.updateTextNode_ = Blockly.Field.prototype.updateTextNode_;
|
||||||
|
|
||||||
if (!pxt.BrowserUtils.isIE() && !soundCache) {
|
if (!pxt.BrowserUtils.isIE() && !soundCache) {
|
||||||
soundCache = JSON.parse(pxtTargetBundle.bundledpkgs['music']['sounds.jres']);
|
soundCache = JSON.parse(pxtTargetBundle.bundledpkgs['music']['sounds.jres']);
|
||||||
}
|
}
|
||||||
if (!soundIconCache) {
|
if (!soundIconCache) {
|
||||||
soundIconCache = JSON.parse(pxtTargetBundle.bundledpkgs['music']['icons.jres']);
|
soundIconCache = JSON.parse(pxtTargetBundle.bundledpkgs['music']['icons.jres']);
|
||||||
soundIconCacheArray = Object.entries(soundIconCache).filter(el => el[0] !== "*");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,17 +50,14 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||||
Blockly.DropDownDiv.clearContent();
|
Blockly.DropDownDiv.clearContent();
|
||||||
// Populate the drop-down with the icons for this field.
|
// Populate the drop-down with the icons for this field.
|
||||||
let dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement;
|
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
|
||||||
let contentDiv = document.createElement('div');
|
let contentDiv = document.createElement('div');
|
||||||
// Accessibility properties
|
// Accessibility properties
|
||||||
contentDiv.setAttribute('role', 'menu');
|
contentDiv.setAttribute('role', 'menu');
|
||||||
contentDiv.setAttribute('aria-haspopup', 'true');
|
contentDiv.setAttribute('aria-haspopup', 'true');
|
||||||
contentDiv.className = 'blocklyMusicFieldOptions';
|
contentDiv.className = 'blocklyMusicFieldOptions';
|
||||||
contentDiv.style.display = "flex";
|
|
||||||
contentDiv.style.flexWrap = "wrap";
|
|
||||||
contentDiv.style.float = "none";
|
|
||||||
const options = this.getOptions();
|
const options = this.getOptions();
|
||||||
//options.sort(); // Do not need to use to not apply sorting in different languages
|
options.sort();
|
||||||
|
|
||||||
// Create categoies
|
// Create categoies
|
||||||
const categories = this.getCategories(options);
|
const categories = this.getCategories(options);
|
||||||
@@ -72,7 +68,7 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
// Accessibility properties
|
// Accessibility properties
|
||||||
categoriesDiv.setAttribute('role', 'menu');
|
categoriesDiv.setAttribute('role', 'menu');
|
||||||
categoriesDiv.setAttribute('aria-haspopup', 'true');
|
categoriesDiv.setAttribute('aria-haspopup', 'true');
|
||||||
categoriesDiv.style.backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary();
|
categoriesDiv.style.backgroundColor = this.sourceBlock_.getColourTertiary();
|
||||||
categoriesDiv.className = 'blocklyMusicFieldCategories';
|
categoriesDiv.className = 'blocklyMusicFieldCategories';
|
||||||
|
|
||||||
this.refreshCategories(categoriesDiv, categories);
|
this.refreshCategories(categoriesDiv, categories);
|
||||||
@@ -86,18 +82,30 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
dropdownDiv.appendChild(categoriesDiv);
|
dropdownDiv.appendChild(categoriesDiv);
|
||||||
dropdownDiv.appendChild(contentDiv);
|
dropdownDiv.appendChild(contentDiv);
|
||||||
|
|
||||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary());
|
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
|
||||||
|
|
||||||
// Position based on the field position.
|
// Calculate positioning based on the field position.
|
||||||
Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this));
|
let scale = (<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).scale;
|
||||||
|
let bBox = { width: this.size_.width, height: this.size_.height };
|
||||||
|
bBox.width *= scale;
|
||||||
|
bBox.height *= scale;
|
||||||
|
let position = this.fieldGroup_.getBoundingClientRect();
|
||||||
|
let primaryX = position.left + bBox.width / 2;
|
||||||
|
let primaryY = position.top + bBox.height;
|
||||||
|
let secondaryX = primaryX;
|
||||||
|
let secondaryY = position.top;
|
||||||
|
// Set bounds to workspace; show the drop-down.
|
||||||
|
(Blockly.DropDownDiv as any).setBoundsElement((<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).getParentSvg().parentNode);
|
||||||
|
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
|
||||||
|
this.onHide_.bind(this));
|
||||||
|
|
||||||
// Update colour to look selected.
|
// Update colour to look selected.
|
||||||
let source = this.sourceBlock_ as Blockly.BlockSvg;
|
if (this.sourceBlock_.isShadow()) {
|
||||||
this.savedPrimary_ = source?.getColour();
|
this.savedPrimary_ = this.sourceBlock_.getColour();
|
||||||
if (source?.isShadow()) {
|
this.sourceBlock_.setColour(this.sourceBlock_.getColourTertiary(),
|
||||||
source.setColour(source.getColourTertiary());
|
this.sourceBlock_.getColourSecondary(), this.sourceBlock_.getColourTertiary());
|
||||||
} else if (this.borderRect_) {
|
} else if (this.box_) {
|
||||||
this.borderRect_.setAttribute('fill', (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary());
|
this.box_.setAttribute('fill', this.sourceBlock_.getColourTertiary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +138,6 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
backgroundColor = '#0c4e5e';
|
backgroundColor = '#0c4e5e';
|
||||||
button.setAttribute('aria-selected', 'true');
|
button.setAttribute('aria-selected', 'true');
|
||||||
}
|
}
|
||||||
button.style.padding = "2px 6px";
|
|
||||||
button.style.backgroundColor = backgroundColor;
|
button.style.backgroundColor = backgroundColor;
|
||||||
button.style.borderColor = backgroundColor;
|
button.style.borderColor = backgroundColor;
|
||||||
Blockly.bindEvent_(button, 'click', this, this.categoryClick_);
|
Blockly.bindEvent_(button, 'click', this, this.categoryClick_);
|
||||||
@@ -144,7 +151,7 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshOptions(contentDiv: Element, options: any) {
|
refreshOptions(contentDiv: Element, options: any) {
|
||||||
const categories = this.getCategories(options);
|
|
||||||
// Show options
|
// Show options
|
||||||
for (let i = 0, option: any; option = options[i]; i++) {
|
for (let i = 0, option: any; option = options[i]; i++) {
|
||||||
let content = (options[i] as any)[0]; // Human-readable text or image.
|
let content = (options[i] as any)[0]; // Human-readable text or image.
|
||||||
@@ -179,11 +186,11 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
let backgroundColor = this.savedPrimary_ || this.sourceBlock_.getColour();
|
let backgroundColor = this.savedPrimary_ || this.sourceBlock_.getColour();
|
||||||
if (value == this.getValue()) {
|
if (value == this.getValue()) {
|
||||||
// This icon is selected, show it in a different colour
|
// This icon is selected, show it in a different colour
|
||||||
backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary();
|
backgroundColor = this.sourceBlock_.getColourTertiary();
|
||||||
button.setAttribute('aria-selected', 'true');
|
button.setAttribute('aria-selected', 'true');
|
||||||
}
|
}
|
||||||
button.style.backgroundColor = backgroundColor;
|
button.style.backgroundColor = backgroundColor;
|
||||||
button.style.borderColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary();
|
button.style.borderColor = this.sourceBlock_.getColourTertiary();
|
||||||
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
||||||
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
|
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
|
||||||
// These are applied manually instead of using the :hover pseudoclass
|
// These are applied manually instead of using the :hover pseudoclass
|
||||||
@@ -208,27 +215,15 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
this.setAttribute('class', 'blocklyDropDownButton');
|
this.setAttribute('class', 'blocklyDropDownButton');
|
||||||
contentDiv.removeAttribute('aria-activedescendant');
|
contentDiv.removeAttribute('aria-activedescendant');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find index in array by category name
|
|
||||||
const categoryIndex = categories.indexOf(category);
|
|
||||||
|
|
||||||
let buttonImg = document.createElement('img');
|
let buttonImg = document.createElement('img');
|
||||||
buttonImg.src = this.getSoundIcon(categoryIndex);
|
buttonImg.src = this.getSoundIcon(category);
|
||||||
|
//buttonImg.alt = icon.alt;
|
||||||
// Upon click/touch, we will be able to get the clicked element as e.target
|
// Upon click/touch, we will be able to get the clicked element as e.target
|
||||||
// Store a data attribute on all possible click targets so we can match it to the icon.
|
// Store a data attribute on all possible click targets so we can match it to the icon.
|
||||||
const textNode = this.createTextNode_(content);
|
const textNode = this.createTextNode_(content);
|
||||||
button.setAttribute('data-value', value);
|
button.setAttribute('data-value', value);
|
||||||
buttonImg.setAttribute('data-value', value);
|
buttonImg.setAttribute('data-value', value);
|
||||||
buttonImg.style.height = "auto";
|
|
||||||
textNode.setAttribute('data-value', value);
|
textNode.setAttribute('data-value', value);
|
||||||
if (pxt.Util.userLanguage() !== "en") textNode.setAttribute('lang', pxt.Util.userLanguage()); // for hyphens, here you need to set the correct abbreviation of the selected language
|
|
||||||
textNode.style.display = "block";
|
|
||||||
textNode.style.lineHeight = "1rem";
|
|
||||||
textNode.style.marginBottom = "5%";
|
|
||||||
textNode.style.padding = "0px 8px";
|
|
||||||
textNode.style.wordBreak = "break-word";
|
|
||||||
textNode.style.hyphens = "auto";
|
|
||||||
|
|
||||||
button.appendChild(buttonImg);
|
button.appendChild(buttonImg);
|
||||||
button.appendChild(textNode);
|
button.appendChild(textNode);
|
||||||
@@ -243,19 +238,12 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
super.onHide_();
|
super.onHide_();
|
||||||
(Blockly.DropDownDiv.getContentDiv() as HTMLElement).style.maxHeight = '';
|
(Blockly.DropDownDiv.getContentDiv() as HTMLElement).style.maxHeight = '';
|
||||||
this.stopSounds();
|
this.stopSounds();
|
||||||
// Update color (deselect) on dropdown hide
|
|
||||||
let source = this.sourceBlock_ as Blockly.BlockSvg;
|
|
||||||
if (source?.isShadow()) {
|
|
||||||
source.setColour(this.savedPrimary_);
|
|
||||||
} else if (this.borderRect_) {
|
|
||||||
this.borderRect_.setAttribute('fill', this.savedPrimary_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createTextNode_(content: string) {
|
protected createTextNode_(content: string) {
|
||||||
const category = this.parseCategory(content);
|
const category = this.parseCategory(content);
|
||||||
let text = content.substr(content.indexOf(' ') + 1);
|
let text = content.substr(content.indexOf(' ') + 1);
|
||||||
|
text = text.length > 15 ? text.substr(0, 12) + "..." : text;
|
||||||
const textSpan = document.createElement('span');
|
const textSpan = document.createElement('span');
|
||||||
textSpan.setAttribute('class', 'blocklyDropdownText');
|
textSpan.setAttribute('class', 'blocklyDropdownText');
|
||||||
textSpan.textContent = text;
|
textSpan.textContent = text;
|
||||||
@@ -323,9 +311,9 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
pxsim.AudioContextManager.stop();
|
pxsim.AudioContextManager.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSoundIcon(indexCategory: number) {
|
private getSoundIcon(category: string) {
|
||||||
if (soundIconCacheArray && soundIconCacheArray[indexCategory]) {
|
if (soundIconCache && soundIconCache[category]) {
|
||||||
return soundIconCacheArray[indexCategory][1].icon;
|
return soundIconCache[category].icon;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export class FieldPorts extends pxtblockly.FieldImages implements Blockly.FieldC
|
|||||||
|
|
||||||
this.setText = Blockly.FieldDropdown.prototype.setText;
|
this.setText = Blockly.FieldDropdown.prototype.setText;
|
||||||
this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
|
this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
|
||||||
|
this.updateTextNode_ = Blockly.Field.prototype.updateTextNode_;
|
||||||
}
|
}
|
||||||
|
|
||||||
trimOptions_() {
|
trimOptions_() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es5",
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
|||||||
@@ -204,8 +204,6 @@ declare namespace control {
|
|||||||
*/
|
*/
|
||||||
//% shim=control::dmesgValue
|
//% shim=control::dmesgValue
|
||||||
function dmesgValue(v: any): void;
|
function dmesgValue(v: any): void;
|
||||||
}
|
|
||||||
declare namespace control {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force GC and dump basic information about heap.
|
* Force GC and dump basic information about heap.
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ namespace sensors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measure the ambient or reflected light value from 0 (darkest) to 100 (brightest). In raw reflection light mode, the range will be different.
|
* Measure the ambient or reflected light value from 0 (darkest) to 100 (brightest).
|
||||||
* @param sensor the color sensor port
|
* @param sensor the color sensor port
|
||||||
*/
|
*/
|
||||||
//% help=sensors/color-sensor/light
|
//% help=sensors/color-sensor/light
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
namespace sensors {
|
namespace sensors {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,7 +9,7 @@ namespace sensors {
|
|||||||
//% blockId=colorEnumPicker block="%color" shim=TD_ID
|
//% blockId=colorEnumPicker block="%color" shim=TD_ID
|
||||||
//% weight=0 blockHidden=1 turnRatio.fieldOptions.decompileLiterals=1
|
//% weight=0 blockHidden=1 turnRatio.fieldOptions.decompileLiterals=1
|
||||||
//% color.fieldEditor="colorenum"
|
//% color.fieldEditor="colorenum"
|
||||||
//% color.fieldOptions.colours='["#f12a21", "#ffd01b", "#006db3", "#00934b", "#000000", "#6c2d00", "#ffffff", "#dfe6e9"]'
|
//% color.fieldOptions.colours='["#f12a21", "#ffd01b", "#006db3", "#00934b", "#000000", "#6c2d00", "#ffffff"]'
|
||||||
//% color.fieldOptions.columns=2 color.fieldOptions.className='legoColorPicker'
|
//% color.fieldOptions.columns=2 color.fieldOptions.className='legoColorPicker'
|
||||||
export function __colorEnumPicker(color: ColorSensorColor): number {
|
export function __colorEnumPicker(color: ColorSensorColor): number {
|
||||||
return color;
|
return color;
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace control {
|
|
||||||
let lastPause = 0;
|
|
||||||
const COOPERATION_INTERVAL = 20
|
|
||||||
export function cooperate() {
|
|
||||||
const now = control.millis()
|
|
||||||
if (now - lastPause > COOPERATION_INTERVAL) {
|
|
||||||
lastPause = now
|
|
||||||
pause(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -68,41 +68,5 @@
|
|||||||
},
|
},
|
||||||
"portAD": {
|
"portAD": {
|
||||||
"icon": "data:image/png;base64,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"
|
"icon": "data:image/png;base64,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"
|
||||||
},
|
|
||||||
"dualMotorLargePortAB": {
|
|
||||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALoAAAC6CAYAAAAZDlfxAAAACXBIWXMAAC4jAAAuIwF4pT92AAAgAElEQVR4nO2dC3Qb5Zn3n5FkyZJ8ja+xnYsNcSB2Qi40CSlbzK2hEEjId4JJKPAVSCgNl5A9XThAib1ACcvuNgT22y3bC7QsG5E9W6BbaNqGxAFCgH4QIJCLSWwTO3bsOLHlqyTbs+f/ekYeyaOrR9bt/Z0zRzOvRqN5pf/zzvM+700QRZE4nERHx/9hTjLAhc5JCrjQOUkBFzonKeBC5yQFXOicpIALnZMUcKFzkgIudE5SwIXOSQq40DlJARf6BKiqqjIIgpAqbYWCIJRL23mCIGQq3jPV1NTw3zqK8E5dYSAIgoGIzEQ0lYjypSuklpsMud9LN5XpBBpJFYTBt3ocRz4bcPUS0QARtRLRWVEUB+MlnzDkuro6g3SYRUQZ0v4wEZ0hIod0LG7ZssVVU1MzEqVbDQgXegigVK6trYXAi4mokIgsD+RaF16dbrqy0KCrTNUJ+d5XGxKpv9k1/EFdn/OPz7b3vkNEx4locM+ePc6qqqqY/PHVDHlOqiH36jTTHOzLhmwQaPiJ072fxoMhc6EHifTn5xBRGUq3B3KtC1Znpq7NMegq1a4gVN9Nwne+R2L9IRJtPydqPUk9I2LDm92D259u732XiI6JotgTS3n0NuQLTYa8TXnWa+aZU65M1wmlvj7XOTRy6OCga9cDLfZXY9WQudCDQBJAHhHNIKK8V6Zn3TLfnLLW1yeFZ35Dwt9cM5bQa6eRjauI6g+xw2OOoTdWN577OyI6IYriUAxkcZwh/7wkc/USi3GdQSDLuJPTMknY9CQJU6eR2Gsn8Rf/wPI2OCK27+51Pv9Qq/3tWDNkLvQgEATBSkTziGjK/5RO2TzTqL/C56dmVZLu5XfGJYtv2Uh88j73cbNr+E/XnDh7i/S4j6pvqzTk2SbDzBdLMn/i60kFdL/ZQ3R+xVgCDPn2y9lTCzQ5h/9yXcPZjbFkyDwSEACppMOjPCugyHF+eqZ6+tRpHsclKfrv7j4v5yU8IaIdkZHclTKI/KXpWVv9iVxY+G1PkYO0DBKuvdl9OMOov+qd83J+hd9MEISY0BgXemDw6C7cUpB+aSCRA/HYIfX0fW+PSysw6K57vjjz+7W1tSmTkA9VlIYMkfvzxUMh36D7dqwYMnGh+wfhNTm6cn2G6a6gPtTbTeJT93umfbqfxD/sUD39O1Zj7eMFabkRzYh/mCG/Mj3r1mBELrZ+o/5G28lxSbFgyDKG4E5LTqQYcv4/FWVcw0KHU6e5H9GshK5XL71fqKuj/g+X0oKsTFpGTrJ8/aXP308vkPUSq/FuInp8sn9k2ZCXp5tK/VWuPWg9yQxZeOBJ5rIA8bUXfRryMmvKjx8vSMObLRHJRJBwoftAetyaDAaDbpnVeAN8U+GF37lPFu78MfvDvf/gTl0KHTRaiUaIjp3toZzeVloQ4LsKDfo7oyF02ZDvy7XehmOEQ+m6tUQ93URv7SDxk/dVP3ff/o8o973LaXZ2Nl1/5huy9Hb5/A6TIBQsGDWif4xcTgLDha6CHGrb+szW58988rEl/eC+UoTTvEGp1vfWayzVogiclA8NMMF36gw0fcgZ8PtSBCr6sDyvbMmxjhOTlUelIZek6JeyuP8DT4ydcG010cO3j6tbHE0x04Cgo5N6E52099N8h4NmB/iu6UZmyFEVOvfRvaiqqkpFBGL7C9tftqZZ1xSnp13HzvCONNBotMFmyaMHssvo2YxiltQ9NEx728/R6dOnaWN3M+WMuIL63v4RcUmk8uQNDPmDAx+c/9zzz/3+qRuu2YpYOZ5Q487b9BQzWIhaxjI0REv6u6h4yElmcYRmuwYCfp9JEC545/zcKRHOll94ia5AKsmnb3tu27/k5+df1draSvkdp0ZPgJ/tLfa2ZvoUbgoRuUSiex2p1EcCO24XBZo/HFgEMhadcBER/Wck8qUEhrzo4kWVa9et3dHb23tegX70fmV/24PCEvpVWj4dM5gpZ2SItnY10tEhkX430E9W6qPXUoPPn0sUlxLRW5HLmX94iS4huyvbX9i+vXBq4VVI1el0JEjCHXniPtYwokTc9ih7jIN8l4MaRB0TuAyE/ztTFt2fXUa/thawFkXdv7xOuv3tpPvT16R0h1IEinjDCvLY29dbufHejTaz2Xze0NAQpTilriltzePO7/vL60zko/kbpJsGzfQzl5EdW6Vs9gs6tnkwq5LlVYlVJ8yPdP78wUv0MXdlOkSen5+/XE5PT08nvUE/elB/iLX+Cd+5lpV+4ls7WARiqy6FlerTXYM0oh+hN4bHflIIv9VgYsaw35ROdz7xc6IFy0bfRCPLTRtYxU/85bN4vEdU6BD5d5d/twwludlsRjO/pyE/dBszQnfJ3tZM/S8/5/686HC4n1YkPbF+4DBTVaaF3rNk0zJHD/2gKI90z/yGPQlIjsZse4ztW3TC+K4Ek0jSC93bXVG+ZzAYaMiUOpaA0Bo6aCmAD37V4GjUwaHXu4W+weBkpd6bkmszbdgxJnLl9y/8NhN6JFG6KyjJ5a8aZ8irF5FQPtooiogLOr48h4qnwUQ5Q04qNOjo1aGxkHifSNRkGtUvKqkehoK8TaIhByKpha50V7xFLr1PXdnBt+Xsy8inVY4Bcg4P08qR0f91Wm8r7Tdm0HzWLd0vezXLmGceDBC55K6UKd+DISOPbtDY5RVStCgqnAWKcx9LcVCmTqCfSRXV2Rnpqn6+wpAjkr9gSVqhy6XcD+/54S/S09Mv8nVe97RScqUYKcXlP0wI94XFz6US/GS3k5XiEIk7MvHp/nGluiysiiPtmgtBzV3xep86CoqJvvg44LUQfdmfWUjXOh1kdvTTJSPDLP3x7pMs7wsyC1U/J47VaxonmJ0JkbSV0bq6usJVN67a5E/kKSkplJ2dTSeLZgZ1TbMUS8crc1W8GHnodtaLkYHurb98lm0jRL/XIEsewJA33L3hurXr1r6ldFe86S8oCup6EDPCjAfNGfRBVqG7Aop83jBwlqY1fDVqyN784T+Rv5MVR9qjKvRkdl2M+fn5PkUuY7VaqW3ORVTWdMznOWhRXFR9Ny0ioiN//YDM//UL9RPhGjx5n0d3XTAk6J5T/0D4wJBvXH3jvf5EDkNOKyik5qIZVHKqye93oS6CECMawSBui0rPYhiy3E8doD6DBqcRkd7QOn+hkvSV0UAYjUYamn8xtXzxVypubhh3NkQubH3ZfXwBXJOLLxkdaBEEItG+BYfbdkfi1s0Ws7o/oQCG/M1Fi/0KHeJddO3NtCgtgzr+ZwfR8zXqJ8qG7JU8oDdsCzMPmsHj6EGAku/zy77HfHVv0HQ+Doh9ls8u3Uq6BaLbo5k3GLIBhlyi3nERLaYseiJVNPNW3Ex5W38V9PWHRXpi6Venjmt2w2HChR4EEHrujJn0/2+8VVXsavgagOHFqmj7rhTIkC+7dvwHVMKkaohEn8872j7pndXU4EIPErPZTOY5c+nwLXeTyzjW90O1h19bs8+efxLdRHRjJCIt4eDXkNOCMthxiER2gWhl1DMnwYUeAnq9noSyWfTZ7fdSp9z6h8jJay+OXaStmbUy+gKlHIIiFUfaX4+lvPk05D+odL9Ri64oQB4Fosti4Wklw4UeIog9p06fSd9s2EyN11fTYHoma+YeWZY/uq1eqDogQyT6RhSEOyqPtF9UcaT9YCzmTTbkT+7+8XhDluLhCI8iuuKLEaLnJZHHVB551CVM8LjvXXwpfb34UkptbSZrQz1ZGupJ19/HjAHbsNE0JDR+/UK60/FyZYyK2xvctzW/gBnymRPHKGf/Hsrd9pi7z4ovhkR6Ra/T/Wru4bY9MZIVD5JG6LadNrT6uFt+7rzrzhJpGosJMzi1hG2dyy73vpSh+WTzO3+7+W/jQuRKYMiu2RXUNruCOgYHmCGbTp0kfWcHpdq7yFE0DYbsFHe//YMCg+6ti462+x5mFAMkvNBtO23oHoo47mXK9Mq5lZ79POIc205blTIHWhrycKqZ7BfOI8LmifHmF195WxTFmBY5JbrQbTttmBhzb/ms8szKikpWSvX199F7779HGFSRCNh22tAy9ZLVas20WqzU3tHOcpVohjxREr1ErykuLs5cMH9seDLEcEXVFVRfXx/VG9MC6Wn1uyXfWkIzZ456ZS6Xi97Z+07CGLJWJHrUZX5JUcm4RJTsmRnhxYdjjBo8qWSRy3mDIXM84eHF+KYqP2/cTNVM7DlTcpL9t/Eg4YXe0Di+Ixb89G57d1TuR2MynT76ycOF4YyR8EJH5ey9/e9RV9doYKDlVAurjGJgcCJw7Nj47sMdHR1k77EnRP60IuHDi6iIoisqSnaU5HjUL5y/kNpPt1NPb0zNwx8WMOQ9e/dQeXk5GVOM7PjosaNxmJPIkvBCz87Kpry8PCouKvZInzJlCnWe7YzafWnF8quXU4oxhVpaWqi9r51KiktY5fSV/3gl7vOmJQkv9KP1R+lc1zlW0vX1jZbo+fn5bCatRCArK4vlonxWuUduLGYLOZzjh/MlKwkvdJR02FZct4LF0Hf9eRcdqz+WMD4s3Ba4ZjBkRFuwj6dY/0A/66TFGSXhK6P444uLi5nIwexZs0ktJBevQOCof6y4dgVzY1xOFx368hANDAQ/XVwykPAleunMUqqYMzZnIvxXbJ99/llU70srYLQwZBlUSmHcvGXUE95gFOdA2Er/HJXuxd9anOw/yzgSvkTHYxzhtuXfXc7cl08Pfsp89EShvb2dVUhl1wwNRaiTcDxJihIdf35/Xz/blxuOEgUY7a4/7XLn5sOPP2Qbx5Ok8NGxyWE4dIKyWC0szpwoFTZlcz8qo5zxJLzQUTFDgxGaxQH2SYozJ4LQ0UU3Lz/Pfbx48WLWQvrbV35LI9FdpzemSBofHaU6Line truncated
|
|
||||||
},
|
|
||||||
"dualMotorLargePortBC": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"dualMotorLargePortCD": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"dualMotorLargePortAD": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorLargePortA": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorLargePortB": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorLargePortC": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorLargePortD": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorMeduimPortA": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorMeduimPortB": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorMeduimPortC": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
},
|
|
||||||
"motorMeduimPortD": {
|
|
||||||
"icon": "data:image/png;base64,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 truncated
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,8 @@ namespace sensors.internal {
|
|||||||
const now = control.millis();
|
const now = control.millis();
|
||||||
if (now - this.lastQuery >= this.interval * 2)
|
if (now - this.lastQuery >= this.interval * 2)
|
||||||
this.queryAndUpdate(); // sensor poller is not allowed to run
|
this.queryAndUpdate(); // sensor poller is not allowed to run
|
||||||
control.cooperate(); // allow events to trigger
|
if (now - this.lastPause >= this.interval * 5)
|
||||||
|
pause(1); // allow events to trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
private queryAndUpdate() {
|
private queryAndUpdate() {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -124,7 +124,7 @@ namespace motors {
|
|||||||
export function stopAll() {
|
export function stopAll() {
|
||||||
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
|
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
|
||||||
writePWM(b);
|
writePWM(b);
|
||||||
control.cooperate();
|
pause(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,7 +136,7 @@ namespace motors {
|
|||||||
//% help=motors/reset-all
|
//% help=motors/reset-all
|
||||||
export function resetAll() {
|
export function resetAll() {
|
||||||
reset(Output.ALL)
|
reset(Output.ALL)
|
||||||
control.cooperate();
|
pause(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MoveSchedule {
|
interface MoveSchedule {
|
||||||
@@ -196,7 +196,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=outputMotorSetBrakeMode block="set %motor|brake %brake=toggleOnOff"
|
//% blockId=outputMotorSetBrakeMode block="set %motor|brake %brake=toggleOnOff"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=60 blockGap=8
|
//% weight=60 blockGap=8
|
||||||
//% group="Properties"
|
//% group="Properties"
|
||||||
//% help=motors/motor/set-brake
|
//% help=motors/motor/set-brake
|
||||||
@@ -211,7 +210,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=outputMotorSetPauseMode block="set %motor|pause on run %brake=toggleOnOff"
|
//% blockId=outputMotorSetPauseMode block="set %motor|pause on run %brake=toggleOnOff"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=60 blockGap=8
|
//% weight=60 blockGap=8
|
||||||
//% group="Properties"
|
//% group="Properties"
|
||||||
setPauseOnRun(value: boolean) {
|
setPauseOnRun(value: boolean) {
|
||||||
@@ -224,7 +222,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorSetInverted block="set %motor|inverted %reversed=toggleOnOff"
|
//% blockId=motorSetInverted block="set %motor|inverted %reversed=toggleOnOff"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=59 blockGap=8
|
//% weight=59 blockGap=8
|
||||||
//% group="Properties"
|
//% group="Properties"
|
||||||
//% help=motors/motor/set-inverted
|
//% help=motors/motor/set-inverted
|
||||||
@@ -242,7 +239,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorSetBrakeSettleTime block="set %motor|brake settle time %millis|ms"
|
//% blockId=motorSetBrakeSettleTime block="set %motor|brake settle time %millis|ms"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=1 blockGap=8
|
//% weight=1 blockGap=8
|
||||||
//% group="Properties"
|
//% group="Properties"
|
||||||
//% millis.defl=200 millis.min=0 millis.max=500
|
//% millis.defl=200 millis.min=0 millis.max=500
|
||||||
@@ -273,7 +269,7 @@ namespace motors {
|
|||||||
if (this._brake && this._brakeSettleTime > 0)
|
if (this._brake && this._brakeSettleTime > 0)
|
||||||
pause(this._brakeSettleTime);
|
pause(this._brakeSettleTime);
|
||||||
else {
|
else {
|
||||||
control.cooperate();
|
pause(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +280,7 @@ namespace motors {
|
|||||||
// allow robot to settle
|
// allow robot to settle
|
||||||
this.settle();
|
this.settle();
|
||||||
} else {
|
} else {
|
||||||
control.cooperate();
|
pause(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +344,6 @@ namespace motors {
|
|||||||
//% weight=100 blockGap=8
|
//% weight=100 blockGap=8
|
||||||
//% group="Move"
|
//% group="Move"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% expandableArgumentMode=toggle
|
//% expandableArgumentMode=toggle
|
||||||
//% help=motors/motor/run
|
//% help=motors/motor/run
|
||||||
run(speed: number, value: number = 0, unit: MoveUnit = MoveUnit.MilliSeconds) {
|
run(speed: number, value: number = 0, unit: MoveUnit = MoveUnit.MilliSeconds) {
|
||||||
@@ -362,7 +357,7 @@ namespace motors {
|
|||||||
// special: 0 is infinity
|
// special: 0 is infinity
|
||||||
if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) {
|
if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) {
|
||||||
this._run(schedule.speed);
|
this._run(schedule.speed);
|
||||||
control.cooperate();
|
pause(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +393,6 @@ namespace motors {
|
|||||||
//% weight=99 blockGap=8
|
//% weight=99 blockGap=8
|
||||||
//% group="Move"
|
//% group="Move"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% help=motors/motor/ramp
|
//% help=motors/motor/ramp
|
||||||
//% inlineInputMode=inline
|
//% inlineInputMode=inline
|
||||||
//% expandableArgumentMode=toggle
|
//% expandableArgumentMode=toggle
|
||||||
@@ -428,7 +422,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=outputMotorsetRunRamp block="set %motor|run %ramp to $value||$unit"
|
//% blockId=outputMotorsetRunRamp block="set %motor|run %ramp to $value||$unit"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=21 blockGap=8
|
//% weight=21 blockGap=8
|
||||||
//% group="Properties"
|
//% group="Properties"
|
||||||
//% help=motors/motor/set-run-phase
|
//% help=motors/motor/set-run-phase
|
||||||
@@ -500,7 +493,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=outputMotorSetRegulated block="set %motor|regulated %value=toggleOnOff"
|
//% blockId=outputMotorSetRegulated block="set %motor|regulated %value=toggleOnOff"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=58 blockGap=8
|
//% weight=58 blockGap=8
|
||||||
//% group="Properties"
|
//% group="Properties"
|
||||||
//% help=motors/motor/set-regulated
|
//% help=motors/motor/set-regulated
|
||||||
@@ -526,7 +518,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorPauseUntilRead block="pause until %motor|ready"
|
//% blockId=motorPauseUntilRead block="pause until %motor|ready"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=90 blockGap=8
|
//% weight=90 blockGap=8
|
||||||
//% group="Move"
|
//% group="Move"
|
||||||
pauseUntilReady(timeOut?: number) {
|
pauseUntilReady(timeOut?: number) {
|
||||||
@@ -585,7 +576,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorSpeed block="%motor|speed"
|
//% blockId=motorSpeed block="%motor|speed"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=72
|
//% weight=72
|
||||||
//% blockGap=8
|
//% blockGap=8
|
||||||
//% group="Counters"
|
//% group="Counters"
|
||||||
@@ -601,14 +591,13 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorAngle block="%motor|angle"
|
//% blockId=motorAngle block="%motor|angle"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=70
|
//% weight=70
|
||||||
//% blockGap=8
|
//% blockGap=8
|
||||||
//% group="Counters"
|
//% group="Counters"
|
||||||
//% help=motors/motor/angle
|
//% help=motors/motor/angle
|
||||||
angle(): number {
|
angle(): number {
|
||||||
this.init();
|
this.init();
|
||||||
return getMotorData(this._port).count * this.invertedFactor();
|
return getMotorData(this._port).count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -616,7 +605,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorClearCount block="clear %motor|counters"
|
//% blockId=motorClearCount block="clear %motor|counters"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=68
|
//% weight=68
|
||||||
//% blockGap=8
|
//% blockGap=8
|
||||||
//% group="Counters"
|
//% group="Counters"
|
||||||
@@ -645,7 +633,6 @@ namespace motors {
|
|||||||
*/
|
*/
|
||||||
//% blockId=motorPauseUntilStall block="pause until %motor|stalled"
|
//% blockId=motorPauseUntilStall block="pause until %motor|stalled"
|
||||||
//% motor.fieldEditor="motors"
|
//% motor.fieldEditor="motors"
|
||||||
//% motor.fieldOptions.decompileLiterals=1
|
|
||||||
//% weight=89
|
//% weight=89
|
||||||
//% group="Move"
|
//% group="Move"
|
||||||
//% help=motors/motor/pause-until-stalled
|
//% help=motors/motor/pause-until-stalled
|
||||||
@@ -669,28 +656,28 @@ namespace motors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motor A" jres=icons.motorLargePortA
|
//% whenUsed fixedInstance block="large motor A" jres=icons.portA
|
||||||
export const largeA = new Motor(Output.A, true);
|
export const largeA = new Motor(Output.A, true);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motor B" jres=icons.motorLargePortB
|
//% whenUsed fixedInstance block="large motor B" jres=icons.portB
|
||||||
export const largeB = new Motor(Output.B, true);
|
export const largeB = new Motor(Output.B, true);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motor C" jres=icons.motorLargePortC
|
//% whenUsed fixedInstance block="large motor C" jres=icons.portC
|
||||||
export const largeC = new Motor(Output.C, true);
|
export const largeC = new Motor(Output.C, true);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motor D" jres=icons.motorLargePortD
|
//% whenUsed fixedInstance block="large motor D" jres=icons.portD
|
||||||
export const largeD = new Motor(Output.D, true);
|
export const largeD = new Motor(Output.D, true);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="medium motor A" jres=icons.motorMeduimPortA
|
//% whenUsed fixedInstance block="medium motor A" jres=icons.portA
|
||||||
export const mediumA = new Motor(Output.A, false);
|
export const mediumA = new Motor(Output.A, false);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="medium motor B" jres=icons.motorMeduimPortB
|
//% whenUsed fixedInstance block="medium motor B" jres=icons.portB
|
||||||
export const mediumB = new Motor(Output.B, false);
|
export const mediumB = new Motor(Output.B, false);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="medium motor C" jres=icons.motorMeduimPortC
|
//% whenUsed fixedInstance block="medium motor C" jres=icons.portC
|
||||||
export const mediumC = new Motor(Output.C, false);
|
export const mediumC = new Motor(Output.C, false);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="medium motor D" jres=icons.motorMeduimPortD
|
//% whenUsed fixedInstance block="medium motor D" jres=icons.portD
|
||||||
export const mediumD = new Motor(Output.D, false);
|
export const mediumD = new Motor(Output.D, false);
|
||||||
|
|
||||||
//% fixedInstances
|
//% fixedInstances
|
||||||
@@ -721,7 +708,7 @@ namespace motors {
|
|||||||
* @param unit (optional) unit of the value
|
* @param unit (optional) unit of the value
|
||||||
*/
|
*/
|
||||||
//% blockId=motorPairTank block="tank **motors** %motors %speedLeft=motorSpeedPicker|\\% %speedRight=motorSpeedPicker|\\%||for %value %unit"
|
//% blockId=motorPairTank block="tank **motors** %motors %speedLeft=motorSpeedPicker|\\% %speedRight=motorSpeedPicker|\\%||for %value %unit"
|
||||||
//% motors.fieldEditor="motors"
|
//% motors.fieldEditor="ports"
|
||||||
//% weight=96 blockGap=8
|
//% weight=96 blockGap=8
|
||||||
//% inlineInputMode=inline
|
//% inlineInputMode=inline
|
||||||
//% group="Move"
|
//% group="Move"
|
||||||
@@ -751,7 +738,7 @@ namespace motors {
|
|||||||
* @param unit (optional) unit of the value
|
* @param unit (optional) unit of the value
|
||||||
*/
|
*/
|
||||||
//% blockId=motorPairSteer block="steer **motors** %chassis turn ratio %turnRatio=motorTurnRatioPicker speed %speed=motorSpeedPicker|\\%||for %value %unit"
|
//% blockId=motorPairSteer block="steer **motors** %chassis turn ratio %turnRatio=motorTurnRatioPicker speed %speed=motorSpeedPicker|\\%||for %value %unit"
|
||||||
//% chassis.fieldEditor="motors"
|
//% chassis.fieldEditor="ports"
|
||||||
//% weight=95
|
//% weight=95
|
||||||
//% turnRatio.min=-200 turnRatio=200
|
//% turnRatio.min=-200 turnRatio=200
|
||||||
//% inlineInputMode=inline
|
//% inlineInputMode=inline
|
||||||
@@ -824,16 +811,16 @@ namespace motors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motors B+C" jres=icons.dualMotorLargePortBC
|
//% whenUsed fixedInstance block="B+C" jres=icons.portBC
|
||||||
export const largeBC = new SynchedMotorPair(Output.BC);
|
export const largeBC = new SynchedMotorPair(Output.BC);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motors A+D" jres=icons.dualMotorLargePortAD
|
//% whenUsed fixedInstance block="A+D" jres=icons.portAD
|
||||||
export const largeAD = new SynchedMotorPair(Output.AD);
|
export const largeAD = new SynchedMotorPair(Output.AD);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motors A+B" jres=icons.dualMotorLargePortAB
|
//% whenUsed fixedInstance block="A+B" jres=icons.portAB
|
||||||
export const largeAB = new SynchedMotorPair(Output.AB);
|
export const largeAB = new SynchedMotorPair(Output.AB);
|
||||||
|
|
||||||
//% whenUsed fixedInstance block="large motors C+D" jres=icons.dualMotorLargePortCD
|
//% whenUsed fixedInstance block="C+D" jres=icons.portCD
|
||||||
export const largeCD = new SynchedMotorPair(Output.CD);
|
export const largeCD = new SynchedMotorPair(Output.CD);
|
||||||
|
|
||||||
function reset(out: Output) {
|
function reset(out: Output) {
|
||||||
|
|||||||
@@ -28,8 +28,7 @@
|
|||||||
"platform.h",
|
"platform.h",
|
||||||
"platform.cpp",
|
"platform.cpp",
|
||||||
"dmesg.cpp",
|
"dmesg.cpp",
|
||||||
"integrator.ts",
|
"integrator.ts"
|
||||||
"cooperate.ts"
|
|
||||||
],
|
],
|
||||||
"testFiles": [
|
"testFiles": [
|
||||||
"test.ts"
|
"test.ts"
|
||||||
|
|||||||
@@ -12,5 +12,4 @@ music.setTempo(120)
|
|||||||
music.noteFrequency(Note.C)
|
music.noteFrequency(Note.C)
|
||||||
music.beat()
|
music.beat()
|
||||||
music.setVolume(50)
|
music.setVolume(50)
|
||||||
music.volume()
|
|
||||||
```
|
```
|
||||||
@@ -13,22 +13,7 @@
|
|||||||
|
|
||||||
namespace music {
|
namespace music {
|
||||||
|
|
||||||
int _readSystemVolume() {
|
uint8_t currVolume = 50;
|
||||||
char ParBuf[8];
|
|
||||||
int volume;
|
|
||||||
|
|
||||||
int fd = open("../sys/settings/Volume.rtf", O_RDONLY);
|
|
||||||
read(fd, ParBuf, sizeof(ParBuf));
|
|
||||||
close(fd);
|
|
||||||
if (sscanf(ParBuf,"%d",&volume) > 0) {
|
|
||||||
if ((volume >= 0) && (volume <= 100)) {
|
|
||||||
return volume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t currVolume = _readSystemVolume();
|
|
||||||
uint8_t *lmsSoundMMap;
|
uint8_t *lmsSoundMMap;
|
||||||
|
|
||||||
int writeDev(void *data, int size) {
|
int writeDev(void *data, int size) {
|
||||||
@@ -52,18 +37,6 @@ void setVolume(int volume) {
|
|||||||
currVolume = max(0, min(100, volume));
|
currVolume = max(0, min(100, volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the output volume of the sound synthesizer.
|
|
||||||
*/
|
|
||||||
//% weight=96
|
|
||||||
//% blockId=synth_get_volume block="volume"
|
|
||||||
//% parts="speaker" blockGap=8
|
|
||||||
//% help=music/volume
|
|
||||||
//% weight=1
|
|
||||||
int volume() {
|
|
||||||
return currVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SOUND_CMD_BREAK 0
|
#define SOUND_CMD_BREAK 0
|
||||||
#define SOUND_CMD_TONE 1
|
#define SOUND_CMD_TONE 1
|
||||||
#define SOUND_CMD_PLAY 2
|
#define SOUND_CMD_PLAY 2
|
||||||
@@ -72,7 +45,7 @@ int volume() {
|
|||||||
|
|
||||||
struct ToneCmd {
|
struct ToneCmd {
|
||||||
uint8_t cmd;
|
uint8_t cmd;
|
||||||
uint8_t lvl;
|
uint8_t vol;
|
||||||
uint16_t freq;
|
uint16_t freq;
|
||||||
uint16_t duration;
|
uint16_t duration;
|
||||||
};
|
};
|
||||||
@@ -82,26 +55,10 @@ static void _stopSound() {
|
|||||||
writeDev(&cmd, sizeof(cmd));
|
writeDev(&cmd, sizeof(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t _getVolumeLevel(uint8_t volume, uint8_t levels) {
|
|
||||||
uint8_t level;
|
|
||||||
uint8_t step = (uint8_t) (100 / (levels - 1));
|
|
||||||
if (volume < step) {
|
|
||||||
level = 0;
|
|
||||||
} else if (volume > step * (levels - 1)) {
|
|
||||||
level = levels;
|
|
||||||
} else {
|
|
||||||
level = (uint8_t) (volume / step);
|
|
||||||
}
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _playTone(uint16_t frequency, uint16_t duration, uint8_t volume) {
|
static void _playTone(uint16_t frequency, uint16_t duration, uint8_t volume) {
|
||||||
// https://github.com/mindboards/ev3sources/blob/78ebaf5b6f8fe31cc17aa5dce0f8e4916a4fc072/lms2012/c_sound/source/c_sound.c#L471
|
|
||||||
uint8_t level = _getVolumeLevel(volume, 13);
|
|
||||||
|
|
||||||
ToneCmd cmd;
|
ToneCmd cmd;
|
||||||
cmd.cmd = SOUND_CMD_TONE;
|
cmd.cmd = SOUND_CMD_TONE;
|
||||||
cmd.lvl = level;
|
cmd.vol = volume;
|
||||||
cmd.freq = frequency;
|
cmd.freq = frequency;
|
||||||
cmd.duration = duration;
|
cmd.duration = duration;
|
||||||
// (*SoundInstance.pSound).Busy = TRUE;
|
// (*SoundInstance.pSound).Busy = TRUE;
|
||||||
@@ -165,8 +122,7 @@ void playSample(Buffer buf) {
|
|||||||
stopUser();
|
stopUser();
|
||||||
pthread_mutex_lock(&pumpMutex);
|
pthread_mutex_lock(&pumpMutex);
|
||||||
*lmsSoundMMap = 1; // BUSY
|
*lmsSoundMMap = 1; // BUSY
|
||||||
// https://github.com/mindboards/ev3sources/blob/78ebaf5b6f8fe31cc17aa5dce0f8e4916a4fc072/lms2012/c_sound/source/c_sound.c#L605
|
uint8_t cmd[] = {SOUND_CMD_PLAY, (uint8_t)((currVolume / 33) + 1)};
|
||||||
uint8_t cmd[] = {SOUND_CMD_PLAY, _getVolumeLevel(currVolume, 8)};
|
|
||||||
writeDev(cmd, 2);
|
writeDev(cmd, 2);
|
||||||
decrRC(currentSample);
|
decrRC(currentSample);
|
||||||
currentSample = buf;
|
currentSample = buf;
|
||||||
|
|||||||
@@ -13,16 +13,6 @@ declare namespace music {
|
|||||||
//% weight=1 shim=music::setVolume
|
//% weight=1 shim=music::setVolume
|
||||||
function setVolume(volume: int32): void;
|
function setVolume(volume: int32): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the output volume of the sound synthesizer.
|
|
||||||
*/
|
|
||||||
//% weight=96
|
|
||||||
//% blockId=synth_get_volume block="volume"
|
|
||||||
//% parts="speaker" blockGap=8
|
|
||||||
//% help=music/volume
|
|
||||||
//% weight=1 shim=music::volume
|
|
||||||
function volume(): int32;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a tone through the speaker for some amount of time.
|
* Play a tone through the speaker for some amount of time.
|
||||||
* @param frequency pitch of the tone to play in Hertz (Hz), eg: Note.C
|
* @param frequency pitch of the tone to play in Hertz (Hz), eg: Note.C
|
||||||
|
|||||||
@@ -5,4 +5,3 @@ music.playTone(1440, 500)
|
|||||||
pause(500)
|
pause(500)
|
||||||
music.playTone(2440, 500)
|
music.playTone(2440, 500)
|
||||||
pause(500)
|
pause(500)
|
||||||
music.volume()
|
|
||||||
@@ -22,7 +22,6 @@ namespace _screen_internal {
|
|||||||
namespace brick {
|
namespace brick {
|
||||||
const textOffset = 4;
|
const textOffset = 4;
|
||||||
const lineOffset = 2;
|
const lineOffset = 2;
|
||||||
|
|
||||||
enum ScreenMode {
|
enum ScreenMode {
|
||||||
None,
|
None,
|
||||||
ShowLines,
|
ShowLines,
|
||||||
@@ -30,14 +29,6 @@ namespace brick {
|
|||||||
Ports,
|
Ports,
|
||||||
Custom
|
Custom
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PrintStyle {
|
|
||||||
//% block="black on white"
|
|
||||||
BlackOnWhite,
|
|
||||||
//% block="white on black"
|
|
||||||
WhiteOnBlack
|
|
||||||
}
|
|
||||||
|
|
||||||
let screenMode = ScreenMode.None;
|
let screenMode = ScreenMode.None;
|
||||||
export let font = image.font8;
|
export let font = image.font8;
|
||||||
|
|
||||||
@@ -57,48 +48,6 @@ namespace brick {
|
|||||||
return ((screen.height - textOffset) / lineHeight()) >> 0
|
return ((screen.height - textOffset) / lineHeight()) >> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of columns
|
|
||||||
*/
|
|
||||||
//%
|
|
||||||
export function columnCount(): number {
|
|
||||||
return ((screen.width - textOffset) / font.charWidth) >> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show text on the screen on a specific line and starting at a column and the selected print style.
|
|
||||||
* @param text the text to print on the screen, eg: "Hello world"
|
|
||||||
* @param line the line number to print the text at (starting at 1), eg: 1
|
|
||||||
* @param column the column number to print the text at (starting at 1), eg: 1
|
|
||||||
* @param printStyle print style black on white or white on black
|
|
||||||
*/
|
|
||||||
//% blockId=screenPrintString block="show string $text|at line $line||column $column|style $printStyle"
|
|
||||||
//% weight=98 group="Screen" inlineInputMode="inline" blockGap=8
|
|
||||||
//% expandableArgumentMode="enabled"
|
|
||||||
//% help=brick/show-string
|
|
||||||
//% line.min=1 line.max=12
|
|
||||||
//% column.min=1 column.max=29
|
|
||||||
export function printString(text: string, line: number, column: number = 1, printStyle: PrintStyle = PrintStyle.BlackOnWhite) {
|
|
||||||
if (screenMode != ScreenMode.ShowLines) {
|
|
||||||
screenMode = ScreenMode.ShowLines;
|
|
||||||
screen.fill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
line = (line - 1) >> 0; // line indexing starts at 1
|
|
||||||
column = (column - 1) >> 0; // column indexing starts at 1
|
|
||||||
const nlines = lineCount();
|
|
||||||
const nColumn = columnCount();
|
|
||||||
if (line < 0 || line > nlines) return; // out of screen by line
|
|
||||||
if (column < 0 || column > nColumn) return; // out of screen by column
|
|
||||||
|
|
||||||
const w = font.charWidth;
|
|
||||||
const h = lineHeight();
|
|
||||||
const x = textOffset + w * column;
|
|
||||||
const y = textOffset + h * line;
|
|
||||||
screen.fillRect(x, y, text.length * font.charWidth, h, (printStyle == PrintStyle.BlackOnWhite ? 0 : 255)); // clear background
|
|
||||||
screen.print(text, x, y, (printStyle == PrintStyle.BlackOnWhite ? 1 : 2), font);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show text on the screen at a specific line.
|
* Show text on the screen at a specific line.
|
||||||
* @param text the text to print on the screen, eg: "Hello world"
|
* @param text the text to print on the screen, eg: "Hello world"
|
||||||
@@ -107,8 +56,7 @@ namespace brick {
|
|||||||
//% blockId=screen_print block="show string %text|at line %line"
|
//% blockId=screen_print block="show string %text|at line %line"
|
||||||
//% weight=98 group="Screen" inlineInputMode="inline" blockGap=8
|
//% weight=98 group="Screen" inlineInputMode="inline" blockGap=8
|
||||||
//% help=brick/show-string
|
//% help=brick/show-string
|
||||||
//% line.min=1 line.max=12
|
//% line.min=1 line.max=10
|
||||||
//% deprecated=true
|
|
||||||
export function showString(text: string, line: number) {
|
export function showString(text: string, line: number) {
|
||||||
if (screenMode != ScreenMode.ShowLines) {
|
if (screenMode != ScreenMode.ShowLines) {
|
||||||
screenMode = ScreenMode.ShowLines;
|
screenMode = ScreenMode.ShowLines;
|
||||||
@@ -126,56 +74,19 @@ namespace brick {
|
|||||||
screen.print(text, textOffset, y, 1, font);
|
screen.print(text, textOffset, y, 1, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a number on the screen on a specific line and starting at a column and the selected print style.
|
|
||||||
* @param value the numeric value
|
|
||||||
* @param line the line number to print the text at, eg: 1
|
|
||||||
* @param column the column number to print the text at (starting at 1), eg: 1
|
|
||||||
* @param printStyle print style black on white or white on black
|
|
||||||
*/
|
|
||||||
//% blockId=screenPrintNumber block="show number $value|at line $line||column $column|style $printStyle"
|
|
||||||
//% weight=97 group="Screen" inlineInputMode="inline" blockGap=8
|
|
||||||
//% expandableArgumentMode="enabled"
|
|
||||||
//% help=brick/show-number
|
|
||||||
//% line.min=1 line.max=12
|
|
||||||
//% column.min=1 column.max=29
|
|
||||||
export function printNumber(value: number, line: number, column: number = 1, printStyle: PrintStyle = PrintStyle.BlackOnWhite) {
|
|
||||||
printString("" + value, line, column, printStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a number on the screen
|
* Show a number on the screen
|
||||||
* @param value the numeric value
|
* @param value the numeric value
|
||||||
* @param line the line number to print the text at, eg: 1
|
* @param line the line number to print the text at, eg: 1
|
||||||
*/
|
*/
|
||||||
//% blockId=screenShowNumber block="show number %name|at line %line"
|
//% blockId=screenShowNumber block="show number %name|at line %line"
|
||||||
//% weight=97 group="Screen" inlineInputMode="inline" blockGap=8
|
//% weight=96 group="Screen" inlineInputMode="inline" blockGap=8
|
||||||
//% help=brick/show-number
|
//% help=brick/show-number
|
||||||
//% line.min=1 line.max=12
|
//% line.min=1 line.max=10
|
||||||
//% deprecated=true
|
|
||||||
export function showNumber(value: number, line: number) {
|
export function showNumber(value: number, line: number) {
|
||||||
showString("" + value, line);
|
showString("" + value, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a name, value pair on the screen on a specific line and starting at a column and the selected print style.
|
|
||||||
* @param name the value name
|
|
||||||
* @param value the numeric value
|
|
||||||
* @param line the line number to print the text at, eg: 1
|
|
||||||
* @param column the column number to print the text at (starting at 1), eg: 1
|
|
||||||
* @param printStyle print style black on white or white on black
|
|
||||||
*/
|
|
||||||
//% blockId=screenPrintValue block="show value $name|= $value|at line $line||column $column|style $printStyle"
|
|
||||||
//% weight=96 group="Screen" inlineInputMode="inline" blockGap=8
|
|
||||||
//% expandableArgumentMode="enabled"
|
|
||||||
//% help=brick/show-value
|
|
||||||
//% line.min=1 line.max=12
|
|
||||||
//% column.min=1 column.max=29
|
|
||||||
export function printValue(name: string, value: number, line: number, column: number = 1, printStyle: PrintStyle = PrintStyle.BlackOnWhite) {
|
|
||||||
value = Math.round(value * 1000) / 1000;
|
|
||||||
printString((name ? name + ": " : "") + value, line, column, printStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a name, value pair on the screen
|
* Show a name, value pair on the screen
|
||||||
* @param value the numeric value
|
* @param value the numeric value
|
||||||
@@ -184,8 +95,7 @@ namespace brick {
|
|||||||
//% blockId=screenShowValue block="show value %name|= %text|at line %line"
|
//% blockId=screenShowValue block="show value %name|= %text|at line %line"
|
||||||
//% weight=96 group="Screen" inlineInputMode="inline" blockGap=8
|
//% weight=96 group="Screen" inlineInputMode="inline" blockGap=8
|
||||||
//% help=brick/show-value
|
//% help=brick/show-value
|
||||||
//% line.min=1 line.max=12
|
//% line.min=1 line.max=10
|
||||||
//% deprecated=true
|
|
||||||
export function showValue(name: string, value: number, line: number) {
|
export function showValue(name: string, value: number, line: number) {
|
||||||
value = Math.round(value * 1000) / 1000;
|
value = Math.round(value * 1000) / 1000;
|
||||||
showString((name ? name + ": " : "") + value, line);
|
showString((name ? name + ": " : "") + value, line);
|
||||||
@@ -213,11 +123,11 @@ namespace brick {
|
|||||||
*/
|
*/
|
||||||
//% blockId=brickShowPorts block="show ports"
|
//% blockId=brickShowPorts block="show ports"
|
||||||
//% help=brick/show-ports blockGap=8
|
//% help=brick/show-ports blockGap=8
|
||||||
//% weight=95 group="Screen"
|
//% weight=10 group="Screen"
|
||||||
export function showPorts() {
|
export function showPorts() {
|
||||||
if (screenMode == ScreenMode.Ports) return;
|
if (screenMode == ScreenMode.Ports) return;
|
||||||
screenMode = ScreenMode.Ports;
|
|
||||||
|
|
||||||
|
screenMode = ScreenMode.Ports;
|
||||||
renderPorts();
|
renderPorts();
|
||||||
control.runInParallel(function () {
|
control.runInParallel(function () {
|
||||||
while (screenMode == ScreenMode.Ports) {
|
while (screenMode == ScreenMode.Ports) {
|
||||||
@@ -236,14 +146,11 @@ namespace brick {
|
|||||||
|
|
||||||
for (let i = 0; i < 4; ++i) {
|
for (let i = 0; i < 4; ++i) {
|
||||||
const x = i * col + 2;
|
const x = i * col + 2;
|
||||||
if (screenMode != ScreenMode.Ports) return;
|
screen.print("ABCD"[i], x, 1 * lineHeight8, 1, image.font8)
|
||||||
screen.print("ABCD"[i], x, 1 * lineHeight8, 1, image.font8);
|
screen.print((i + 1).toString(), x, h - lineHeight8, 1, image.font8)
|
||||||
screen.print((i + 1).toString(), x, h - lineHeight8, 1, image.font8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenMode != ScreenMode.Ports) return;
|
|
||||||
screen.drawLine(0, 5 * lineHeight8, screen.width, 5 * lineHeight8, 1);
|
screen.drawLine(0, 5 * lineHeight8, screen.width, 5 * lineHeight8, 1);
|
||||||
screen.drawLine(0, h - 5 * lineHeight8, screen.width, h - 5 * lineHeight8, 1);
|
screen.drawLine(0, h - 5 * lineHeight8, screen.width, h - 5 * lineHeight8, 1)
|
||||||
|
|
||||||
function scale(x: number) {
|
function scale(x: number) {
|
||||||
if (Math.abs(x) >= 5000) {
|
if (Math.abs(x) >= 5000) {
|
||||||
@@ -260,9 +167,8 @@ namespace brick {
|
|||||||
const data = datas[i];
|
const data = datas[i];
|
||||||
const x = i * col + 2;
|
const x = i * col + 2;
|
||||||
if (!data.actualSpeed && !data.count) continue;
|
if (!data.actualSpeed && !data.count) continue;
|
||||||
if (screenMode != ScreenMode.Ports) return;
|
screen.print(`${scale(data.actualSpeed)}%`, x, 3 * lineHeight8, 1, image.font8)
|
||||||
screen.print(`${scale(data.actualSpeed)}%`, x, 3 * lineHeight8, 1, image.font8);
|
screen.print(`${scale(data.count)}>`, x, 4 * lineHeight8, 1, image.font8)
|
||||||
screen.print(`${scale(data.count)}>`, x, 4 * lineHeight8, 1, image.font8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sensors
|
// sensors
|
||||||
@@ -271,16 +177,16 @@ namespace brick {
|
|||||||
const si = sis[i];
|
const si = sis[i];
|
||||||
const x = (si.port() - 1) * col + 2;
|
const x = (si.port() - 1) * col + 2;
|
||||||
const inf = si._info();
|
const inf = si._info();
|
||||||
if (screenMode != ScreenMode.Ports) return;
|
if (inf)
|
||||||
if (inf) screen.print(inf, x, h - 2 * lineHeight8, 1, inf.length > 4 ? image.font5 : image.font8);
|
screen.print(inf, x, h - 2 * lineHeight8, 1, inf.length > 4 ? image.font5 : image.font8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showBoot() {
|
export function showBoot() {
|
||||||
// pulse green, play startup sound, turn off light
|
// pulse green, play startup sound, turn off light
|
||||||
brick.setStatusLight(StatusLight.GreenPulse);
|
brick.setStatusLight(StatusLight.GreenPulse);
|
||||||
// We pause to give time to read sensor values, so they work in on_start block
|
// We pause for 100ms to give time to read sensor values, so they work in on_start block
|
||||||
pause(400); // It turns out that this time is not enough for the simulator to display the LED change
|
pause(400)
|
||||||
// and we're ready
|
// and we're ready
|
||||||
brick.setStatusLight(StatusLight.Off);
|
brick.setStatusLight(StatusLight.Off);
|
||||||
// always show port by default if no UI is set
|
// always show port by default if no UI is set
|
||||||
@@ -304,33 +210,11 @@ namespace brick {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear on the screen at a specific line.
|
|
||||||
* @param line the line number to clear at (starting at 1), eg: 1
|
|
||||||
*/
|
|
||||||
//% blockId=clearLine block="clear line $line"
|
|
||||||
//% weight=94 group="Screen" inlineInputMode="inline" blockGap=8
|
|
||||||
//% line.min=1 line.max=12
|
|
||||||
export function clearLine(line: number) {
|
|
||||||
if (screenMode != ScreenMode.ShowLines) {
|
|
||||||
screenMode = ScreenMode.ShowLines;
|
|
||||||
screen.fill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
line = (line - 1) >> 0; // line indexing starts at 1
|
|
||||||
const nlines = lineCount();
|
|
||||||
if (line < 0 || line > nlines) return; // out of screen by line
|
|
||||||
|
|
||||||
const h = lineHeight();
|
|
||||||
const y = textOffset + h * line;
|
|
||||||
screen.fillRect(0, y, screen.width, h, 0); // clear background
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the screen
|
* Clear the screen
|
||||||
*/
|
*/
|
||||||
//% blockId=screen_clear_screen block="clear screen"
|
//% blockId=screen_clear_screen block="clear screen"
|
||||||
//% weight=93 group="Screen"
|
//% weight=90 group="Screen"
|
||||||
//% help=brick/clear-screen weight=1
|
//% help=brick/clear-screen weight=1
|
||||||
export function clearScreen() {
|
export function clearScreen() {
|
||||||
screen.fill(0)
|
screen.fill(0)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pxt-ev3",
|
"name": "pxt-ev3",
|
||||||
"version": "1.4.32",
|
"version": "1.4.21",
|
||||||
"description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode",
|
"description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode",
|
||||||
"private": false,
|
"private": false,
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -32,20 +32,21 @@
|
|||||||
"docs/*/*/*.md"
|
"docs/*/*/*.md"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/marked": "^0.3.0",
|
"react": "16.8.3",
|
||||||
"@types/node": "^9.3.0",
|
"webfonts-generator": "^0.4.0",
|
||||||
"@types/react": "16.8.25",
|
"@types/jquery": "3.2.16",
|
||||||
|
"@types/react": "16.0.25",
|
||||||
"@types/react-dom": "16.0.3",
|
"@types/react-dom": "16.0.3",
|
||||||
"@types/web-bluetooth": "0.0.4",
|
"@types/web-bluetooth": "0.0.4",
|
||||||
"@vusion/webfonts-generator": "^0.7.1",
|
"@types/bluebird": "^2.0.33",
|
||||||
"react": "16.8.3",
|
"@types/marked": "^0.3.0",
|
||||||
"react-dom": "16.11.0",
|
"@types/node": "^9.3.0",
|
||||||
"semantic-ui-less": "2.4.1",
|
"semantic-ui-less": "2.2.14",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^3.7.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pxt-common-packages": "9.2.7",
|
"pxt-common-packages": "8.1.3",
|
||||||
"pxt-core": "7.2.27"
|
"pxt-core": "6.2.8"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node node_modules/pxt-core/built/pxt.js travis"
|
"test": "node node_modules/pxt-core/built/pxt.js travis"
|
||||||
|
|||||||
@@ -77,9 +77,8 @@
|
|||||||
"pauseUntilBlock": {
|
"pauseUntilBlock": {
|
||||||
"category": "loops"
|
"category": "loops"
|
||||||
},
|
},
|
||||||
"breakBlock": true,
|
"bannedCategories": [
|
||||||
"continueBlock": true,
|
]
|
||||||
"bannedCategories": []
|
|
||||||
},
|
},
|
||||||
"compileService": {
|
"compileService": {
|
||||||
"buildEngine": "dockermake",
|
"buildEngine": "dockermake",
|
||||||
@@ -107,21 +106,20 @@
|
|||||||
"driveDisplayName": "EV3",
|
"driveDisplayName": "EV3",
|
||||||
"boardName": "LEGO® MINDSTORMS® Education EV3",
|
"boardName": "LEGO® MINDSTORMS® Education EV3",
|
||||||
"copyrightText": "LEGO, the LEGO logo, MINDSTORMS and the MINDSTORMS EV3 logo are trademarks and/ or copyrights of the LEGO Group. ©2018 The LEGO Group. All rights reserved.",
|
"copyrightText": "LEGO, the LEGO logo, MINDSTORMS and the MINDSTORMS EV3 logo are trademarks and/ or copyrights of the LEGO Group. ©2018 The LEGO Group. All rights reserved.",
|
||||||
"crowdinProject": "makecode",
|
"crowdinProject": "kindscript",
|
||||||
"selectLanguage": true,
|
"selectLanguage": true,
|
||||||
"blocksCollapsing": true,
|
|
||||||
"highContrast": true,
|
|
||||||
"greenScreen": true,
|
"greenScreen": true,
|
||||||
"availableLocales": [
|
"availableLocales": [
|
||||||
"en",
|
"en",
|
||||||
"de",
|
"de",
|
||||||
"ja",
|
"ja",
|
||||||
"ru",
|
"ru",
|
||||||
"zh-CN",
|
"zh-CN"
|
||||||
"fr"
|
|
||||||
],
|
],
|
||||||
|
"highContrast": true,
|
||||||
"lightToc": true,
|
"lightToc": true,
|
||||||
"docMenu": [{
|
"docMenu": [
|
||||||
|
{
|
||||||
"name": "Support",
|
"name": "Support",
|
||||||
"path": "https://forum.makecode.com/"
|
"path": "https://forum.makecode.com/"
|
||||||
},
|
},
|
||||||
@@ -160,10 +158,7 @@
|
|||||||
"extendEditor": true,
|
"extendEditor": true,
|
||||||
"extendFieldEditors": true,
|
"extendFieldEditors": true,
|
||||||
"scriptManager": true,
|
"scriptManager": true,
|
||||||
"debugger": true,
|
|
||||||
"errorList": true,
|
|
||||||
"importExtensionFiles": true,
|
"importExtensionFiles": true,
|
||||||
"addNewTypeScriptFile": true,
|
|
||||||
"experiments": [
|
"experiments": [
|
||||||
"python",
|
"python",
|
||||||
"alwaysGithubItemBlocks"
|
"alwaysGithubItemBlocks"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
CACHE MANIFEST
|
CACHE MANIFEST
|
||||||
|
|
||||||
CACHE:
|
CACHE:
|
||||||
|
/cdn/bluebird.min.js
|
||||||
/cdn/pxtsim.js
|
/cdn/pxtsim.js
|
||||||
/sim/common-sim.js
|
/sim/common-sim.js
|
||||||
/sim/sim.js
|
/sim/sim.js
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ body {
|
|||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script src="/cdn/bluebird.min.js"></script>
|
||||||
<script src="/cdn/pxtsim.js"></script>
|
<script src="/cdn/pxtsim.js"></script>
|
||||||
<script src="/sim/common-sim.js"></script>
|
<script src="/sim/common-sim.js"></script>
|
||||||
<script src="/sim/sim.js"></script>
|
<script src="/sim/sim.js"></script>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace pxsim {
|
|||||||
export class ColorSensorNode extends UartSensorNode {
|
export class ColorSensorNode extends UartSensorNode {
|
||||||
id = NodeType.ColorSensor;
|
id = NodeType.ColorSensor;
|
||||||
|
|
||||||
private color: number = 0;
|
private color: number = 50;
|
||||||
|
|
||||||
constructor(port: number) {
|
constructor(port: number) {
|
||||||
super(port);
|
super(port);
|
||||||
@@ -40,13 +40,5 @@ namespace pxsim {
|
|||||||
getValue() {
|
getValue() {
|
||||||
return this.color;
|
return this.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMode(mode: number) {
|
|
||||||
this.mode = mode;
|
|
||||||
if (this.mode == ColorSensorMode.RefRaw) this.color = 512;
|
|
||||||
else this.color = 50;
|
|
||||||
this.changed = true;
|
|
||||||
this.modeChanged = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ namespace pxsim {
|
|||||||
return Math.round(this.angle);
|
return Math.round(this.angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the secondary motor if any
|
// returns the slave motor if any
|
||||||
getSynchedMotor() {
|
getSynchedMotor() {
|
||||||
return this._synchedMotor;
|
return this._synchedMotor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ namespace pxsim.music {
|
|||||||
export function stopAllSounds() {
|
export function stopAllSounds() {
|
||||||
SoundMethods.stop()
|
SoundMethods.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
pxsim.music.setVolume = (volume: number): void => {
|
|
||||||
pxsim.getAudioState().volume = volume;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function volume() {
|
|
||||||
return pxsim.getAudioState().volume;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace pxsim.SoundMethods {
|
namespace pxsim.SoundMethods {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es5",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"newLine": "LF",
|
"newLine": "LF",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"lib": ["dom", "dom.iterable", "scripthost", "es2017"],
|
"lib": ["dom", "dom.iterable", "scripthost", "es6"],
|
||||||
"types": [],
|
"types": ["bluebird"],
|
||||||
"typeRoots": ["../node_modules/@types"]
|
"typeRoots": ["../node_modules/@types"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,8 +241,6 @@ namespace pxsim.visuals {
|
|||||||
view = new ColorGridControl(this.element, this.defs, state, port);
|
view = new ColorGridControl(this.element, this.defs, state, port);
|
||||||
} else if (state.getMode() == ColorSensorMode.Reflected) {
|
} else if (state.getMode() == ColorSensorMode.Reflected) {
|
||||||
view = new ColorWheelControl(this.element, this.defs, state, port);
|
view = new ColorWheelControl(this.element, this.defs, state, port);
|
||||||
} else if (state.getMode() == ColorSensorMode.RefRaw) {
|
|
||||||
view = new ColorWheelControl(this.element, this.defs, state, port);
|
|
||||||
} else if (state.getMode() == ColorSensorMode.Ambient) {
|
} else if (state.getMode() == ColorSensorMode.Ambient) {
|
||||||
view = new ColorWheelControl(this.element, this.defs, state, port);
|
view = new ColorWheelControl(this.element, this.defs, state, port);
|
||||||
}
|
}
|
||||||
@@ -383,7 +381,7 @@ namespace pxsim.visuals {
|
|||||||
this.screenCanvas = document.createElement("canvas");
|
this.screenCanvas = document.createElement("canvas");
|
||||||
this.screenCanvas.id = "board-screen-canvas";
|
this.screenCanvas.id = "board-screen-canvas";
|
||||||
this.screenCanvas.style.userSelect = "none";
|
this.screenCanvas.style.userSelect = "none";
|
||||||
(this.screenCanvas.style as any).msUserSelect = "none";
|
this.screenCanvas.style.msUserSelect = "none";
|
||||||
this.screenCanvas.style.webkitUserSelect = "none";
|
this.screenCanvas.style.webkitUserSelect = "none";
|
||||||
(this.screenCanvas.style as any).MozUserSelect = "none";
|
(this.screenCanvas.style as any).MozUserSelect = "none";
|
||||||
this.screenCanvas.style.position = "absolute";
|
this.screenCanvas.style.position = "absolute";
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ namespace pxsim.visuals {
|
|||||||
export class ColorGridControl extends ControlView<ColorSensorNode> {
|
export class ColorGridControl extends ControlView<ColorSensorNode> {
|
||||||
private group: SVGGElement;
|
private group: SVGGElement;
|
||||||
|
|
||||||
private static colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'brown', 'white', 'none'];
|
private static colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'grey', 'white'];
|
||||||
private static colorValue = [5, 4, 2, 3, 1, 7, 6, 0];
|
private static colorValue = [5, 4, 2, 3, 1, 7, 6];
|
||||||
|
|
||||||
private colorDivs: Element[] = [];
|
private colorDivs: Element[] = [];
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ namespace pxsim.visuals {
|
|||||||
this.group.setAttribute("transform", `translate(2, 2.5) scale(0.6)`)
|
this.group.setAttribute("transform", `translate(2, 2.5) scale(0.6)`)
|
||||||
|
|
||||||
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#000', '#6c2d00'];
|
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#000', '#6c2d00'];
|
||||||
const colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'brown'];
|
const colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'grey'];
|
||||||
|
|
||||||
let cy = -4;
|
let cy = -4;
|
||||||
for (let c = 0; c < colorIds.length; c++) {
|
for (let c = 0; c < colorIds.length; c++) {
|
||||||
@@ -33,19 +33,12 @@ namespace pxsim.visuals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const whiteCircleWrapper = pxsim.svg.child(this.group, "g", { 'id': 'white-cirlce-wrapper' });
|
const whiteCircleWrapper = pxsim.svg.child(this.group, "g", { 'id': 'white-cirlce-wrapper' });
|
||||||
const noneCircleWrapper = pxsim.svg.child(this.group, "g", { 'id': 'nothing-circle-wrapper' });
|
const circle = pxsim.svg.child(whiteCircleWrapper, "circle", { 'class': 'sim-color-grid-circle sim-color-grid-white', 'cx': 2.2, 'cy': '16', 'r': '2', 'style': `fill: #fff` });
|
||||||
const whiteCircle = pxsim.svg.child(whiteCircleWrapper, "circle", { 'class': 'sim-color-grid-circle sim-color-grid-white', 'cx': 2.2, 'cy': '16', 'r': '2', 'style': `fill: #fff` });
|
this.colorDivs.push(circle);
|
||||||
const noneCircle = pxsim.svg.child(noneCircleWrapper, "circle", { 'class': 'sim-color-grid-circle sim-color-grid-none', 'cx': 7.5, 'cy': '16', 'r': '2', 'style': `fill: #fff; fill-opacity: 0%;` });
|
|
||||||
this.colorDivs.push(whiteCircle);
|
|
||||||
this.colorDivs.push(noneCircle);
|
|
||||||
pxsim.svg.child(whiteCircleWrapper, "circle", { 'cx': 2.2, 'cy': '16', 'r': '2', 'style': `fill: none;stroke: #94989b;stroke-width: 0.1px` });
|
pxsim.svg.child(whiteCircleWrapper, "circle", { 'cx': 2.2, 'cy': '16', 'r': '2', 'style': `fill: none;stroke: #94989b;stroke-width: 0.1px` });
|
||||||
pxsim.svg.child(noneCircleWrapper, "circle", { 'cx': 7.5, 'cy': '16', 'r': '2', 'style': `fill: none; stroke: #94989b; stroke-width: 0.1px` });
|
|
||||||
pointerEvents.down.forEach(evid => whiteCircleWrapper.addEventListener(evid, ev => {
|
pointerEvents.down.forEach(evid => whiteCircleWrapper.addEventListener(evid, ev => {
|
||||||
this.setColor(6);
|
this.setColor(6);
|
||||||
}));
|
}));
|
||||||
pointerEvents.down.forEach(evid => noneCircleWrapper.addEventListener(evid, ev => {
|
|
||||||
this.setColor(0);
|
|
||||||
}));
|
|
||||||
return this.group;
|
return this.group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,8 @@ namespace pxsim.visuals {
|
|||||||
return 131;
|
return 131;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMaxValue(state: ColorSensorMode) {
|
private getMaxValue() {
|
||||||
return (state == ColorSensorMode.RefRaw ? 1023 : 100);
|
return 100;
|
||||||
}
|
|
||||||
|
|
||||||
private mapValue(x: number, inMin: number, inMax: number, outMin: number, outMax: number) {
|
|
||||||
return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState() {
|
updateState() {
|
||||||
@@ -39,12 +35,10 @@ namespace pxsim.visuals {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const node = this.state;
|
const node = this.state;
|
||||||
const value = node.getValue();
|
const percentage = node.getValue();
|
||||||
let inverseValue = this.getMaxValue(node.getMode()) - value;
|
const inversePercentage = this.getMaxValue() - percentage;
|
||||||
if (node.getMode() == ColorSensorMode.RefRaw) inverseValue = this.mapValue(inverseValue, 0, 1023, 0, 100);
|
svg.setGradientValue(this.colorGradient, inversePercentage + "%");
|
||||||
svg.setGradientValue(this.colorGradient, inverseValue + "%");
|
this.reporter.textContent = `${parseFloat((percentage).toString()).toFixed(0)}%`;
|
||||||
this.reporter.textContent = `${parseFloat((value).toString()).toFixed(0)}`;
|
|
||||||
if (node.getMode() != ColorSensorMode.RefRaw) this.reporter.textContent += `%`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateColorLevel(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
|
updateColorLevel(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
|
||||||
@@ -53,7 +47,7 @@ namespace pxsim.visuals {
|
|||||||
const height = bBox.height;
|
const height = bBox.height;
|
||||||
let t = Math.max(0, Math.min(1, (height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height));
|
let t = Math.max(0, Math.min(1, (height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height));
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
state.setColor(t * this.getMaxValue(state.getMode()));
|
state.setColor(t * this.getMaxValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
|
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ namespace pxsim.visuals {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case ColorSensorMode.Colors: this.updateSensorLightVisual('#0062DD'); return; // blue
|
case ColorSensorMode.Colors: this.updateSensorLightVisual('#0062DD'); return; // blue
|
||||||
case ColorSensorMode.Reflected: this.updateSensorLightVisual('#F86262'); return; // red
|
case ColorSensorMode.Reflected: this.updateSensorLightVisual('#F86262'); return; // red
|
||||||
case ColorSensorMode.RefRaw: this.updateSensorLightVisual('#F86262'); return; // red
|
|
||||||
case ColorSensorMode.Ambient: this.updateSensorLightVisual('#67C3E2'); return; // light blue
|
case ColorSensorMode.Ambient: this.updateSensorLightVisual('#67C3E2'); return; // light blue
|
||||||
}
|
}
|
||||||
this.updateSensorLightVisual('#ffffff');
|
this.updateSensorLightVisual('#ffffff');
|
||||||
|
|||||||
@@ -92,11 +92,6 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* active toggle */
|
|
||||||
.ui.item.editor-menuitem .active~.item.toggle {
|
|
||||||
background: @editorToggleColor!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menubar .ui.item.editor-menuitem .item.toggle {
|
.menubar .ui.item.editor-menuitem .item.toggle {
|
||||||
height: 45px;
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
specify theme name below
|
specify theme name below
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@placeholder: 'default';
|
|
||||||
|
|
||||||
/* Global */
|
/* Global */
|
||||||
@site : 'pxt';
|
@site : 'pxt';
|
||||||
@reset : 'default';
|
@reset : 'default';
|
||||||
@@ -89,7 +87,7 @@
|
|||||||
Import Theme
|
Import Theme
|
||||||
*******************************/
|
*******************************/
|
||||||
|
|
||||||
@import (multiple) "theme.less";
|
@import "theme.less";
|
||||||
|
|
||||||
@fontPath : 'fonts';
|
@fontPath : 'fonts';
|
||||||
|
|
||||||
|
|||||||