@runhuman/sensor 0.2.2 → 0.2.4

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/index.mjs CHANGED
@@ -1154,7 +1154,281 @@ var Runhuman = class _Runhuman {
1154
1154
  }
1155
1155
  }
1156
1156
  };
1157
+
1158
+ // src/overlay/RunhumanOverlay.tsx
1159
+ import { View as View2, StyleSheet as StyleSheet2 } from "react-native";
1160
+
1161
+ // src/overlay/use-sensor-state.ts
1162
+ import { useCallback, useRef } from "react";
1163
+ import { useSyncExternalStore } from "react";
1164
+ var IDLE_STATE = { state: "idle", activeJobId: null, sessionId: null };
1165
+ function tryGetInstance() {
1166
+ try {
1167
+ return Runhuman.getInstance();
1168
+ } catch {
1169
+ return null;
1170
+ }
1171
+ }
1172
+ function useSensorState() {
1173
+ const cached = useRef(IDLE_STATE);
1174
+ const subscribe = useCallback((onStoreChange) => {
1175
+ const instance = tryGetInstance();
1176
+ if (!instance) {
1177
+ const interval = setInterval(() => {
1178
+ if (tryGetInstance()) {
1179
+ clearInterval(interval);
1180
+ onStoreChange();
1181
+ }
1182
+ }, 100);
1183
+ return () => clearInterval(interval);
1184
+ }
1185
+ return instance.getSessionManager().subscribe(onStoreChange);
1186
+ }, []);
1187
+ const getSnapshot = useCallback(() => {
1188
+ const instance = tryGetInstance();
1189
+ if (!instance) return IDLE_STATE;
1190
+ const sm = instance.getSessionManager();
1191
+ const state = sm.getSnapshot();
1192
+ const activeJobId = sm.getActiveJobId();
1193
+ const sessionId = sm.getSessionId();
1194
+ const prev = cached.current;
1195
+ if (prev.state === state && prev.activeJobId === activeJobId && prev.sessionId === sessionId) {
1196
+ return prev;
1197
+ }
1198
+ cached.current = { state, activeJobId, sessionId };
1199
+ return cached.current;
1200
+ }, []);
1201
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1202
+ }
1203
+
1204
+ // src/overlay/CodeEntryPanel.tsx
1205
+ import { useState } from "react";
1206
+ import { View, Text, TextInput, Pressable } from "react-native";
1207
+
1208
+ // src/overlay/overlay-styles.ts
1209
+ import { StyleSheet } from "react-native";
1210
+ var BRAND_GREEN = "#22c55e";
1211
+ var BRAND_AMBER = "#f59e0b";
1212
+ var BRAND_RED = "#ef4444";
1213
+ var BRAND_BLUE = "#3b82f6";
1214
+ var OVERLAY_BG = "rgba(0, 0, 0, 0.85)";
1215
+ var OVERLAY_BORDER = "rgba(255, 255, 255, 0.15)";
1216
+ var overlayStyles = StyleSheet.create({
1217
+ // Positioning containers
1218
+ topLeft: { top: 60, left: 16 },
1219
+ topRight: { top: 60, right: 16 },
1220
+ bottomLeft: { bottom: 40, left: 16 },
1221
+ bottomRight: { bottom: 40, right: 16 },
1222
+ // Code entry panel
1223
+ panel: {
1224
+ position: "absolute",
1225
+ zIndex: 99999,
1226
+ backgroundColor: OVERLAY_BG,
1227
+ borderRadius: 12,
1228
+ borderWidth: 1,
1229
+ borderColor: OVERLAY_BORDER,
1230
+ padding: 16,
1231
+ width: 220
1232
+ },
1233
+ panelTitle: {
1234
+ color: "#ffffff",
1235
+ fontSize: 13,
1236
+ fontWeight: "600",
1237
+ marginBottom: 8
1238
+ },
1239
+ input: {
1240
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
1241
+ borderRadius: 8,
1242
+ borderWidth: 1,
1243
+ borderColor: OVERLAY_BORDER,
1244
+ color: "#ffffff",
1245
+ fontSize: 18,
1246
+ fontFamily: "monospace",
1247
+ fontWeight: "bold",
1248
+ letterSpacing: 2,
1249
+ padding: 10,
1250
+ textAlign: "center"
1251
+ },
1252
+ inputError: {
1253
+ borderColor: BRAND_RED
1254
+ },
1255
+ submitButton: {
1256
+ backgroundColor: BRAND_BLUE,
1257
+ borderRadius: 8,
1258
+ paddingVertical: 8,
1259
+ marginTop: 8,
1260
+ alignItems: "center"
1261
+ },
1262
+ submitButtonDisabled: {
1263
+ opacity: 0.5
1264
+ },
1265
+ submitButtonText: {
1266
+ color: "#ffffff",
1267
+ fontSize: 13,
1268
+ fontWeight: "600"
1269
+ },
1270
+ errorText: {
1271
+ color: BRAND_RED,
1272
+ fontSize: 11,
1273
+ marginTop: 4,
1274
+ textAlign: "center"
1275
+ },
1276
+ minimizeButton: {
1277
+ position: "absolute",
1278
+ top: 8,
1279
+ right: 8
1280
+ },
1281
+ minimizeText: {
1282
+ color: "rgba(255, 255, 255, 0.5)",
1283
+ fontSize: 16
1284
+ },
1285
+ // Minimized fab (floating action button)
1286
+ fab: {
1287
+ position: "absolute",
1288
+ zIndex: 99999,
1289
+ width: 40,
1290
+ height: 40,
1291
+ borderRadius: 20,
1292
+ backgroundColor: OVERLAY_BG,
1293
+ borderWidth: 1,
1294
+ borderColor: OVERLAY_BORDER,
1295
+ alignItems: "center",
1296
+ justifyContent: "center"
1297
+ },
1298
+ fabText: {
1299
+ color: "#ffffff",
1300
+ fontSize: 16
1301
+ },
1302
+ // Active indicator dot
1303
+ indicator: {
1304
+ position: "absolute",
1305
+ zIndex: 99999,
1306
+ width: 12,
1307
+ height: 12,
1308
+ borderRadius: 6
1309
+ },
1310
+ indicatorActive: {
1311
+ backgroundColor: BRAND_GREEN
1312
+ },
1313
+ indicatorEnding: {
1314
+ backgroundColor: BRAND_AMBER
1315
+ },
1316
+ indicatorPolling: {
1317
+ backgroundColor: BRAND_BLUE
1318
+ }
1319
+ });
1320
+
1321
+ // src/overlay/CodeEntryPanel.tsx
1322
+ import { jsx, jsxs } from "react/jsx-runtime";
1323
+ function CodeEntryPanel({ position }) {
1324
+ const [code, setCode] = useState("");
1325
+ const [error, setError] = useState(null);
1326
+ const [resolving, setResolving] = useState(false);
1327
+ const [minimized, setMinimized] = useState(false);
1328
+ const handleSubmit = async () => {
1329
+ const trimmed = code.trim();
1330
+ if (!trimmed) return;
1331
+ setResolving(true);
1332
+ setError(null);
1333
+ const instance = Runhuman.getInstance();
1334
+ const result = await instance.getApiClient().resolveShortCode(trimmed);
1335
+ if (result) {
1336
+ instance.activate(result.jobId);
1337
+ } else {
1338
+ setError("Invalid or expired code");
1339
+ setResolving(false);
1340
+ }
1341
+ };
1342
+ if (minimized) {
1343
+ return /* @__PURE__ */ jsx(Pressable, { style: [overlayStyles.fab, overlayStyles[position]], onPress: () => setMinimized(false), children: /* @__PURE__ */ jsx(Text, { style: overlayStyles.fabText, children: "RH" }) });
1344
+ }
1345
+ return /* @__PURE__ */ jsxs(View, { style: [overlayStyles.panel, overlayStyles[position]], children: [
1346
+ /* @__PURE__ */ jsx(Pressable, { style: overlayStyles.minimizeButton, onPress: () => setMinimized(true), children: /* @__PURE__ */ jsx(Text, { style: overlayStyles.minimizeText, children: "-" }) }),
1347
+ /* @__PURE__ */ jsx(Text, { style: overlayStyles.panelTitle, children: "Runhuman Sensor" }),
1348
+ /* @__PURE__ */ jsx(
1349
+ TextInput,
1350
+ {
1351
+ style: error ? { ...overlayStyles.input, ...overlayStyles.inputError } : overlayStyles.input,
1352
+ value: code,
1353
+ onChangeText: (text) => {
1354
+ setCode(text.toUpperCase());
1355
+ setError(null);
1356
+ },
1357
+ placeholder: "RH-XXXX",
1358
+ placeholderTextColor: "rgba(255,255,255,0.3)",
1359
+ autoCapitalize: "characters",
1360
+ maxLength: 10,
1361
+ editable: !resolving,
1362
+ onSubmitEditing: handleSubmit
1363
+ }
1364
+ ),
1365
+ error ? /* @__PURE__ */ jsx(Text, { style: overlayStyles.errorText, children: error }) : null,
1366
+ /* @__PURE__ */ jsx(
1367
+ Pressable,
1368
+ {
1369
+ style: resolving ? { ...overlayStyles.submitButton, ...overlayStyles.submitButtonDisabled } : overlayStyles.submitButton,
1370
+ onPress: handleSubmit,
1371
+ disabled: resolving || !code.trim(),
1372
+ children: /* @__PURE__ */ jsx(Text, { style: overlayStyles.submitButtonText, children: resolving ? "Activating..." : "Activate" })
1373
+ }
1374
+ )
1375
+ ] });
1376
+ }
1377
+
1378
+ // src/overlay/ActiveIndicator.tsx
1379
+ import { useEffect, useRef as useRef2 } from "react";
1380
+ import { Animated } from "react-native";
1381
+ import { jsx as jsx2 } from "react/jsx-runtime";
1382
+ var stateStyle = {
1383
+ active: overlayStyles.indicatorActive,
1384
+ ending: overlayStyles.indicatorEnding,
1385
+ polling: overlayStyles.indicatorPolling
1386
+ };
1387
+ function ActiveIndicator({ state, position }) {
1388
+ const pulse = useRef2(new Animated.Value(1)).current;
1389
+ useEffect(() => {
1390
+ if (state === "active") {
1391
+ const animation = Animated.loop(
1392
+ Animated.sequence([
1393
+ Animated.timing(pulse, { toValue: 0.3, duration: 800, useNativeDriver: true }),
1394
+ Animated.timing(pulse, { toValue: 1, duration: 800, useNativeDriver: true })
1395
+ ])
1396
+ );
1397
+ animation.start();
1398
+ return () => animation.stop();
1399
+ }
1400
+ pulse.setValue(1);
1401
+ }, [state, pulse]);
1402
+ const colorStyle = stateStyle[state];
1403
+ if (!colorStyle) return null;
1404
+ return /* @__PURE__ */ jsx2(Animated.View, { style: [overlayStyles.indicator, overlayStyles[position], colorStyle, { opacity: pulse }] });
1405
+ }
1406
+
1407
+ // src/overlay/RunhumanOverlay.tsx
1408
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1409
+ var positionMap = {
1410
+ "top-left": "topLeft",
1411
+ "top-right": "topRight",
1412
+ "bottom-left": "bottomLeft",
1413
+ "bottom-right": "bottomRight"
1414
+ };
1415
+ function RunhumanOverlay({ children, position = "bottom-right" }) {
1416
+ const { state, activeJobId } = useSensorState();
1417
+ const posKey = positionMap[position];
1418
+ return /* @__PURE__ */ jsxs2(View2, { style: styles.container, children: [
1419
+ children,
1420
+ state === "idle" && !activeJobId ? /* @__PURE__ */ jsx3(CodeEntryPanel, { position: posKey }) : null,
1421
+ state !== "idle" ? /* @__PURE__ */ jsx3(ActiveIndicator, { state, position: posKey }) : null
1422
+ ] });
1423
+ }
1424
+ var styles = StyleSheet2.create({
1425
+ container: {
1426
+ flex: 1
1427
+ }
1428
+ });
1157
1429
  export {
1158
- Runhuman
1430
+ Runhuman,
1431
+ RunhumanOverlay,
1432
+ useSensorState
1159
1433
  };
1160
1434
  //# sourceMappingURL=index.mjs.map