Skip to content

Commit

Permalink
Rebuild UI with react + jotai
Browse files Browse the repository at this point in the history
  • Loading branch information
noahm committed Mar 29, 2024
1 parent 549b91b commit ea0bd31
Show file tree
Hide file tree
Showing 16 changed files with 262 additions and 133 deletions.
14 changes: 14 additions & 0 deletions .yarn/patches/baconjs-npm-3.0.17-1a95474784.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/package.json b/package.json
index 6df2387cbfaead99f6fa36a8d6a5b11900a05bb4..3b5586d74fb8f7312c64406dda8a8ef0ced929ac 100644
--- a/package.json
+++ b/package.json
@@ -91,9 +91,5 @@
},
"main": "dist/Bacon.js",
"module": "./dist/Bacon.mjs",
- "exports": {
- "import": "./dist/Bacon.mjs",
- "require": "./dist/Bacon.js"
- },
"types": "types/bacon.d.ts"
}
10 changes: 2 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@
<link rel="icon" href="https://stepmaniax.com/img/smx_pad.png" />
<title>SMX Web Config Tool</title>
<link rel="stylesheet" href="./ui/styles.css" />
<script src="./ui/script.ts" defer type="module"></script>
<script src="./ui/index.tsx" defer type="module"></script>
</head>
<body>
<h1>SMX WebUSB</h1>

<button id="btn">Pick device...</button>
<button id="btnp1">Fetch P1 Config...</button>

<pre id="status"></pre>
<pre id="output"></pre>
<div id="root"></div>
</body>
</html>
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"license": "MIT",
"dependencies": {
"@nmann/struct-buffer": "^5.3.0",
"baconjs": "patch:baconjs@npm%3A3.0.17#~/.yarn/patches/baconjs-npm-3.0.17-1a95474784.patch",
"jotai": "^2.7.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
7 changes: 3 additions & 4 deletions sdk/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StructBuffer, bits, uint8_t, uint16_t } from "@nmann/struct-buffer";
import type { EachPanel } from "./inputs";
import { StructBuffer, bits, uint16_t, uint8_t } from "@nmann/struct-buffer";
import type { EachPanel } from "./inputs.ts";

type DecodedStruct<SB extends StructBuffer> = ReturnType<SB["decode"]>;

Expand Down Expand Up @@ -252,9 +252,8 @@ class Panel {
/**
* Convert a panels 4 sensors back into a 4-bit LSB byte
* TODO: Determine if this ordering is actually correct
* @returns {Number}
*/
toByte() {
toByte(): number {
return (this.up ? 1 << 3 : 0) + (this.right ? 1 << 2 : 0) + (this.down ? 1 << 1 : 0) + (this.left ? 1 : 0);
}
}
Expand Down
12 changes: 12 additions & 0 deletions sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { API_COMMAND } from "./api";
import { process_packets, send_data } from "./packet";

export async function getDeviceInfo(dev: HIDDevice) {
await send_data(dev, [API_COMMAND.GET_DEVICE_INFO], true);
return process_packets(dev, true);
}

export async function getStageConfig(dev: HIDDevice) {
await send_data(dev, [API_COMMAND.GET_CONFIG_V5], true);
return process_packets(dev, true);
}
2 changes: 1 addition & 1 deletion sdk/packet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pad_packet } from "./utils.js";
import { pad_packet } from "./utils.ts";

/**
* Gets the next report from the device matching a given reportId,
Expand Down
2 changes: 1 addition & 1 deletion sdk/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MAX_PACKET_SIZE } from "./packet.js";
import { MAX_PACKET_SIZE } from "./packet.ts";

/**
* Pad incomming packet to `MAX_PACKET_SIZE` and convert to Uint8Array as a
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"jsx": "react-jsx",

/* Linting */
"strict": true,
"strict": true
},
"include": ["ui/", "sdk/"],
"references": [{ "path": "./tsconfig.node.json" }]
Expand Down
17 changes: 17 additions & 0 deletions ui/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as Bacon from "baconjs";
import { Provider } from "jotai";
import { createRoot } from "react-dom/client";

import { uiState } from "./state.ts";
import { UI } from "./ui.tsx";

Bacon.fromEvent(document, "DOMContentLoaded").onValue(async () => {
const rootEl = document.getElementById("root");
if (!rootEl) return;
const reactRoot = createRoot(rootEl);
reactRoot.render(
<Provider store={uiState}>
<UI />
</Provider>,
);
});
78 changes: 78 additions & 0 deletions ui/pad-coms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { getDeviceInfo, getStageConfig } from "../sdk";
import { SMX_USB_PRODUCT_ID, SMX_USB_VENDOR_ID } from "../sdk/api";
import { SMXConfig } from "../sdk/commands/config";
import { SMXDeviceInfo } from "../sdk/commands/data_info";
import { devices$, nextStatusTextLine$, statusText$, uiState } from "./state";

