diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 000000000..a9ada5e5e --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,27 @@ +name: Node CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.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 ci + working-directory: ./typescript + run: npm ci + - name: build types + working-directory: ./typescript + run: npm run build:apps + - name: build all TS apps + working-directory: ./typescript + run: npm run build:types diff --git a/apps.json b/apps.json index fd2e14e60..3466ab0fb 100644 --- a/apps.json +++ b/apps.json @@ -4788,7 +4788,7 @@ "tags": "widget", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"widChargingStatus.wid.js","url":"widget.js"} + {"name":"widChargingStatus.wid.js","url":"./widget.js"} ] }, { diff --git a/apps/widChargingStatus/widget.js b/apps/widChargingStatus/widget.js index 90f9199fa..68a7c0aca 100644 --- a/apps/widChargingStatus/widget.js +++ b/apps/widChargingStatus/widget.js @@ -1,31 +1,33 @@ -(() => { - const icon = require("heatshrink").decompress(atob("ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA")); - const iconWidth = 18; - - function draw() { - g.reset(); - if (Bangle.isCharging()) { - g.setColor("#FD0"); - g.drawImage(icon, this.x + 1, this.y + 1, { - scale: 0.6875 - }); - } - } - - WIDGETS.chargingStatus = { - area: 'tr', - width: Bangle.isCharging() ? iconWidth : 0, - draw: draw, - }; - - Bangle.on('charging', (charging) => { - if (charging) { - Bangle.buzz(); - WIDGETS.chargingStatus.width = iconWidth; - } else { - WIDGETS.chargingStatus.width = 0; - } - Bangle.drawWidgets(); // re-layout widgets - g.flip(); - }); -})(); \ No newline at end of file +"use strict"; +(() => { + const icon = require('heatshrink').decompress(atob('ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA')); + const iconWidth = 18; + function draw() { + g.reset(); + if (Bangle.isCharging()) { + g.setColor('#FD0'); + g.drawImage(icon, this.x + 1, this.y + 1, { + scale: 0.6875, + }); + } + } + WIDGETS.chargingStatus = { + area: 'tr', + width: Bangle.isCharging() ? iconWidth : 0, + draw, + }; + Bangle.on('charging', (charging) => { + const widget = WIDGETS.chargingStatus; + if (widget) { + if (charging) { + Bangle.buzz(); + widget.width = iconWidth; + } + else { + widget.width = 0; + } + Bangle.drawWidgets(); // re-layout widgets + g.flip(); + } + }); +})(); diff --git a/apps/widChargingStatus/widget.ts b/apps/widChargingStatus/widget.ts new file mode 100644 index 000000000..712bf4f97 --- /dev/null +++ b/apps/widChargingStatus/widget.ts @@ -0,0 +1,38 @@ +(() => { + const icon = require('heatshrink').decompress( + atob( + 'ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA' + ) + ); + const iconWidth = 18; + + function draw(this: { x: number; y: number }) { + g.reset(); + if (Bangle.isCharging()) { + g.setColor('#FD0'); + g.drawImage(icon, this.x + 1, this.y + 1, { + scale: 0.6875, + }); + } + } + + WIDGETS.chargingStatus = { + area: 'tr', + width: Bangle.isCharging() ? iconWidth : 0, + draw, + }; + + Bangle.on('charging', (charging) => { + const widget = WIDGETS.chargingStatus; + if (widget) { + if (charging) { + Bangle.buzz(); + widget.width = iconWidth; + } else { + widget.width = 0; + } + Bangle.drawWidgets(); // re-layout widgets + g.flip(); + } + }); +})(); diff --git a/typescript/.gitignore b/typescript/.gitignore new file mode 100644 index 000000000..630f61ee5 --- /dev/null +++ b/typescript/.gitignore @@ -0,0 +1,2 @@ +./node_modules +!package-lock.json diff --git a/typescript/README.md b/typescript/README.md new file mode 100644 index 000000000..d1f60fd0b --- /dev/null +++ b/typescript/README.md @@ -0,0 +1,29 @@ +# BangleTS + +A generic project setup for compiling apps from Typescript to Bangle.js ready, readable Javascript. +It includes types for _some_ of the modules and globals that are exposed for apps to use. +The goal is to have types for everything, but that will take some time. Feel free to help out by contributing! + +## Using the types + +All currently typed modules can be found in `/typescript/types.globals.d.ts`. +The typing is an ongoing process. If anything is still missing, you can add it! It will automatically be available in your TS files. + +## Compilation + +Install [npm](https://www.npmjs.com/get-npm) if you haven't already. +Make sure you are using version ^8 by running `npm -v`. If the version is incorrect, run `npm i -g npm@^8`. + +After having installed npm for your platform, open a terminal, and navigate into the `/typescript` folder. Then run: + +``` +npm ci +``` + +to install the project's build tools, and: + +``` +npm run build:apps +``` + +To build all Typescript apps. The last command will generate the `app.js` files containing the transpiled code for the BangleJS. diff --git a/typescript/package-lock.json b/typescript/package-lock.json new file mode 100644 index 000000000..52be5f98a --- /dev/null +++ b/typescript/package-lock.json @@ -0,0 +1,36 @@ +{ + "name": "Bangle.ts", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "Bangle.ts", + "version": "0.0.1", + "devDependencies": { + "typescript": "4.5.2" + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true + } + } +} diff --git a/typescript/package.json b/typescript/package.json new file mode 100644 index 000000000..4bfb88d27 --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,13 @@ +{ + "name": "Bangle.ts", + "description": "Bangle.js Typescript Project Setup and Types", + "author": "Sebastian Di Luzio (https://diluz.io)", + "version": "0.0.1", + "devDependencies": { + "typescript": "4.5.2" + }, + "scripts": { + "build:apps": "tsc", + "build:types": "tsc ./types/globals.d.ts" + } +} diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json new file mode 100644 index 000000000..40537c680 --- /dev/null +++ b/typescript/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "es2015", + "noImplicitAny": true, + "target": "es2015", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true + }, + "include": ["../apps/**/*", "./**/*"], + // these apps have been excluded because they were built before this configuration was created and are using their own + "exclude": ["../apps/banglerun", "../apps/hebrew_calendar"] +} diff --git a/typescript/types/globals.d.ts b/typescript/types/globals.d.ts new file mode 100644 index 000000000..46d1e3b0a --- /dev/null +++ b/typescript/types/globals.d.ts @@ -0,0 +1,183 @@ +// TODO all of these globals (copied from eslintrc need to be typed at some point) +/* { + // Methods and Fields at https://banglejs.com/reference + "Array": "readonly", + "ArrayBuffer": "readonly", + "ArrayBufferView": "readonly", + "Bangle": "readonly", + "BluetoothDevice": "readonly", + "BluetoothRemoteGATTCharacteristic": "readonly", + "BluetoothRemoteGATTServer": "readonly", + "BluetoothRemoteGATTService": "readonly", + "Boolean": "readonly", + "console": "readonly", + "DataView": "readonly", + "Date": "readonly", + "E": "readonly", + "Error": "readonly", + "Flash": "readonly", + "Float32Array": "readonly", + "Float64Array": "readonly", + "fs": "readonly", + "Function": "readonly", + "Graphics": "readonly", // partly done + "heatshrink": "readonly", + "I2C": "readonly", + "Int16Array": "readonly", + "Int32Array": "readonly", + "Int8Array": "readonly", + "InternalError": "readonly", + "JSON": "readonly", + "Math": "readonly", + "Modules": "readonly", + "NRF": "readonly", + "Number": "readonly", + "Object": "readonly", + "OneWire": "readonly", + "Pin": "readonly", + "process": "readonly", + "Promise": "readonly", + "ReferenceError": "readonly", + "RegExp": "readonly", + "Serial": "readonly", + "SPI": "readonly", + "Storage": "readonly", + "StorageFile": "readonly", + "String": "readonly", + "SyntaxError": "readonly", + "tensorflow": "readonly", + "TFMicroInterpreter": "readonly", + "TypeError": "readonly", + "Uint16Array": "readonly", + "Uint24Array": "readonly", + "Uint32Array": "readonly", + "Uint8Array": "readonly", + "Uint8ClampedArray": "readonly", + "Waveform": "readonly", + // Methods and Fields at https://banglejs.com/reference + "analogRead": "readonly", + "analogWrite": "readonly", + "arguments": "readonly", + "atob": "readonly", + "Bluetooth": "readonly", + "BTN": "readonly", + "BTN1": "readonly", + "BTN2": "readonly", + "BTN3": "readonly", + "BTN4": "readonly", + "BTN5": "readonly", + "btoa": "readonly", + "changeInterval": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "clearWatch": "readonly", + "decodeURIComponent": "readonly", + "digitalPulse": "readonly", + "digitalRead": "readonly", + "digitalWrite": "readonly", + "dump": "readonly", + "echo": "readonly", + "edit": "readonly", + "encodeURIComponent": "readonly", + "eval": "readonly", + "getPinMode": "readonly", + "getSerial": "readonly", + "getTime": "readonly", + "global": "readonly", + "HIGH": "readonly", + "I2C1": "readonly", + "Infinity": "readonly", + "isFinite": "readonly", + "isNaN": "readonly", + "LED": "readonly", + "LED1": "readonly", + "LED2": "readonly", + "load": "readonly", + "LoopbackA": "readonly", + "LoopbackB": "readonly", + "LOW": "readonly", + "NaN": "readonly", + "parseFloat": "readonly", + "parseInt": "readonly", + "peek16": "readonly", + "peek32": "readonly", + "peek8": "readonly", + "pinMode": "readonly", + "poke16": "readonly", + "poke32": "readonly", + "poke8": "readonly", + "print": "readonly", + "require": "readonly", + "reset": "readonly", + "save": "readonly", + "Serial1": "readonly", + "setBusyIndicator": "readonly", + "setInterval": "readonly", + "setSleepIndicator": "readonly", + "setTime": "readonly", + "setTimeout": "readonly", + "setWatch": "readonly", + "shiftOut": "readonly", + "SPI1": "readonly", + "Terminal": "readonly", + "trace": "readonly", + "VIBRATE": "readonly", + // Aliases and not defined at https://banglejs.com/reference + "g": "readonly", // done + "WIDGETS": "readonly" // done + } + */ + +// ambient JS definitions + +declare const require: ((module: 'heatshrink') => { + decompress: (compressedString: string) => string; +}) & // TODO add more + ((module: 'otherString') => {}); + +// ambient bangle.js definitions + +declare const Bangle: { + // functions + buzz: () => void; + drawWidgets: () => void; + isCharging: () => boolean; + // events + on(event: 'charging', listener: (charging: boolean) => void): void; + // TODO add more +}; + +declare type Image = { + width: number; + height: number; + bpp?: number; + buffer: ArrayBuffer | string; + transparent?: number; + palette?: Uint16Array; +}; + +declare type GraphicsApi = { + reset: () => void; + flip: () => void; + setColor: (color: string) => void; // TODO we can most likely type color more usefully than this + drawImage: ( + image: string | Image | ArrayBuffer, + xOffset: number, + yOffset: number, + options?: { + rotate?: number; + scale?: number; + } + ) => void; + // TODO add more +}; + +declare const Graphics: GraphicsApi; +declare const g: GraphicsApi; + +declare type Widget = { + area: 'tr' | 'tl'; + width: number; + draw: (this: { x: number; y: number }) => void; +}; +declare const WIDGETS: { [key: string]: Widget };