Skip to content

Commit

Permalink
refactor!: migrate to ESM
Browse files Browse the repository at this point in the history
* refactor: convert all source files to ESM
* build: upgrade deps to latest; migrate to newer eslint flat config; lint src
* test: migrate tests to ESM
* ci: migrate to new action versions; add node 21 to test matrix
* build(examples/web): upgrade deps to latest; migrate to newer eslint flat config
* build: use browser-compatible implementation of EventEmitter
* build(examples/web): do not import serial/ws connections for browser builds
* test: reinstate test for optional dependency `canvas`
  • Loading branch information
foxxyz authored Jun 9, 2024
1 parent e41e5d9 commit ee15f1a
Show file tree
Hide file tree
Showing 30 changed files with 1,751 additions and 1,024 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, latest]
node-version: [18.x, 20.x, 21.x, latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install system dependencies
Expand Down
8 changes: 2 additions & 6 deletions __mocks__/serialport.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const EventEmitter = require('events')
import EventEmitter from 'node:events'

const WS_UPGRADE_HEADER = `GET /index.html
HTTP/1.1
Expand All @@ -15,7 +15,7 @@ Sec-WebSocket-Accept: PLOTDYCXHOTMeouth==
`

// Stand-in for a Loupedeck serial device
class MockLoupedeckSerialPort extends EventEmitter {
export class SerialPort extends EventEmitter {
static list() {
return [
{
Expand Down Expand Up @@ -58,7 +58,3 @@ class MockLoupedeckSerialPort extends EventEmitter {
if (buff.toString() === WS_UPGRADE_HEADER) this.emit('data', Buffer.from(WS_UPGRADE_RESPONSE))
}
}

module.exports = {
SerialPort: MockLoupedeckSerialPort
}
6 changes: 2 additions & 4 deletions __mocks__/ws.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const EventEmitter = require('events')
import EventEmitter from 'node:events'

// Stand-in for a real WebSocket
class MockSocket extends EventEmitter {
export default class MockSocket extends EventEmitter {
constructor(url) {
super()
this.url = url
Expand Down Expand Up @@ -29,5 +29,3 @@ class MockSocket extends EventEmitter {
this.emit('close', 1006)
}
}

module.exports = MockSocket
10 changes: 4 additions & 6 deletions connections/serial.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const EventEmitter = require('events')
const { SerialPort } = require('serialport')
import { Emitter as EventEmitter } from 'strict-event-emitter'
import { SerialPort } from 'serialport'

const MagicByteLengthParser = require('../parser')
import { MagicByteLengthParser } from '../parser.js'

const WS_UPGRADE_HEADER = `GET /index.html
HTTP/1.1
Expand All @@ -21,7 +21,7 @@ const MANUFACTURERS = [
'Razer'
]

class LoupedeckSerialConnection extends EventEmitter {
export default class LoupedeckSerialConnection extends EventEmitter {
constructor({ path } = {}) {
super()
this.path = path
Expand Down Expand Up @@ -100,5 +100,3 @@ class LoupedeckSerialConnection extends EventEmitter {
this.connection.write(buff)
}
}

module.exports = LoupedeckSerialConnection
6 changes: 2 additions & 4 deletions connections/web-serial.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const EventEmitter = require('events')
import { Emitter as EventEmitter } from 'strict-event-emitter'

const WS_UPGRADE_HEADER = `GET /index.html
HTTP/1.1
Expand Down Expand Up @@ -52,7 +52,7 @@ async function *read(port) {
}
}

class LoupedeckWebSerialConnection extends EventEmitter {
export default class LoupedeckWebSerialConnection extends EventEmitter {
constructor({ port } = {}) {
super()
this.port = port
Expand Down Expand Up @@ -144,5 +144,3 @@ class LoupedeckWebSerialConnection extends EventEmitter {
return this.writer.write(buff)
}
}

module.exports = LoupedeckWebSerialConnection
14 changes: 5 additions & 9 deletions connections/ws.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
const EventEmitter = require('events')
const { networkInterfaces } = require('os')
const WebSocket = require('ws')
import { Emitter as EventEmitter } from 'strict-event-emitter'
import { networkInterfaces } from 'node:os'
import WebSocket from 'ws'

const {
CONNECTION_TIMEOUT,
} = require('../constants')
import { CONNECTION_TIMEOUT } from '../constants.js'

const DISCONNECT_CODES = {
NORMAL: 1000,
TIMEOUT: 1006,
}

class LoupedeckWSConnection extends EventEmitter {
export default class LoupedeckWSConnection extends EventEmitter {
constructor({ host } = {}) {
super()
this.host = host
Expand Down Expand Up @@ -80,5 +78,3 @@ class LoupedeckWSConnection extends EventEmitter {
this.connection.send(buff)
}
}

module.exports = LoupedeckWSConnection
21 changes: 6 additions & 15 deletions constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Various constants used by the Loupedeck firmware

const BUTTONS = {
export const BUTTONS = {
0x00: 'knobCT',
0x01: 'knobTL',
0x02: 'knobCL',
Expand Down Expand Up @@ -47,9 +47,9 @@ const BUTTONS = {
}

// How long without ticks until a connection is considered "timed out"
const CONNECTION_TIMEOUT = 3000
export const CONNECTION_TIMEOUT = 3000

const COMMANDS = {
export const COMMANDS = {
BUTTON_PRESS: 0x00,
KNOB_ROTATE: 0x01,
SET_COLOR: 0x02,
Expand All @@ -68,9 +68,9 @@ const COMMANDS = {
}

// How long until trying to reconnect after a disconnect
const DEFAULT_RECONNECT_INTERVAL = 3000
export const DEFAULT_RECONNECT_INTERVAL = 3000

const HAPTIC = {
export const HAPTIC = {
SHORT: 0x01,
MEDIUM: 0x0a,
LONG: 0x0f,
Expand Down Expand Up @@ -102,13 +102,4 @@ const HAPTIC = {
}

// Maximum brightness value
const MAX_BRIGHTNESS = 10

module.exports = {
MAX_BRIGHTNESS,
BUTTONS,
COMMANDS,
CONNECTION_TIMEOUT,
DEFAULT_RECONNECT_INTERVAL,
HAPTIC,
}
export const MAX_BRIGHTNESS = 10
55 changes: 26 additions & 29 deletions device.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
const EventEmitter = require('events')
const rgba = require('color-rgba')
import { Emitter as EventEmitter } from 'strict-event-emitter'
import rgba from 'color-rgba'

let SerialConnection, WSConnection
if (typeof navigator !== 'undefined' && navigator.serial) {
SerialConnection = require('./connections/web-serial')
// Only import when in a browser environment
if (typeof navigator !== 'undefined' && navigator.serial || import.meta.env?.PROD) {
SerialConnection = (await import('./connections/web-serial.js')).default
} else {
SerialConnection = require('./connections/serial')
WSConnection = require('./connections/ws')
SerialConnection = (await import('./connections/serial.js')).default
WSConnection = (await import('./connections/ws.js')).default
}

const {
let canvasModule
try {
canvasModule = await import('canvas')
// eslint-disable-next-line
} catch (e) {
// No canvas is ok, do check in `drawCanvas`
}

import {
BUTTONS,
COMMANDS,
DEFAULT_RECONNECT_INTERVAL,
HAPTIC,
MAX_BRIGHTNESS,
} = require('./constants')
} from './constants.js'

const { rgba2rgb565 } = require('./util')
import { rgba2rgb565 } from './util.js'

class LoupedeckDevice extends EventEmitter {
export class LoupedeckDevice extends EventEmitter {
static async list({ ignoreSerial = false, ignoreWebsocket = false } = {}) {
const ps = []

Expand Down Expand Up @@ -123,14 +132,11 @@ class LoupedeckDevice extends EventEmitter {
if (!displayInfo) throw new Error(`Display '${id}' is not available on this device!`)
if (!width) width = displayInfo.width
if (!height) height = displayInfo.height
let createCanvas
try {
createCanvas = require('canvas').createCanvas
} catch (e) {
if (!canvasModule || !canvasModule.createCanvas) {
throw new Error('Using callbacks requires the `canvas` library to be installed. Install it using `npm install canvas`.')
}

const canvas = createCanvas(width, height)
const canvas = canvasModule.createCanvas(width, height)
const ctx = canvas.getContext('2d', { pixelFormat: 'RGB16_565' }) // Loupedeck uses 16-bit (5-6-5) LE RGB colors
cb(ctx, width, height)
let buffer
Expand Down Expand Up @@ -259,7 +265,7 @@ class LoupedeckDevice extends EventEmitter {
}
}

class LoupedeckLive extends LoupedeckDevice {
export class LoupedeckLive extends LoupedeckDevice {
static productId = 0x0004
static vendorId = 0x2ec2
buttons = [0, 1, 2, 3, 4, 5, 6, 7]
Expand Down Expand Up @@ -289,7 +295,7 @@ class LoupedeckLive extends LoupedeckDevice {
}
}

class LoupedeckCT extends LoupedeckLive {
export class LoupedeckCT extends LoupedeckLive {
static productId = 0x0003
buttons = [0, 1, 2, 3, 4, 5, 6, 7, 'home', 'enter', 'undo', 'save', 'keyboard', 'fnL', 'a', 'b', 'c', 'd', 'fnR', 'e']
displays = {
Expand All @@ -306,7 +312,7 @@ class LoupedeckCT extends LoupedeckLive {
}
}

class LoupedeckLiveS extends LoupedeckDevice {
export class LoupedeckLiveS extends LoupedeckDevice {
static productId = 0x0006
static vendorId = 0x2ec2
buttons = [0, 1, 2, 3]
Expand All @@ -331,13 +337,13 @@ class LoupedeckLiveS extends LoupedeckDevice {
}
}

class RazerStreamController extends LoupedeckLive {
export class RazerStreamController extends LoupedeckLive {
static productId = 0x0d06
static vendorId = 0x1532
type = 'Razer Stream Controller'
}

class RazerStreamControllerX extends LoupedeckDevice {
export class RazerStreamControllerX extends LoupedeckDevice {
static productId = 0x0d09
static vendorId = 0x1532
type = 'Razer Stream Controller X'
Expand Down Expand Up @@ -377,12 +383,3 @@ class RazerStreamControllerX extends LoupedeckDevice {
throw new Error('Vibration not available on this device!')
}
}

module.exports = {
LoupedeckCT,
LoupedeckDevice,
LoupedeckLive,
LoupedeckLiveS,
RazerStreamController,
RazerStreamControllerX,
}
12 changes: 4 additions & 8 deletions discovery.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
const ALL_DEVICES = require('./device')
import * as ALL_DEVICES from './device.js'

async function discover() {
export async function discover(args) {
const devices = await ALL_DEVICES.LoupedeckDevice.list()
if (devices.length === 0) throw new Error('No devices found')
const { productId, ...args } = devices[0]
const { productId, ...connectArgs } = devices[0]
const deviceType = Object.values(ALL_DEVICES).find(dev => dev.productId === productId)
if (!deviceType) throw new Error(`Device with product ID ${productId} not yet supported! Please file an issue at https://github.com/foxxyz/loupedeck/issues`)
const device = new deviceType(args)
const device = new deviceType({ ...args, ...connectArgs })
return device
}

module.exports = {
discover
}
15 changes: 15 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import jsConfig from '@appliedminds/eslint-config'
import jest from 'eslint-plugin-jest'
import globals from 'globals'

export default [
...jsConfig,
jest.configs['flat/recommended'],
{
languageOptions: {
globals: {
...globals.browser
}
}
}
]
10 changes: 5 additions & 5 deletions examples/slide-puzzle/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class SlidePuzzle {
async init() {
this.sourceImage = await loadImage(this.sourceImageFile)
const tiles = []
for(let column = 0; column < this.columns; column++) {
for(let row = 0; row < this.rows; row++) {
for (let column = 0; column < this.columns; column++) {
for (let row = 0; row < this.rows; row++) {
if (row === this.rows - 1 && column === this.columns - 1) continue
tiles.push(new Tile({
sourceImage: this.sourceImage,
Expand Down Expand Up @@ -82,8 +82,8 @@ class SlidePuzzle {
}
shuffle() {
const shuffled = []
for(let column = 0; column < this.columns; column++) {
for(let row = 0; row < this.rows; row++) {
for (let column = 0; column < this.columns; column++) {
for (let row = 0; row < this.rows; row++) {
if (row === this.rows - 1 && column === this.columns - 1) continue
const randomIndex = Math.floor(Math.random() * this.tiles.length)
const tile = this.tiles[randomIndex]
Expand Down Expand Up @@ -163,7 +163,7 @@ await game.init()
game.onStart = () => {
clearTimeout(winAnimation)
device.drawScreen('center', ctx => {
for(const tile of game.tiles) {
for (const tile of game.tiles) {
ctx.drawImage(tile.canvas, tile.x, tile.y)
}
})
Expand Down
7 changes: 7 additions & 0 deletions examples/web/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import amJs from '@appliedminds/eslint-config'
import amVue from '@appliedminds/eslint-config/vue.js'

export default [
...amJs,
...amVue,
]
Loading

0 comments on commit ee15f1a

Please sign in to comment.