// function formatDataForDisplay(data: DataView) {
// const len = data.byteLength;

// // Read raw report into groups of 8 bytes.
// let str = "";
// for (let i = 0; i !== len; ++i) {
// if (i !== 0 && i % 8 === 0) str += "\n";

// // biome-ignore lint/style/useTemplate: <explanation>
// str += data.getUint8(i).toString(2).padStart(8, "0") + " ";
// }

// // Read raw report into groups of 8 bytes of hex
// str += "\n\n";
// for (let i = 0; i !== len; ++i) {
// if (i !== 0 && i % 8 === 0) str += "\n";

// // biome-ignore lint/style/useTemplate: <explanation>
// str += data.getUint8(i).toString(16).padStart(2, "0") + " ";
// }

// return `report:\n${str}`;
// }

export async function promptSelectDevice() {
const devices = await navigator.hid.requestDevice({
filters: [{ vendorId: SMX_USB_VENDOR_ID, productId: SMX_USB_PRODUCT_ID }],
});

if (!devices.length || !devices[0]) {
uiState.set(statusText$, "no device selected");
return;
}

await open_smx_device(devices[0]);
}

export async function open_smx_device(dev: HIDDevice) {
if (!dev.opened) {
await dev.open();
}
// Get the device info an find the player number
const packet = await getDeviceInfo(dev);
const device_info = new SMXDeviceInfo(packet);
uiState.set(devices$, (devices) => {
return {
...devices,
[device_info.player]: dev,
};
});
uiState.set(
nextStatusTextLine$,
`status: device opened: ${dev.productName}:P${device_info.player}:${device_info.serial}`,
);
}

export async function requestConfig(dev: HIDDevice) {
const response = await getStageConfig(dev);

console.log("Raw Bytes: ", response);
const smxconfig = new SMXConfig(response);
console.log("Decoded Config:", smxconfig.config);

// Right now I just want to confirm that decoding and re-encoding gives back the same data
const encoded_config = smxconfig.encode();
if (encoded_config) {
console.log("Encoded Config:", smxconfig.config);
const buf = new Uint8Array(encoded_config.buffer);
console.log("Encoded Bytes: ", buf);
console.log("Same Thing:", response.slice(2, -1).toString() === buf.toString());
}
}
115 changes: 0 additions & 115 deletions ui/script.ts

This file was deleted.

4 changes: 2 additions & 2 deletions ui/simple-pad.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { StageInputs } from "../sdk/commands/inputs";
import { HID_REPORT_INPUT_STATE } from "../sdk/packet";
import { StageInputs } from "../sdk/commands/inputs.ts";
import { HID_REPORT_INPUT_STATE } from "../sdk/packet.ts";

interface Props {
dev: HIDDevice;
Expand Down
34 changes: 34 additions & 0 deletions ui/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { atom, createStore } from "jotai";

export const browserSupported = "hid" in navigator;

/** actually holds the state of each atom */
export const uiState = createStore();

/** backing atom of all known devices */
export const devices$ = atom<Record<number, HIDDevice | undefined>>({});

/** current p1 pad, derived from devices$ above */
export const p1Dev$ = atom<HIDDevice | undefined, [HIDDevice | undefined], void>(
(get) => get(devices$)[1],
(_, set, dev: HIDDevice | undefined) => {
set(devices$, (prev) => ({ ...prev, [1]: dev }));
},
);

/** current p2 pad, derived from devices$ above */
export const p2Dev$ = atom<HIDDevice | undefined, [HIDDevice | undefined], void>(
(get) => get(devices$)[2],
(_, set, dev: HIDDevice | undefined) => {
set(devices$, (prev) => ({ ...prev, [2]: dev }));
},
);

export const statusText$ = atom(
browserSupported
? "no device connected"
: "HID API not supported, use Google Chrome or MS Edge browsers for this tool",
);

/** write-only atom. write to this to append a line to statusText */
export const nextStatusTextLine$ = atom(null, (_, set, line: string) => set(statusText$, (prev) => prev + line));
7 changes: 6 additions & 1 deletion ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ input {
box-shadow: 2px 2px 1px rgba(0, 0, 0, 0.4);
}

button:active {
button:disabled {
background: #ececec;
cursor: not-allowed;
}

button:active:not(:disabled) {
transform: translateY(5px);
box-shadow: 0px 0px 0px rgba(0, 0, 0, 1);
}
Expand Down
Loading

0 comments on commit ea0bd31

Please sign in to comment.