@kelnishi/satmouse-client 0.9.12 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-I47ZOX3M.js +64 -0
- package/dist/chunk-I47ZOX3M.js.map +1 -0
- package/dist/chunk-LDSDVYCV.cjs +69 -0
- package/dist/chunk-LDSDVYCV.cjs.map +1 -0
- package/dist/{connection-CH2YsPn1.d.cts → connection-5KQFvHoJ.d.cts} +8 -1
- package/dist/{connection-CH2YsPn1.d.ts → connection-5KQFvHoJ.d.ts} +8 -1
- package/dist/core/index.cjs +18 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +18 -2
- package/dist/core/index.js.map +1 -1
- package/dist/elements/index.cjs +301 -0
- package/dist/elements/index.cjs.map +1 -0
- package/dist/elements/index.d.cts +35 -0
- package/dist/elements/index.d.ts +35 -0
- package/dist/elements/index.js +295 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/react/index.cjs +58 -21
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +13 -3
- package/dist/react/index.d.ts +13 -3
- package/dist/react/index.js +58 -21
- package/dist/react/index.js.map +1 -1
- package/dist/utils/index.cjs +60 -87
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +6 -3
- package/dist/utils/index.d.ts +6 -3
- package/dist/utils/index.js +43 -81
- package/dist/utils/index.js.map +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { DEFAULT_ACTION_MAP } from '../chunk-I47ZOX3M.js';
|
|
2
|
+
|
|
3
|
+
// src/elements/registry.ts
|
|
4
|
+
var globalManager = null;
|
|
5
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
function registerSatMouse(manager) {
|
|
7
|
+
globalManager = manager;
|
|
8
|
+
for (const fn of listeners) fn(manager);
|
|
9
|
+
listeners.clear();
|
|
10
|
+
}
|
|
11
|
+
function getManager() {
|
|
12
|
+
return globalManager;
|
|
13
|
+
}
|
|
14
|
+
function onManagerReady(fn) {
|
|
15
|
+
if (globalManager) fn(globalManager);
|
|
16
|
+
else listeners.add(fn);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/elements/satmouse-status.ts
|
|
20
|
+
var TEMPLATE = `
|
|
21
|
+
<style>
|
|
22
|
+
:host { display: inline-flex; align-items: center; gap: 8px; font-family: inherit; font-size: 13px; }
|
|
23
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; background: #e74c3c; transition: background 0.3s; }
|
|
24
|
+
.dot[data-state="connected"] { background: #2ecc71; }
|
|
25
|
+
.dot[data-state="connecting"] { background: #f39c12; }
|
|
26
|
+
.dot[data-state="failed"] { background: #e74c3c; }
|
|
27
|
+
.protocol { color: #7f8c8d; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }
|
|
28
|
+
.launch { padding: 4px 12px; background: #2980b9; color: #fff; border-radius: 4px; font-size: 11px;
|
|
29
|
+
text-decoration: none; cursor: pointer; border: none; font-family: inherit; display: none; }
|
|
30
|
+
.launch:hover { background: #3498db; }
|
|
31
|
+
</style>
|
|
32
|
+
<span class="dot"></span>
|
|
33
|
+
<span class="text">Disconnected</span>
|
|
34
|
+
<span class="protocol"></span>
|
|
35
|
+
<button class="launch">Launch SatMouse</button>
|
|
36
|
+
`;
|
|
37
|
+
var SatMouseStatus = class extends HTMLElement {
|
|
38
|
+
dot;
|
|
39
|
+
text;
|
|
40
|
+
proto;
|
|
41
|
+
launch;
|
|
42
|
+
disconnectTimer = null;
|
|
43
|
+
constructor() {
|
|
44
|
+
super();
|
|
45
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
46
|
+
shadow.innerHTML = TEMPLATE;
|
|
47
|
+
this.dot = shadow.querySelector(".dot");
|
|
48
|
+
this.text = shadow.querySelector(".text");
|
|
49
|
+
this.proto = shadow.querySelector(".protocol");
|
|
50
|
+
this.launch = shadow.querySelector(".launch");
|
|
51
|
+
this.launch.addEventListener("click", () => {
|
|
52
|
+
window.location.href = "satmouse://launch";
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
if (!document.hidden) {
|
|
55
|
+
if (confirm("SatMouse doesn't appear to be installed. Go to the download page?")) {
|
|
56
|
+
window.open("https://github.com/kelnishi/SatMouse/releases/latest", "_blank", "noopener");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, 1e3);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
connectedCallback() {
|
|
63
|
+
this.disconnectTimer = setTimeout(() => {
|
|
64
|
+
this.launch.style.display = "inline-block";
|
|
65
|
+
}, 3e3);
|
|
66
|
+
onManagerReady((manager) => this.bind(manager));
|
|
67
|
+
}
|
|
68
|
+
bind(manager) {
|
|
69
|
+
manager.on("stateChange", (state, protocol) => {
|
|
70
|
+
this.dot.dataset.state = state;
|
|
71
|
+
this.proto.textContent = protocol !== "none" ? protocol : "";
|
|
72
|
+
if (this.disconnectTimer) {
|
|
73
|
+
clearTimeout(this.disconnectTimer);
|
|
74
|
+
this.disconnectTimer = null;
|
|
75
|
+
}
|
|
76
|
+
if (state === "connected") {
|
|
77
|
+
this.text.textContent = "Connected";
|
|
78
|
+
this.launch.style.display = "none";
|
|
79
|
+
} else if (state === "connecting") {
|
|
80
|
+
this.text.textContent = "Connecting...";
|
|
81
|
+
this.launch.style.display = "none";
|
|
82
|
+
} else if (state === "failed") {
|
|
83
|
+
this.text.textContent = "Not running";
|
|
84
|
+
this.launch.style.display = "inline-block";
|
|
85
|
+
} else {
|
|
86
|
+
this.text.textContent = "Disconnected";
|
|
87
|
+
this.launch.style.display = "none";
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
customElements.define("satmouse-status", SatMouseStatus);
|
|
93
|
+
|
|
94
|
+
// src/elements/satmouse-devices.ts
|
|
95
|
+
var AXES = ["tx", "ty", "tz", "rx", "ry", "rz"];
|
|
96
|
+
var STYLES = `
|
|
97
|
+
<style>
|
|
98
|
+
:host { display: block; font-family: inherit; font-size: 12px; }
|
|
99
|
+
.panel { background: #0f3460; border: 1px solid #1a4a8a; border-radius: 6px; padding: 10px; margin-bottom: 8px; }
|
|
100
|
+
summary { cursor: pointer; font-weight: 600; color: #e0e0e0; font-size: 13px; }
|
|
101
|
+
.type { font-size: 10px; color: #7f8c8d; text-transform: uppercase; margin-left: 6px; }
|
|
102
|
+
.controls { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
|
|
103
|
+
.slider-row { display: flex; align-items: center; gap: 6px; }
|
|
104
|
+
.slider-row label { color: #7f8c8d; font-weight: 600; width: 38px; flex-shrink: 0; }
|
|
105
|
+
.slider-row input[type="range"] { flex: 1; min-width: 0; height: 4px; accent-color: #3498db; }
|
|
106
|
+
.slider-row span { color: #7f8c8d; font-family: monospace; font-size: 10px; min-width: 44px; text-align: right; }
|
|
107
|
+
.flip-group { display: flex; flex-direction: column; gap: 4px; }
|
|
108
|
+
.flip-row { display: flex; gap: 8px; }
|
|
109
|
+
.flip-row label { display: flex; align-items: center; gap: 2px; color: #7f8c8d; min-width: 36px; }
|
|
110
|
+
.flip-row input { accent-color: #e74c3c; }
|
|
111
|
+
.remap-group { display: grid; grid-template-columns: 1fr 1fr; gap: 3px 8px; }
|
|
112
|
+
.remap-row { display: flex; gap: 4px; align-items: center; }
|
|
113
|
+
.remap-row label { color: #7f8c8d; width: 24px; flex-shrink: 0; }
|
|
114
|
+
.remap-row select { background: #16213e; color: #e0e0e0; border: 1px solid #1a4a8a; border-radius: 3px;
|
|
115
|
+
font-size: 11px; padding: 1px 4px; flex: 1; min-width: 0; }
|
|
116
|
+
.empty { color: #7f8c8d; font-style: italic; }
|
|
117
|
+
</style>
|
|
118
|
+
`;
|
|
119
|
+
function mapSlider(v) {
|
|
120
|
+
return 1e-4 * Math.pow(500, v / 100);
|
|
121
|
+
}
|
|
122
|
+
function unmapSlider(v) {
|
|
123
|
+
return 100 * Math.log(v / 1e-4) / Math.log(500);
|
|
124
|
+
}
|
|
125
|
+
var SatMouseDevices = class extends HTMLElement {
|
|
126
|
+
manager = null;
|
|
127
|
+
container;
|
|
128
|
+
constructor() {
|
|
129
|
+
super();
|
|
130
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
131
|
+
shadow.innerHTML = STYLES + `<div class="container"><span class="empty">No devices</span></div>`;
|
|
132
|
+
this.container = shadow.querySelector(".container");
|
|
133
|
+
}
|
|
134
|
+
connectedCallback() {
|
|
135
|
+
onManagerReady((manager) => {
|
|
136
|
+
this.manager = manager;
|
|
137
|
+
manager.on("deviceStatus", (event, device) => {
|
|
138
|
+
if (event === "connected") this.addDevice(device);
|
|
139
|
+
else this.removeDevice(device);
|
|
140
|
+
});
|
|
141
|
+
manager.on("stateChange", (state) => {
|
|
142
|
+
if (state === "connected") {
|
|
143
|
+
manager.fetchDeviceInfo().then((devices) => devices.forEach((d) => this.addDevice(d)));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
addDevice(device) {
|
|
149
|
+
if (this.shadowRoot.getElementById(`dev-${device.id}`)) return;
|
|
150
|
+
const empty = this.container.querySelector(".empty");
|
|
151
|
+
if (empty) empty.remove();
|
|
152
|
+
const mgr = this.manager;
|
|
153
|
+
const cfg = mgr.getDeviceConfig(device.id);
|
|
154
|
+
const panel = document.createElement("details");
|
|
155
|
+
panel.className = "panel";
|
|
156
|
+
panel.id = `dev-${device.id}`;
|
|
157
|
+
panel.open = true;
|
|
158
|
+
const summary = document.createElement("summary");
|
|
159
|
+
summary.innerHTML = `${device.model ?? device.name}<span class="type">${device.connectionType ?? ""}</span>`;
|
|
160
|
+
panel.appendChild(summary);
|
|
161
|
+
const controls = document.createElement("div");
|
|
162
|
+
controls.className = "controls";
|
|
163
|
+
for (const type of ["translation", "rotation"]) {
|
|
164
|
+
const row = document.createElement("div");
|
|
165
|
+
row.className = "slider-row";
|
|
166
|
+
const val = cfg.sensitivity?.[type] ?? mgr.config.sensitivity[type];
|
|
167
|
+
row.innerHTML = `<label>${type === "translation" ? "Trans" : "Rot"}</label><input type="range" min="0" max="100" value="${Math.round(unmapSlider(val))}"><span>${val.toFixed(4)}</span>`;
|
|
168
|
+
const slider = row.querySelector("input");
|
|
169
|
+
const span = row.querySelector("span");
|
|
170
|
+
slider.addEventListener("input", () => {
|
|
171
|
+
const v = mapSlider(+slider.value);
|
|
172
|
+
span.textContent = v.toFixed(4);
|
|
173
|
+
mgr.updateDeviceConfig(device.id, {
|
|
174
|
+
sensitivity: { ...mgr.getDeviceConfig(device.id).sensitivity, [type]: v }
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
controls.appendChild(row);
|
|
178
|
+
}
|
|
179
|
+
const flipGroup = document.createElement("div");
|
|
180
|
+
flipGroup.className = "flip-group";
|
|
181
|
+
for (const group of [["tx", "ty", "tz"], ["rx", "ry", "rz"]]) {
|
|
182
|
+
const row = document.createElement("div");
|
|
183
|
+
row.className = "flip-row";
|
|
184
|
+
for (const axis of group) {
|
|
185
|
+
const label = document.createElement("label");
|
|
186
|
+
const cb = document.createElement("input");
|
|
187
|
+
cb.type = "checkbox";
|
|
188
|
+
cb.checked = cfg.flip?.[axis] ?? mgr.config.flip[axis];
|
|
189
|
+
cb.addEventListener("change", () => {
|
|
190
|
+
mgr.updateDeviceConfig(device.id, {
|
|
191
|
+
flip: { ...mgr.getDeviceConfig(device.id).flip, [axis]: cb.checked }
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
label.appendChild(cb);
|
|
195
|
+
label.appendChild(document.createTextNode(axis.toUpperCase()));
|
|
196
|
+
row.appendChild(label);
|
|
197
|
+
}
|
|
198
|
+
flipGroup.appendChild(row);
|
|
199
|
+
}
|
|
200
|
+
controls.appendChild(flipGroup);
|
|
201
|
+
const remapGroup = document.createElement("div");
|
|
202
|
+
remapGroup.className = "remap-group";
|
|
203
|
+
const actionMap = cfg.actionMap ?? mgr.config.actionMap;
|
|
204
|
+
for (const action of AXES) {
|
|
205
|
+
const row = document.createElement("div");
|
|
206
|
+
row.className = "remap-row";
|
|
207
|
+
row.innerHTML = `<label>${action.toUpperCase()}</label>`;
|
|
208
|
+
const sel = document.createElement("select");
|
|
209
|
+
for (const src of AXES) {
|
|
210
|
+
const opt = document.createElement("option");
|
|
211
|
+
opt.value = src;
|
|
212
|
+
opt.textContent = src.toUpperCase();
|
|
213
|
+
if (actionMap[action]?.source === src) opt.selected = true;
|
|
214
|
+
sel.appendChild(opt);
|
|
215
|
+
}
|
|
216
|
+
sel.addEventListener("change", () => {
|
|
217
|
+
const current = mgr.getDeviceConfig(device.id).actionMap ?? { ...DEFAULT_ACTION_MAP };
|
|
218
|
+
current[action] = { ...current[action], source: sel.value };
|
|
219
|
+
mgr.updateDeviceConfig(device.id, { actionMap: current });
|
|
220
|
+
});
|
|
221
|
+
row.appendChild(sel);
|
|
222
|
+
remapGroup.appendChild(row);
|
|
223
|
+
}
|
|
224
|
+
controls.appendChild(remapGroup);
|
|
225
|
+
panel.appendChild(controls);
|
|
226
|
+
this.container.appendChild(panel);
|
|
227
|
+
}
|
|
228
|
+
removeDevice(device) {
|
|
229
|
+
this.shadowRoot.getElementById(`dev-${device.id}`)?.remove();
|
|
230
|
+
if (this.container.children.length === 0) {
|
|
231
|
+
this.container.innerHTML = `<span class="empty">No devices</span>`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
customElements.define("satmouse-devices", SatMouseDevices);
|
|
236
|
+
|
|
237
|
+
// src/elements/satmouse-debug.ts
|
|
238
|
+
var TEMPLATE2 = `
|
|
239
|
+
<style>
|
|
240
|
+
:host { display: block; font-family: monospace; font-size: 12px; }
|
|
241
|
+
.row { display: flex; justify-content: space-between; padding: 2px 0; }
|
|
242
|
+
.label { color: #7f8c8d; font-weight: 600; width: 28px; }
|
|
243
|
+
.value { color: #3498db; text-align: right; min-width: 50px; }
|
|
244
|
+
.meta { color: #7f8c8d; font-size: 11px; padding: 2px 0; }
|
|
245
|
+
</style>
|
|
246
|
+
<div class="meta"><span class="state">Disconnected</span> \xB7 <span class="protocol"></span> \xB7 <span class="fps">0</span> fps</div>
|
|
247
|
+
<div class="row"><span class="label">TX</span><span class="value" id="tx">0</span></div>
|
|
248
|
+
<div class="row"><span class="label">TY</span><span class="value" id="ty">0</span></div>
|
|
249
|
+
<div class="row"><span class="label">TZ</span><span class="value" id="tz">0</span></div>
|
|
250
|
+
<div class="row"><span class="label">RX</span><span class="value" id="rx">0</span></div>
|
|
251
|
+
<div class="row"><span class="label">RY</span><span class="value" id="ry">0</span></div>
|
|
252
|
+
<div class="row"><span class="label">RZ</span><span class="value" id="rz">0</span></div>
|
|
253
|
+
`;
|
|
254
|
+
var SatMouseDebug = class extends HTMLElement {
|
|
255
|
+
els = {};
|
|
256
|
+
frameCount = 0;
|
|
257
|
+
constructor() {
|
|
258
|
+
super();
|
|
259
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
260
|
+
shadow.innerHTML = TEMPLATE2;
|
|
261
|
+
for (const id of ["tx", "ty", "tz", "rx", "ry", "rz"]) {
|
|
262
|
+
this.els[id] = shadow.getElementById(id);
|
|
263
|
+
}
|
|
264
|
+
this.els.state = shadow.querySelector(".state");
|
|
265
|
+
this.els.protocol = shadow.querySelector(".protocol");
|
|
266
|
+
this.els.fps = shadow.querySelector(".fps");
|
|
267
|
+
}
|
|
268
|
+
connectedCallback() {
|
|
269
|
+
onManagerReady((manager) => this.bind(manager));
|
|
270
|
+
}
|
|
271
|
+
bind(manager) {
|
|
272
|
+
manager.on("rawSpatialData", (data) => {
|
|
273
|
+
this.frameCount++;
|
|
274
|
+
this.els.tx.textContent = String(Math.round(data.translation.x));
|
|
275
|
+
this.els.ty.textContent = String(Math.round(data.translation.y));
|
|
276
|
+
this.els.tz.textContent = String(Math.round(data.translation.z));
|
|
277
|
+
this.els.rx.textContent = String(Math.round(data.rotation.x));
|
|
278
|
+
this.els.ry.textContent = String(Math.round(data.rotation.y));
|
|
279
|
+
this.els.rz.textContent = String(Math.round(data.rotation.z));
|
|
280
|
+
});
|
|
281
|
+
manager.on("stateChange", (state, protocol) => {
|
|
282
|
+
this.els.state.textContent = state;
|
|
283
|
+
this.els.protocol.textContent = protocol !== "none" ? protocol : "";
|
|
284
|
+
});
|
|
285
|
+
setInterval(() => {
|
|
286
|
+
this.els.fps.textContent = String(this.frameCount);
|
|
287
|
+
this.frameCount = 0;
|
|
288
|
+
}, 1e3);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
customElements.define("satmouse-debug", SatMouseDebug);
|
|
292
|
+
|
|
293
|
+
export { SatMouseDebug, SatMouseDevices, SatMouseStatus, getManager, registerSatMouse };
|
|
294
|
+
//# sourceMappingURL=index.js.map
|
|
295
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/elements/registry.ts","../../src/elements/satmouse-status.ts","../../src/elements/satmouse-devices.ts","../../src/elements/satmouse-debug.ts"],"names":["TEMPLATE"],"mappings":"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","file":"index.js","sourcesContent":["import type { InputManager } from \"../utils/input-manager.js\";\n\n/**\n * Global registry for SatMouse Web Components.\n *\n * Usage:\n * import { registerSatMouse } from \"@kelnishi/satmouse-client/elements\";\n * registerSatMouse(manager);\n * // All <satmouse-*> elements auto-connect to this manager\n */\n\nlet globalManager: InputManager | null = null;\nconst listeners = new Set<(m: InputManager) => void>();\n\nexport function registerSatMouse(manager: InputManager): void {\n globalManager = manager;\n for (const fn of listeners) fn(manager);\n listeners.clear();\n}\n\nexport function getManager(): InputManager | null {\n return globalManager;\n}\n\nexport function onManagerReady(fn: (m: InputManager) => void): void {\n if (globalManager) fn(globalManager);\n else listeners.add(fn);\n}\n","import { onManagerReady } from \"./registry.js\";\nimport type { InputManager } from \"../utils/input-manager.js\";\nimport type { ConnectionState, TransportProtocol } from \"../core/types.js\";\n\nconst TEMPLATE = `\n<style>\n :host { display: inline-flex; align-items: center; gap: 8px; font-family: inherit; font-size: 13px; }\n .dot { width: 8px; height: 8px; border-radius: 50%; background: #e74c3c; transition: background 0.3s; }\n .dot[data-state=\"connected\"] { background: #2ecc71; }\n .dot[data-state=\"connecting\"] { background: #f39c12; }\n .dot[data-state=\"failed\"] { background: #e74c3c; }\n .protocol { color: #7f8c8d; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }\n .launch { padding: 4px 12px; background: #2980b9; color: #fff; border-radius: 4px; font-size: 11px;\n text-decoration: none; cursor: pointer; border: none; font-family: inherit; display: none; }\n .launch:hover { background: #3498db; }\n</style>\n<span class=\"dot\"></span>\n<span class=\"text\">Disconnected</span>\n<span class=\"protocol\"></span>\n<button class=\"launch\">Launch SatMouse</button>\n`;\n\nexport class SatMouseStatus extends HTMLElement {\n private dot!: HTMLElement;\n private text!: HTMLElement;\n private proto!: HTMLElement;\n private launch!: HTMLButtonElement;\n private disconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor() {\n super();\n const shadow = this.attachShadow({ mode: \"open\" });\n shadow.innerHTML = TEMPLATE;\n this.dot = shadow.querySelector(\".dot\")!;\n this.text = shadow.querySelector(\".text\")!;\n this.proto = shadow.querySelector(\".protocol\")!;\n this.launch = shadow.querySelector(\".launch\")!;\n\n this.launch.addEventListener(\"click\", () => {\n // Try the URL scheme — if installed, the OS opens the app\n window.location.href = \"satmouse://launch\";\n // If we're still here after 1s, the scheme wasn't handled\n setTimeout(() => {\n if (!document.hidden) {\n if (confirm(\"SatMouse doesn't appear to be installed. Go to the download page?\")) {\n window.open(\"https://github.com/kelnishi/SatMouse/releases/latest\", \"_blank\", \"noopener\");\n }\n }\n }, 1000);\n });\n }\n\n connectedCallback() {\n // Show launch button if still disconnected after 3s (covers cold page load)\n this.disconnectTimer = setTimeout(() => { this.launch.style.display = \"inline-block\"; }, 3000);\n onManagerReady((manager) => this.bind(manager));\n }\n\n private bind(manager: InputManager): void {\n manager.on(\"stateChange\", (state: ConnectionState, protocol: TransportProtocol) => {\n this.dot.dataset.state = state;\n this.proto.textContent = protocol !== \"none\" ? protocol : \"\";\n\n if (this.disconnectTimer) { clearTimeout(this.disconnectTimer); this.disconnectTimer = null; }\n\n if (state === \"connected\") {\n this.text.textContent = \"Connected\";\n this.launch.style.display = \"none\";\n } else if (state === \"connecting\") {\n this.text.textContent = \"Connecting...\";\n this.launch.style.display = \"none\";\n } else if (state === \"failed\") {\n this.text.textContent = \"Not running\";\n this.launch.style.display = \"inline-block\";\n } else {\n this.text.textContent = \"Disconnected\";\n this.launch.style.display = \"none\";\n }\n });\n }\n}\n\ncustomElements.define(\"satmouse-status\", SatMouseStatus);\n","import { onManagerReady } from \"./registry.js\";\nimport type { InputManager } from \"../utils/input-manager.js\";\nimport type { DeviceInfo } from \"../core/types.js\";\nimport type { InputAxis } from \"../utils/action-map.js\";\nimport { DEFAULT_ACTION_MAP } from \"../utils/action-map.js\";\n\nconst AXES: InputAxis[] = [\"tx\", \"ty\", \"tz\", \"rx\", \"ry\", \"rz\"];\n\nconst STYLES = `\n<style>\n :host { display: block; font-family: inherit; font-size: 12px; }\n .panel { background: #0f3460; border: 1px solid #1a4a8a; border-radius: 6px; padding: 10px; margin-bottom: 8px; }\n summary { cursor: pointer; font-weight: 600; color: #e0e0e0; font-size: 13px; }\n .type { font-size: 10px; color: #7f8c8d; text-transform: uppercase; margin-left: 6px; }\n .controls { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }\n .slider-row { display: flex; align-items: center; gap: 6px; }\n .slider-row label { color: #7f8c8d; font-weight: 600; width: 38px; flex-shrink: 0; }\n .slider-row input[type=\"range\"] { flex: 1; min-width: 0; height: 4px; accent-color: #3498db; }\n .slider-row span { color: #7f8c8d; font-family: monospace; font-size: 10px; min-width: 44px; text-align: right; }\n .flip-group { display: flex; flex-direction: column; gap: 4px; }\n .flip-row { display: flex; gap: 8px; }\n .flip-row label { display: flex; align-items: center; gap: 2px; color: #7f8c8d; min-width: 36px; }\n .flip-row input { accent-color: #e74c3c; }\n .remap-group { display: grid; grid-template-columns: 1fr 1fr; gap: 3px 8px; }\n .remap-row { display: flex; gap: 4px; align-items: center; }\n .remap-row label { color: #7f8c8d; width: 24px; flex-shrink: 0; }\n .remap-row select { background: #16213e; color: #e0e0e0; border: 1px solid #1a4a8a; border-radius: 3px;\n font-size: 11px; padding: 1px 4px; flex: 1; min-width: 0; }\n .empty { color: #7f8c8d; font-style: italic; }\n</style>\n`;\n\nfunction mapSlider(v: number): number { return 0.0001 * Math.pow(500, v / 100); }\nfunction unmapSlider(v: number): number { return (100 * Math.log(v / 0.0001)) / Math.log(500); }\n\nexport class SatMouseDevices extends HTMLElement {\n private manager: InputManager | null = null;\n private container!: HTMLElement;\n\n constructor() {\n super();\n const shadow = this.attachShadow({ mode: \"open\" });\n shadow.innerHTML = STYLES + `<div class=\"container\"><span class=\"empty\">No devices</span></div>`;\n this.container = shadow.querySelector(\".container\")!;\n }\n\n connectedCallback() {\n onManagerReady((manager) => {\n this.manager = manager;\n manager.on(\"deviceStatus\", (event, device) => {\n if (event === \"connected\") this.addDevice(device);\n else this.removeDevice(device);\n });\n manager.on(\"stateChange\", (state) => {\n if (state === \"connected\") {\n manager.fetchDeviceInfo().then((devices) => devices.forEach((d) => this.addDevice(d)));\n }\n });\n });\n }\n\n private addDevice(device: DeviceInfo): void {\n if (this.shadowRoot!.getElementById(`dev-${device.id}`)) return;\n const empty = this.container.querySelector(\".empty\");\n if (empty) empty.remove();\n\n const mgr = this.manager!;\n const cfg = mgr.getDeviceConfig(device.id);\n\n const panel = document.createElement(\"details\");\n panel.className = \"panel\";\n panel.id = `dev-${device.id}`;\n panel.open = true;\n\n const summary = document.createElement(\"summary\");\n summary.innerHTML = `${device.model ?? device.name}<span class=\"type\">${device.connectionType ?? \"\"}</span>`;\n panel.appendChild(summary);\n\n const controls = document.createElement(\"div\");\n controls.className = \"controls\";\n\n // Sensitivity sliders\n for (const type of [\"translation\", \"rotation\"] as const) {\n const row = document.createElement(\"div\");\n row.className = \"slider-row\";\n const val = cfg.sensitivity?.[type] ?? mgr.config.sensitivity[type];\n row.innerHTML = `<label>${type === \"translation\" ? \"Trans\" : \"Rot\"}</label>` +\n `<input type=\"range\" min=\"0\" max=\"100\" value=\"${Math.round(unmapSlider(val))}\">` +\n `<span>${val.toFixed(4)}</span>`;\n const slider = row.querySelector(\"input\")! as HTMLInputElement;\n const span = row.querySelector(\"span\")!;\n slider.addEventListener(\"input\", () => {\n const v = mapSlider(+slider.value);\n span.textContent = v.toFixed(4);\n mgr.updateDeviceConfig(device.id, {\n sensitivity: { ...mgr.getDeviceConfig(device.id).sensitivity, [type]: v },\n });\n });\n controls.appendChild(row);\n }\n\n // Flip checkboxes\n const flipGroup = document.createElement(\"div\");\n flipGroup.className = \"flip-group\";\n for (const group of [[\"tx\", \"ty\", \"tz\"], [\"rx\", \"ry\", \"rz\"]] as InputAxis[][]) {\n const row = document.createElement(\"div\");\n row.className = \"flip-row\";\n for (const axis of group) {\n const label = document.createElement(\"label\");\n const cb = document.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.checked = cfg.flip?.[axis] ?? mgr.config.flip[axis];\n cb.addEventListener(\"change\", () => {\n mgr.updateDeviceConfig(device.id, {\n flip: { ...mgr.getDeviceConfig(device.id).flip, [axis]: cb.checked },\n });\n });\n label.appendChild(cb);\n label.appendChild(document.createTextNode(axis.toUpperCase()));\n row.appendChild(label);\n }\n flipGroup.appendChild(row);\n }\n controls.appendChild(flipGroup);\n\n // Axis remap\n const remapGroup = document.createElement(\"div\");\n remapGroup.className = \"remap-group\";\n const actionMap = cfg.actionMap ?? mgr.config.actionMap;\n for (const action of AXES) {\n const row = document.createElement(\"div\");\n row.className = \"remap-row\";\n row.innerHTML = `<label>${action.toUpperCase()}</label>`;\n const sel = document.createElement(\"select\");\n for (const src of AXES) {\n const opt = document.createElement(\"option\");\n opt.value = src;\n opt.textContent = src.toUpperCase();\n if (actionMap[action]?.source === src) opt.selected = true;\n sel.appendChild(opt);\n }\n sel.addEventListener(\"change\", () => {\n const current = mgr.getDeviceConfig(device.id).actionMap ?? { ...DEFAULT_ACTION_MAP };\n current[action] = { ...current[action], source: sel.value as InputAxis };\n mgr.updateDeviceConfig(device.id, { actionMap: current });\n });\n row.appendChild(sel);\n remapGroup.appendChild(row);\n }\n controls.appendChild(remapGroup);\n\n panel.appendChild(controls);\n this.container.appendChild(panel);\n }\n\n private removeDevice(device: DeviceInfo): void {\n this.shadowRoot!.getElementById(`dev-${device.id}`)?.remove();\n if (this.container.children.length === 0) {\n this.container.innerHTML = `<span class=\"empty\">No devices</span>`;\n }\n }\n}\n\ncustomElements.define(\"satmouse-devices\", SatMouseDevices);\n","import { onManagerReady } from \"./registry.js\";\nimport type { InputManager } from \"../utils/input-manager.js\";\nimport type { SpatialData, ConnectionState, TransportProtocol } from \"../core/types.js\";\n\nconst TEMPLATE = `\n<style>\n :host { display: block; font-family: monospace; font-size: 12px; }\n .row { display: flex; justify-content: space-between; padding: 2px 0; }\n .label { color: #7f8c8d; font-weight: 600; width: 28px; }\n .value { color: #3498db; text-align: right; min-width: 50px; }\n .meta { color: #7f8c8d; font-size: 11px; padding: 2px 0; }\n</style>\n<div class=\"meta\"><span class=\"state\">Disconnected</span> · <span class=\"protocol\"></span> · <span class=\"fps\">0</span> fps</div>\n<div class=\"row\"><span class=\"label\">TX</span><span class=\"value\" id=\"tx\">0</span></div>\n<div class=\"row\"><span class=\"label\">TY</span><span class=\"value\" id=\"ty\">0</span></div>\n<div class=\"row\"><span class=\"label\">TZ</span><span class=\"value\" id=\"tz\">0</span></div>\n<div class=\"row\"><span class=\"label\">RX</span><span class=\"value\" id=\"rx\">0</span></div>\n<div class=\"row\"><span class=\"label\">RY</span><span class=\"value\" id=\"ry\">0</span></div>\n<div class=\"row\"><span class=\"label\">RZ</span><span class=\"value\" id=\"rz\">0</span></div>\n`;\n\nexport class SatMouseDebug extends HTMLElement {\n private els: Record<string, HTMLElement> = {};\n private frameCount = 0;\n\n constructor() {\n super();\n const shadow = this.attachShadow({ mode: \"open\" });\n shadow.innerHTML = TEMPLATE;\n for (const id of [\"tx\", \"ty\", \"tz\", \"rx\", \"ry\", \"rz\"]) {\n this.els[id] = shadow.getElementById(id)!;\n }\n this.els.state = shadow.querySelector(\".state\")!;\n this.els.protocol = shadow.querySelector(\".protocol\")!;\n this.els.fps = shadow.querySelector(\".fps\")!;\n }\n\n connectedCallback() {\n onManagerReady((manager) => this.bind(manager));\n }\n\n private bind(manager: InputManager): void {\n manager.on(\"rawSpatialData\", (data: SpatialData) => {\n this.frameCount++;\n this.els.tx.textContent = String(Math.round(data.translation.x));\n this.els.ty.textContent = String(Math.round(data.translation.y));\n this.els.tz.textContent = String(Math.round(data.translation.z));\n this.els.rx.textContent = String(Math.round(data.rotation.x));\n this.els.ry.textContent = String(Math.round(data.rotation.y));\n this.els.rz.textContent = String(Math.round(data.rotation.z));\n });\n\n manager.on(\"stateChange\", (state: ConnectionState, protocol: TransportProtocol) => {\n this.els.state.textContent = state;\n this.els.protocol.textContent = protocol !== \"none\" ? protocol : \"\";\n });\n\n setInterval(() => {\n this.els.fps.textContent = String(this.frameCount);\n this.frameCount = 0;\n }, 1000);\n }\n}\n\ncustomElements.define(\"satmouse-debug\", SatMouseDebug);\n"]}
|
package/dist/react/index.cjs
CHANGED
|
@@ -269,6 +269,7 @@ function parseSatMouseUri(uri) {
|
|
|
269
269
|
var DEFAULT_OPTIONS = {
|
|
270
270
|
transports: ["webtransport", "websocket"],
|
|
271
271
|
reconnectDelay: 2e3,
|
|
272
|
+
maxRetries: 3,
|
|
272
273
|
wsSubprotocol: "satmouse-json"
|
|
273
274
|
};
|
|
274
275
|
var SatMouseConnection = class extends TypedEmitter {
|
|
@@ -277,6 +278,7 @@ var SatMouseConnection = class extends TypedEmitter {
|
|
|
277
278
|
reconnectTimer = null;
|
|
278
279
|
intentionalClose = false;
|
|
279
280
|
deviceInfoUrl = null;
|
|
281
|
+
retryCount = 0;
|
|
280
282
|
_state = "disconnected";
|
|
281
283
|
_protocol = "none";
|
|
282
284
|
get state() {
|
|
@@ -302,7 +304,7 @@ var SatMouseConnection = class extends TypedEmitter {
|
|
|
302
304
|
this.options.tdUrl = this.options.tdUrl ?? parsed.tdUrl;
|
|
303
305
|
}
|
|
304
306
|
if (!wtUrl && !wsUrl) {
|
|
305
|
-
const tdUrl = this.options.tdUrl ??
|
|
307
|
+
const tdUrl = this.options.tdUrl ?? "http://localhost:18945/td.json";
|
|
306
308
|
try {
|
|
307
309
|
const td = await fetchThingDescription(tdUrl);
|
|
308
310
|
const endpoints = resolveEndpoints(td);
|
|
@@ -312,7 +314,7 @@ var SatMouseConnection = class extends TypedEmitter {
|
|
|
312
314
|
this.deviceInfoUrl = endpoints.deviceInfoUrl ?? null;
|
|
313
315
|
} catch (err) {
|
|
314
316
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
315
|
-
wsUrl =
|
|
317
|
+
wsUrl = "ws://localhost:18945/spatial";
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
for (const proto of this.options.transports) {
|
|
@@ -337,6 +339,12 @@ var SatMouseConnection = class extends TypedEmitter {
|
|
|
337
339
|
this.setState("disconnected", "none");
|
|
338
340
|
this.scheduleReconnect();
|
|
339
341
|
}
|
|
342
|
+
/** Reset retry count and reconnect. Use after "failed" state. */
|
|
343
|
+
retry() {
|
|
344
|
+
this.retryCount = 0;
|
|
345
|
+
this.intentionalClose = false;
|
|
346
|
+
this.connect();
|
|
347
|
+
}
|
|
340
348
|
disconnect() {
|
|
341
349
|
this.intentionalClose = true;
|
|
342
350
|
this.clearReconnect();
|
|
@@ -368,6 +376,7 @@ var SatMouseConnection = class extends TypedEmitter {
|
|
|
368
376
|
try {
|
|
369
377
|
await adapter.connect();
|
|
370
378
|
this.transport = adapter;
|
|
379
|
+
this.retryCount = 0;
|
|
371
380
|
this.setState("connected", adapter.protocol);
|
|
372
381
|
return true;
|
|
373
382
|
} catch (err) {
|
|
@@ -383,6 +392,13 @@ var SatMouseConnection = class extends TypedEmitter {
|
|
|
383
392
|
}
|
|
384
393
|
scheduleReconnect() {
|
|
385
394
|
if (this.options.reconnectDelay <= 0 || this.intentionalClose) return;
|
|
395
|
+
this.retryCount++;
|
|
396
|
+
console.log(`[SatMouse] Reconnect attempt ${this.retryCount}/${this.options.maxRetries}`);
|
|
397
|
+
if (this.retryCount > this.options.maxRetries) {
|
|
398
|
+
console.log("[SatMouse] Max retries exceeded, giving up");
|
|
399
|
+
this.setState("failed", "none");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
386
402
|
this.clearReconnect();
|
|
387
403
|
this.reconnectTimer = setTimeout(() => {
|
|
388
404
|
this.reconnectTimer = null;
|
|
@@ -481,14 +497,17 @@ function actionValuesToSpatialData(values, timestamp) {
|
|
|
481
497
|
// src/utils/config.ts
|
|
482
498
|
var DEFAULT_CONFIG = {
|
|
483
499
|
sensitivity: { translation: 1e-3, rotation: 1e-3 },
|
|
484
|
-
flip: { tx: false, ty:
|
|
500
|
+
flip: { tx: false, ty: false, tz: false, rx: false, ry: false, rz: false },
|
|
485
501
|
deadZone: 0,
|
|
486
502
|
dominant: false,
|
|
487
503
|
axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
|
|
488
504
|
lockPosition: false,
|
|
489
505
|
lockRotation: false,
|
|
490
506
|
actionMap: { ...DEFAULT_ACTION_MAP },
|
|
491
|
-
devices: {
|
|
507
|
+
devices: {
|
|
508
|
+
// SpaceMouse Z-up → Three.js Y-up axis correction
|
|
509
|
+
"cnx-*": { flip: { ty: true, tz: true, ry: true, rz: true } }
|
|
510
|
+
}
|
|
492
511
|
};
|
|
493
512
|
function mergeConfig(base, partial) {
|
|
494
513
|
const merged = {
|
|
@@ -652,8 +671,8 @@ var InputManager = class extends TypedEmitter {
|
|
|
652
671
|
connections = [];
|
|
653
672
|
storage;
|
|
654
673
|
knownDevices = /* @__PURE__ */ new Map();
|
|
655
|
-
//
|
|
656
|
-
|
|
674
|
+
// Per-device accumulators: latest value from each device per frame tick
|
|
675
|
+
deviceAccumulators = /* @__PURE__ */ new Map();
|
|
657
676
|
accDirty = false;
|
|
658
677
|
flushTimer = null;
|
|
659
678
|
_config;
|
|
@@ -751,13 +770,16 @@ var InputManager = class extends TypedEmitter {
|
|
|
751
770
|
wireConnection(connection) {
|
|
752
771
|
connection.on("spatialData", (raw) => {
|
|
753
772
|
this.emit("rawSpatialData", raw);
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
773
|
+
const id = raw.deviceId ?? "_default";
|
|
774
|
+
const processed = this.processPerDevice(raw, id);
|
|
775
|
+
this.deviceAccumulators.set(id, {
|
|
776
|
+
tx: processed.translation.x,
|
|
777
|
+
ty: processed.translation.y,
|
|
778
|
+
tz: processed.translation.z,
|
|
779
|
+
rx: processed.rotation.x,
|
|
780
|
+
ry: processed.rotation.y,
|
|
781
|
+
rz: processed.rotation.z
|
|
782
|
+
});
|
|
761
783
|
this.accDirty = true;
|
|
762
784
|
});
|
|
763
785
|
connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
|
|
@@ -770,25 +792,40 @@ var InputManager = class extends TypedEmitter {
|
|
|
770
792
|
}
|
|
771
793
|
flushAccumulator() {
|
|
772
794
|
if (!this.accDirty) return;
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
795
|
+
const merged = { tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0 };
|
|
796
|
+
for (const acc of this.deviceAccumulators.values()) {
|
|
797
|
+
merged.tx += acc.tx;
|
|
798
|
+
merged.ty += acc.ty;
|
|
799
|
+
merged.tz += acc.tz;
|
|
800
|
+
merged.rx += acc.rx;
|
|
801
|
+
merged.ry += acc.ry;
|
|
802
|
+
merged.rz += acc.rz;
|
|
803
|
+
}
|
|
804
|
+
this.deviceAccumulators.clear();
|
|
805
|
+
this.accDirty = false;
|
|
806
|
+
const data = {
|
|
807
|
+
translation: { x: merged.tx, y: merged.ty, z: merged.tz },
|
|
808
|
+
rotation: { x: merged.rx, y: merged.ry, z: merged.rz },
|
|
776
809
|
timestamp: performance.now() * 1e3
|
|
777
810
|
};
|
|
778
|
-
|
|
779
|
-
this.accDirty = false;
|
|
780
|
-
const { spatial, actions } = this.processSpatialData(raw);
|
|
811
|
+
const { spatial, actions } = this.applyGlobalTransforms(data);
|
|
781
812
|
if (spatial) this.emit("spatialData", spatial);
|
|
782
813
|
if (actions) this.emit("actionValues", actions);
|
|
783
814
|
}
|
|
784
|
-
|
|
785
|
-
|
|
815
|
+
/** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */
|
|
816
|
+
processPerDevice(raw, deviceId) {
|
|
817
|
+
const cfg = resolveDeviceConfig(this._config, deviceId);
|
|
786
818
|
let data = raw;
|
|
787
819
|
if (cfg.deadZone > 0) data = applyDeadZone(data, cfg.deadZone);
|
|
788
820
|
if (cfg.dominant) data = applyDominant(data);
|
|
789
821
|
data = applyFlip(data, cfg.flip);
|
|
790
822
|
data = applyAxisRemap(data, cfg.axisRemap);
|
|
791
823
|
data = applySensitivity(data, cfg.sensitivity);
|
|
824
|
+
return data;
|
|
825
|
+
}
|
|
826
|
+
/** Global transforms applied after per-device merge: locks + action map */
|
|
827
|
+
applyGlobalTransforms(data) {
|
|
828
|
+
const cfg = this._config;
|
|
792
829
|
if (cfg.lockPosition) {
|
|
793
830
|
data = { ...data, translation: { x: 0, y: 0, z: 0 } };
|
|
794
831
|
}
|