@limrun/ui 0.5.0 → 0.5.2
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/components/remote-control.d.ts +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +484 -442
- package/package.json +1 -1
- package/src/assets/pixel_tablet_landscape.webp +0 -0
- package/src/assets/pixel_tablet_portrait.webp +0 -0
- package/src/components/remote-control.tsx +85 -1
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -7,6 +7,8 @@ import { ANDROID_KEYS, AMOTION_EVENT, codeMap } from '../core/constants';
|
|
|
7
7
|
import iphoneFrameImage from '../assets/iphone16pro_black_bg.webp';
|
|
8
8
|
import pixelFrameImage from '../assets/pixel9_black.webp';
|
|
9
9
|
import pixelFrameImageLandscape from '../assets/pixel9_black_landscape.webp';
|
|
10
|
+
import pixelTabletFrameImage from '../assets/pixel_tablet_portrait.webp';
|
|
11
|
+
import pixelTabletFrameImageLandscape from '../assets/pixel_tablet_landscape.webp';
|
|
10
12
|
import iphoneFrameImageLandscape from '../assets/iphone16pro_black_landscape_bg.webp';
|
|
11
13
|
import appleLogoSvg from '../assets/Apple_logo_white.svg';
|
|
12
14
|
import androidBootImage from '../assets/android_boot.webp';
|
|
@@ -68,6 +70,7 @@ export interface RemoteControlHandle {
|
|
|
68
70
|
openUrl: (url: string) => void;
|
|
69
71
|
sendKeyEvent: (event: ImperativeKeyboardEvent) => void;
|
|
70
72
|
screenshot: () => Promise<ScreenshotData>;
|
|
73
|
+
terminateApp: (bundleId: string) => Promise<void>;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
const debugLog = (...args: any[]) => {
|
|
@@ -114,6 +117,13 @@ type DeviceConfig = {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
const ANDROID_TABLET_VIDEO_WIDTH = 1920;
|
|
121
|
+
const ANDROID_TABLET_VIDEO_HEIGHT = 1200;
|
|
122
|
+
|
|
123
|
+
const isAndroidTabletVideo = (width: number, height: number): boolean =>
|
|
124
|
+
(width === ANDROID_TABLET_VIDEO_WIDTH && height === ANDROID_TABLET_VIDEO_HEIGHT) ||
|
|
125
|
+
(width === ANDROID_TABLET_VIDEO_HEIGHT && height === ANDROID_TABLET_VIDEO_WIDTH);
|
|
126
|
+
|
|
117
127
|
// Device-specific configuration for frame sizing and video positioning
|
|
118
128
|
// Video position percentages are relative to the frame image dimensions
|
|
119
129
|
const deviceConfig: Record<DevicePlatform, DeviceConfig> = {
|
|
@@ -184,6 +194,7 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
184
194
|
const frameRef = useRef<HTMLImageElement>(null);
|
|
185
195
|
const [videoLoaded, setVideoLoaded] = useState(false);
|
|
186
196
|
const [isLandscape, setIsLandscape] = useState(false);
|
|
197
|
+
const [useAndroidTabletFrame, setUseAndroidTabletFrame] = useState(false);
|
|
187
198
|
const [videoStyle, setVideoStyle] = useState<React.CSSProperties>({});
|
|
188
199
|
const wsRef = useRef<WebSocket | null>(null);
|
|
189
200
|
const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
|
|
@@ -193,6 +204,8 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
193
204
|
Map<string, (value: ScreenshotData | PromiseLike<ScreenshotData>) => void>
|
|
194
205
|
>(new Map());
|
|
195
206
|
const pendingScreenshotRejectersRef = useRef<Map<string, (reason?: any) => void>>(new Map());
|
|
207
|
+
const pendingTerminateAppResolversRef = useRef<Map<string, () => void>>(new Map());
|
|
208
|
+
const pendingTerminateAppRejectersRef = useRef<Map<string, (reason?: any) => void>>(new Map());
|
|
196
209
|
|
|
197
210
|
// Map to track active pointers for real touch/mouse single-finger events.
|
|
198
211
|
// Key: pointerId (-1 for mouse, touch.identifier for touch), Value: { x: number, y: number }
|
|
@@ -1273,6 +1286,33 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
1273
1286
|
pendingScreenshotResolversRef.current.delete(message.id);
|
|
1274
1287
|
pendingScreenshotRejectersRef.current.delete(message.id);
|
|
1275
1288
|
break;
|
|
1289
|
+
case 'terminateAppResult':
|
|
1290
|
+
if (typeof message.id !== 'string') {
|
|
1291
|
+
debugWarn('Received invalid terminateApp result message:', message);
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
if (typeof message.error === 'string') {
|
|
1295
|
+
const terminateRejecter = pendingTerminateAppRejectersRef.current.get(message.id);
|
|
1296
|
+
if (!terminateRejecter) {
|
|
1297
|
+
debugWarn(`Received terminateApp error for unknown or handled id: ${message.id}`);
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
debugWarn(`Received terminateApp error for id ${message.id}: ${message.error}`);
|
|
1301
|
+
terminateRejecter(new Error(message.error));
|
|
1302
|
+
pendingTerminateAppResolversRef.current.delete(message.id);
|
|
1303
|
+
pendingTerminateAppRejectersRef.current.delete(message.id);
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
const terminateResolver = pendingTerminateAppResolversRef.current.get(message.id);
|
|
1307
|
+
if (!terminateResolver) {
|
|
1308
|
+
debugWarn(`Received terminateApp result for unknown or handled id: ${message.id}`);
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
debugLog(`Received terminateApp success for id ${message.id}`);
|
|
1312
|
+
terminateResolver();
|
|
1313
|
+
pendingTerminateAppResolversRef.current.delete(message.id);
|
|
1314
|
+
pendingTerminateAppRejectersRef.current.delete(message.id);
|
|
1315
|
+
break;
|
|
1276
1316
|
default:
|
|
1277
1317
|
debugWarn(`Received unhandled message type: ${message.type}`, message);
|
|
1278
1318
|
break;
|
|
@@ -1367,6 +1407,9 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
1367
1407
|
// Determine landscape based on video's intrinsic dimensions
|
|
1368
1408
|
const landscape = video.videoWidth > video.videoHeight;
|
|
1369
1409
|
setIsLandscape(landscape);
|
|
1410
|
+
setUseAndroidTabletFrame(
|
|
1411
|
+
platform === 'android' && isAndroidTabletVideo(video.videoWidth, video.videoHeight),
|
|
1412
|
+
);
|
|
1370
1413
|
|
|
1371
1414
|
const pos = landscape ? config.videoPosition.landscape : config.videoPosition.portrait;
|
|
1372
1415
|
let newStyle: React.CSSProperties = {};
|
|
@@ -1518,10 +1561,51 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
1518
1561
|
}, 30000); // 30-second timeout
|
|
1519
1562
|
});
|
|
1520
1563
|
},
|
|
1564
|
+
terminateApp: (bundleId: string): Promise<void> => {
|
|
1565
|
+
return new Promise<void>((resolve, reject) => {
|
|
1566
|
+
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
|
|
1567
|
+
debugWarn('WebSocket not open, cannot send terminateApp command.');
|
|
1568
|
+
return reject(new Error('WebSocket is not connected or connection is not open.'));
|
|
1569
|
+
}
|
|
1570
|
+
const id = `ui-term-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
1571
|
+
const request = {
|
|
1572
|
+
type: 'terminateApp',
|
|
1573
|
+
id,
|
|
1574
|
+
bundleId,
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1577
|
+
pendingTerminateAppResolversRef.current.set(id, resolve);
|
|
1578
|
+
pendingTerminateAppRejectersRef.current.set(id, reject);
|
|
1579
|
+
|
|
1580
|
+
debugLog('Sending terminateApp request:', request);
|
|
1581
|
+
try {
|
|
1582
|
+
wsRef.current.send(JSON.stringify(request));
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
debugWarn('Failed to send terminateApp request immediately:', err);
|
|
1585
|
+
pendingTerminateAppResolversRef.current.delete(id);
|
|
1586
|
+
pendingTerminateAppRejectersRef.current.delete(id);
|
|
1587
|
+
reject(err);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
setTimeout(() => {
|
|
1592
|
+
if (pendingTerminateAppResolversRef.current.has(id)) {
|
|
1593
|
+
debugWarn(`terminateApp request timed out for id ${id}`);
|
|
1594
|
+
pendingTerminateAppRejectersRef.current.get(id)?.(new Error('terminateApp request timed out'));
|
|
1595
|
+
pendingTerminateAppResolversRef.current.delete(id);
|
|
1596
|
+
pendingTerminateAppRejectersRef.current.delete(id);
|
|
1597
|
+
}
|
|
1598
|
+
}, 30000);
|
|
1599
|
+
});
|
|
1600
|
+
},
|
|
1521
1601
|
}));
|
|
1522
1602
|
|
|
1523
1603
|
// Show indicators when Alt is held and we have a valid hover point (null when outside)
|
|
1524
1604
|
const showAltIndicators = isAltHeld && hoverPoint !== null;
|
|
1605
|
+
const frameImageSrc =
|
|
1606
|
+
platform === 'android' && useAndroidTabletFrame
|
|
1607
|
+
? (isLandscape ? pixelTabletFrameImageLandscape : pixelTabletFrameImage)
|
|
1608
|
+
: (isLandscape ? config.frame.imageLandscape : config.frame.image);
|
|
1525
1609
|
|
|
1526
1610
|
return (
|
|
1527
1611
|
<div
|
|
@@ -1563,7 +1647,7 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
1563
1647
|
{showFrame && (
|
|
1564
1648
|
<img
|
|
1565
1649
|
ref={frameRef}
|
|
1566
|
-
src={
|
|
1650
|
+
src={frameImageSrc}
|
|
1567
1651
|
alt=""
|
|
1568
1652
|
className={platform === 'ios' ? clsx('rc-phone-frame', 'rc-phone-frame-ios') : 'rc-phone-frame'}
|
|
1569
1653
|
draggable={false}
|
package/src/index.ts
CHANGED