@moldable-ai/ui 0.2.18 → 0.2.19
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/chat/chat-panel.d.ts +18 -12
- package/dist/components/chat/chat-panel.d.ts.map +1 -1
- package/dist/components/chat/chat-panel.js +73 -19
- package/dist/components/chat/index.d.ts +2 -1
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +2 -1
- package/dist/components/chat/model-effort-power-effects.d.ts +7 -0
- package/dist/components/chat/model-effort-power-effects.d.ts.map +1 -0
- package/dist/components/chat/model-effort-power-effects.js +54 -0
- package/dist/components/chat/model-effort-selector.d.ts +26 -0
- package/dist/components/chat/model-effort-selector.d.ts.map +1 -0
- package/dist/components/chat/model-effort-selector.js +179 -0
- package/dist/components/chat/model-effort-ultra-effects.d.ts +5 -0
- package/dist/components/chat/model-effort-ultra-effects.d.ts.map +1 -0
- package/dist/components/chat/model-effort-ultra-effects.js +56 -0
- package/dist/components/hardware/bluetooth-panel-helpers.d.ts +9 -0
- package/dist/components/hardware/bluetooth-panel-helpers.d.ts.map +1 -0
- package/dist/components/hardware/bluetooth-panel-helpers.js +37 -0
- package/dist/components/hardware/bluetooth-panel.d.ts +56 -0
- package/dist/components/hardware/bluetooth-panel.d.ts.map +1 -0
- package/dist/components/hardware/bluetooth-panel.js +470 -0
- package/dist/components/hardware/camera-preview.d.ts +39 -0
- package/dist/components/hardware/camera-preview.d.ts.map +1 -0
- package/dist/components/hardware/camera-preview.js +228 -0
- package/dist/components/hardware/capability-badge.d.ts +23 -0
- package/dist/components/hardware/capability-badge.d.ts.map +1 -0
- package/dist/components/hardware/capability-badge.js +39 -0
- package/dist/components/hardware/capability-matrix.d.ts +14 -0
- package/dist/components/hardware/capability-matrix.d.ts.map +1 -0
- package/dist/components/hardware/capability-matrix.js +97 -0
- package/dist/components/hardware/clipboard-button.d.ts +26 -0
- package/dist/components/hardware/clipboard-button.d.ts.map +1 -0
- package/dist/components/hardware/clipboard-button.js +114 -0
- package/dist/components/hardware/device-io-controls.d.ts +4 -0
- package/dist/components/hardware/device-io-controls.d.ts.map +1 -0
- package/dist/components/hardware/device-io-controls.js +28 -0
- package/dist/components/hardware/device-list.d.ts +20 -0
- package/dist/components/hardware/device-list.d.ts.map +1 -0
- package/dist/components/hardware/device-list.js +14 -0
- package/dist/components/hardware/display-map.d.ts +23 -0
- package/dist/components/hardware/display-map.d.ts.map +1 -0
- package/dist/components/hardware/display-map.js +114 -0
- package/dist/components/hardware/haptic-button.d.ts +26 -0
- package/dist/components/hardware/haptic-button.d.ts.map +1 -0
- package/dist/components/hardware/haptic-button.js +94 -0
- package/dist/components/hardware/hardware-panel.d.ts +11 -0
- package/dist/components/hardware/hardware-panel.d.ts.map +1 -0
- package/dist/components/hardware/hardware-panel.js +28 -0
- package/dist/components/hardware/hid-panel.d.ts +40 -0
- package/dist/components/hardware/hid-panel.d.ts.map +1 -0
- package/dist/components/hardware/hid-panel.js +373 -0
- package/dist/components/hardware/index.d.ts +27 -0
- package/dist/components/hardware/index.d.ts.map +1 -0
- package/dist/components/hardware/index.js +26 -0
- package/dist/components/hardware/live-indicator.d.ts +15 -0
- package/dist/components/hardware/live-indicator.d.ts.map +1 -0
- package/dist/components/hardware/live-indicator.js +26 -0
- package/dist/components/hardware/local-auth-button.d.ts +33 -0
- package/dist/components/hardware/local-auth-button.d.ts.map +1 -0
- package/dist/components/hardware/local-auth-button.js +115 -0
- package/dist/components/hardware/location-panel.d.ts +29 -0
- package/dist/components/hardware/location-panel.d.ts.map +1 -0
- package/dist/components/hardware/location-panel.js +154 -0
- package/dist/components/hardware/microphone-meter.d.ts +35 -0
- package/dist/components/hardware/microphone-meter.d.ts.map +1 -0
- package/dist/components/hardware/microphone-meter.js +244 -0
- package/dist/components/hardware/midi-monitor.d.ts +54 -0
- package/dist/components/hardware/midi-monitor.d.ts.map +1 -0
- package/dist/components/hardware/midi-monitor.js +534 -0
- package/dist/components/hardware/notification-panel.d.ts +24 -0
- package/dist/components/hardware/notification-panel.d.ts.map +1 -0
- package/dist/components/hardware/notification-panel.js +124 -0
- package/dist/components/hardware/power-blocker-controller.d.ts +19 -0
- package/dist/components/hardware/power-blocker-controller.d.ts.map +1 -0
- package/dist/components/hardware/power-blocker-controller.js +83 -0
- package/dist/components/hardware/power-panel.d.ts +67 -0
- package/dist/components/hardware/power-panel.d.ts.map +1 -0
- package/dist/components/hardware/power-panel.js +309 -0
- package/dist/components/hardware/readout.d.ts +10 -0
- package/dist/components/hardware/readout.d.ts.map +1 -0
- package/dist/components/hardware/readout.js +11 -0
- package/dist/components/hardware/screen-share-preview.d.ts +36 -0
- package/dist/components/hardware/screen-share-preview.d.ts.map +1 -0
- package/dist/components/hardware/screen-share-preview.js +191 -0
- package/dist/components/hardware/secure-storage-panel.d.ts +36 -0
- package/dist/components/hardware/secure-storage-panel.d.ts.map +1 -0
- package/dist/components/hardware/secure-storage-panel.js +193 -0
- package/dist/components/hardware/serial-console.d.ts +64 -0
- package/dist/components/hardware/serial-console.d.ts.map +1 -0
- package/dist/components/hardware/serial-console.js +399 -0
- package/dist/components/hardware/shortcut-recorder.d.ts +35 -0
- package/dist/components/hardware/shortcut-recorder.d.ts.map +1 -0
- package/dist/components/hardware/shortcut-recorder.js +217 -0
- package/dist/components/hardware/stream-log.d.ts +35 -0
- package/dist/components/hardware/stream-log.d.ts.map +1 -0
- package/dist/components/hardware/stream-log.js +65 -0
- package/dist/components/hardware/system-audio-monitor.d.ts +34 -0
- package/dist/components/hardware/system-audio-monitor.d.ts.map +1 -0
- package/dist/components/hardware/system-audio-monitor.js +275 -0
- package/dist/components/hardware/usb-panel.d.ts +54 -0
- package/dist/components/hardware/usb-panel.d.ts.map +1 -0
- package/dist/components/hardware/usb-panel.js +363 -0
- package/dist/components/hardware/use-capability.d.ts +20 -0
- package/dist/components/hardware/use-capability.d.ts.map +1 -0
- package/dist/components/hardware/use-capability.js +61 -0
- package/dist/components/ui/input-group.d.ts +1 -1
- package/dist/components/ui/slider.d.ts +4 -1
- package/dist/components/ui/slider.d.ts.map +1 -1
- package/dist/components/ui/slider.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/native-hardware/bluetooth-access.d.ts +14 -0
- package/dist/lib/native-hardware/bluetooth-access.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth-access.js +40 -0
- package/dist/lib/native-hardware/bluetooth-connection.d.ts +19 -0
- package/dist/lib/native-hardware/bluetooth-connection.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth-connection.js +88 -0
- package/dist/lib/native-hardware/bluetooth-gatt.d.ts +7 -0
- package/dist/lib/native-hardware/bluetooth-gatt.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth-gatt.js +77 -0
- package/dist/lib/native-hardware/bluetooth-internal.d.ts +10 -0
- package/dist/lib/native-hardware/bluetooth-internal.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth-internal.js +105 -0
- package/dist/lib/native-hardware/bluetooth-native-fallback.d.ts +33 -0
- package/dist/lib/native-hardware/bluetooth-native-fallback.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth-native-fallback.js +214 -0
- package/dist/lib/native-hardware/bluetooth-types.d.ts +85 -0
- package/dist/lib/native-hardware/bluetooth-types.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth-types.js +2 -0
- package/dist/lib/native-hardware/bluetooth.d.ts +7 -0
- package/dist/lib/native-hardware/bluetooth.d.ts.map +1 -0
- package/dist/lib/native-hardware/bluetooth.js +4 -0
- package/dist/lib/native-hardware/bridge.d.ts +23 -0
- package/dist/lib/native-hardware/bridge.d.ts.map +1 -0
- package/dist/lib/native-hardware/bridge.js +266 -0
- package/dist/lib/native-hardware/capabilities.d.ts +7 -0
- package/dist/lib/native-hardware/capabilities.d.ts.map +1 -0
- package/dist/lib/native-hardware/capabilities.js +95 -0
- package/dist/lib/native-hardware/clipboard.d.ts +11 -0
- package/dist/lib/native-hardware/clipboard.d.ts.map +1 -0
- package/dist/lib/native-hardware/clipboard.js +55 -0
- package/dist/lib/native-hardware/device-io-errors.d.ts +11 -0
- package/dist/lib/native-hardware/device-io-errors.d.ts.map +1 -0
- package/dist/lib/native-hardware/device-io-errors.js +82 -0
- package/dist/lib/native-hardware/displays.d.ts +38 -0
- package/dist/lib/native-hardware/displays.d.ts.map +1 -0
- package/dist/lib/native-hardware/displays.js +12 -0
- package/dist/lib/native-hardware/global-shortcuts.d.ts +86 -0
- package/dist/lib/native-hardware/global-shortcuts.d.ts.map +1 -0
- package/dist/lib/native-hardware/global-shortcuts.js +351 -0
- package/dist/lib/native-hardware/haptics.d.ts +25 -0
- package/dist/lib/native-hardware/haptics.d.ts.map +1 -0
- package/dist/lib/native-hardware/haptics.js +18 -0
- package/dist/lib/native-hardware/hid-native.d.ts +26 -0
- package/dist/lib/native-hardware/hid-native.d.ts.map +1 -0
- package/dist/lib/native-hardware/hid-native.js +229 -0
- package/dist/lib/native-hardware/hid.d.ts +52 -0
- package/dist/lib/native-hardware/hid.d.ts.map +1 -0
- package/dist/lib/native-hardware/hid.js +160 -0
- package/dist/lib/native-hardware/index.d.ts +38 -0
- package/dist/lib/native-hardware/index.d.ts.map +1 -0
- package/dist/lib/native-hardware/index.js +20 -0
- package/dist/lib/native-hardware/local-authentication.d.ts +32 -0
- package/dist/lib/native-hardware/local-authentication.d.ts.map +1 -0
- package/dist/lib/native-hardware/local-authentication.js +46 -0
- package/dist/lib/native-hardware/location.d.ts +49 -0
- package/dist/lib/native-hardware/location.d.ts.map +1 -0
- package/dist/lib/native-hardware/location.js +114 -0
- package/dist/lib/native-hardware/media-permissions.d.ts +28 -0
- package/dist/lib/native-hardware/media-permissions.d.ts.map +1 -0
- package/dist/lib/native-hardware/media-permissions.js +37 -0
- package/dist/lib/native-hardware/media.d.ts +17 -0
- package/dist/lib/native-hardware/media.d.ts.map +1 -0
- package/dist/lib/native-hardware/media.js +99 -0
- package/dist/lib/native-hardware/midi-access.d.ts +13 -0
- package/dist/lib/native-hardware/midi-access.d.ts.map +1 -0
- package/dist/lib/native-hardware/midi-access.js +66 -0
- package/dist/lib/native-hardware/midi-internal.d.ts +12 -0
- package/dist/lib/native-hardware/midi-internal.d.ts.map +1 -0
- package/dist/lib/native-hardware/midi-internal.js +88 -0
- package/dist/lib/native-hardware/midi-native-fallback.d.ts +27 -0
- package/dist/lib/native-hardware/midi-native-fallback.d.ts.map +1 -0
- package/dist/lib/native-hardware/midi-native-fallback.js +127 -0
- package/dist/lib/native-hardware/midi-ports.d.ts +6 -0
- package/dist/lib/native-hardware/midi-ports.d.ts.map +1 -0
- package/dist/lib/native-hardware/midi-ports.js +115 -0
- package/dist/lib/native-hardware/midi-types.d.ts +25 -0
- package/dist/lib/native-hardware/midi-types.d.ts.map +1 -0
- package/dist/lib/native-hardware/midi-types.js +1 -0
- package/dist/lib/native-hardware/midi.d.ts +6 -0
- package/dist/lib/native-hardware/midi.d.ts.map +1 -0
- package/dist/lib/native-hardware/midi.js +3 -0
- package/dist/lib/native-hardware/native-device-io.d.ts +28 -0
- package/dist/lib/native-hardware/native-device-io.d.ts.map +1 -0
- package/dist/lib/native-hardware/native-device-io.js +91 -0
- package/dist/lib/native-hardware/native-device-lifecycle.d.ts +13 -0
- package/dist/lib/native-hardware/native-device-lifecycle.d.ts.map +1 -0
- package/dist/lib/native-hardware/native-device-lifecycle.js +40 -0
- package/dist/lib/native-hardware/notifications.d.ts +38 -0
- package/dist/lib/native-hardware/notifications.d.ts.map +1 -0
- package/dist/lib/native-hardware/notifications.js +50 -0
- package/dist/lib/native-hardware/power-session.d.ts +113 -0
- package/dist/lib/native-hardware/power-session.d.ts.map +1 -0
- package/dist/lib/native-hardware/power-session.js +264 -0
- package/dist/lib/native-hardware/secure-storage.d.ts +36 -0
- package/dist/lib/native-hardware/secure-storage.d.ts.map +1 -0
- package/dist/lib/native-hardware/secure-storage.js +64 -0
- package/dist/lib/native-hardware/serial-native.d.ts +23 -0
- package/dist/lib/native-hardware/serial-native.d.ts.map +1 -0
- package/dist/lib/native-hardware/serial-native.js +181 -0
- package/dist/lib/native-hardware/serial.d.ts +76 -0
- package/dist/lib/native-hardware/serial.d.ts.map +1 -0
- package/dist/lib/native-hardware/serial.js +212 -0
- package/dist/lib/native-hardware/system-audio.d.ts +127 -0
- package/dist/lib/native-hardware/system-audio.d.ts.map +1 -0
- package/dist/lib/native-hardware/system-audio.js +330 -0
- package/dist/lib/native-hardware/types.d.ts +62 -0
- package/dist/lib/native-hardware/types.d.ts.map +1 -0
- package/dist/lib/native-hardware/types.js +27 -0
- package/dist/lib/native-hardware/usb-native.d.ts +39 -0
- package/dist/lib/native-hardware/usb-native.d.ts.map +1 -0
- package/dist/lib/native-hardware/usb-native.js +217 -0
- package/dist/lib/native-hardware/usb.d.ts +85 -0
- package/dist/lib/native-hardware/usb.d.ts.map +1 -0
- package/dist/lib/native-hardware/usb.js +222 -0
- package/package.json +1 -1
- package/src/styles/index.css +377 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location-panel.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/location-panel.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EACL,KAAK,kCAAkC,EACvC,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAK5B,MAAM,2BAA2B,CAAA;AAelC,MAAM,MAAM,sBAAsB,GAC9B,MAAM,GACN,UAAU,GACV,SAAS,GACT,QAAQ,GACR,OAAO,CAAA;AAEX;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,uBAA4B;;;;;;EAyDxE;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAIpE;AAMD,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACrE,iEAAiE;IACjE,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,8BAA8B;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAA;CACxD;AAED;;;GAGG;AACH,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAU,EACV,GAAG,KAAK,EACT,EAAE,kBAAkB,2CA8MpB;AAED,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Check, Copy, LocateFixed, MapPin } from 'lucide-react';
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { NativeHardwareBridgeError, getMoldableCurrentPosition, getMoldableLocationAuthorizationStatus, writeMoldableClipboardText, } from '../../lib/native-hardware';
|
|
6
|
+
import { Button } from '../ui/button';
|
|
7
|
+
import { Spinner } from '../ui/spinner';
|
|
8
|
+
import { CapabilityBadge } from './capability-badge';
|
|
9
|
+
import { HardwarePanel, HardwarePanelAction, HardwarePanelContent, HardwarePanelFooter, HardwarePanelHeader, HardwarePanelIcon, HardwarePanelTitle, } from './hardware-panel';
|
|
10
|
+
import { Readout, ReadoutItem } from './readout';
|
|
11
|
+
/**
|
|
12
|
+
* One-shot location state machine behind `LocationPanel`. Use directly to
|
|
13
|
+
* build custom location UI on the same authorization/error handling.
|
|
14
|
+
*/
|
|
15
|
+
export function useMoldableLocation(options = {}) {
|
|
16
|
+
const [status, setStatus] = useState('idle');
|
|
17
|
+
const [position, setPosition] = useState(null);
|
|
18
|
+
const [authorization, setAuthorization] = useState(null);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const optionsRef = useRef(options);
|
|
21
|
+
const tokenRef = useRef(0);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
optionsRef.current = options;
|
|
24
|
+
}, [options]);
|
|
25
|
+
const locate = useCallback(async () => {
|
|
26
|
+
const token = ++tokenRef.current;
|
|
27
|
+
setStatus('locating');
|
|
28
|
+
setPosition(null);
|
|
29
|
+
setAuthorization(null);
|
|
30
|
+
setError(null);
|
|
31
|
+
void getMoldableLocationAuthorizationStatus()
|
|
32
|
+
.then((state) => {
|
|
33
|
+
if (tokenRef.current === token)
|
|
34
|
+
setAuthorization(state);
|
|
35
|
+
})
|
|
36
|
+
.catch(() => {
|
|
37
|
+
// Authorization detail is best-effort outside Moldable.
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
const result = await getMoldableCurrentPosition({
|
|
41
|
+
enableHighAccuracy: true,
|
|
42
|
+
timeout: 10_000,
|
|
43
|
+
...optionsRef.current,
|
|
44
|
+
});
|
|
45
|
+
if (tokenRef.current !== token)
|
|
46
|
+
return;
|
|
47
|
+
setPosition(result);
|
|
48
|
+
setStatus('success');
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
catch (cause) {
|
|
52
|
+
if (tokenRef.current !== token)
|
|
53
|
+
return null;
|
|
54
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
55
|
+
setStatus(cause instanceof NativeHardwareBridgeError &&
|
|
56
|
+
cause.code === 'permission_denied'
|
|
57
|
+
? 'denied'
|
|
58
|
+
: 'error');
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
useEffect(() => () => {
|
|
63
|
+
tokenRef.current++;
|
|
64
|
+
}, []);
|
|
65
|
+
return { status, position, authorization, error, locate };
|
|
66
|
+
}
|
|
67
|
+
function formatCoordinate(value, axis) {
|
|
68
|
+
const hemisphere = axis === 'lat' ? (value >= 0 ? 'N' : 'S') : value >= 0 ? 'E' : 'W';
|
|
69
|
+
return `${Math.abs(value).toFixed(5)}° ${hemisphere}`;
|
|
70
|
+
}
|
|
71
|
+
function formatClock(timestamp) {
|
|
72
|
+
return new Date(timestamp).toLocaleTimeString(undefined, { hour12: false });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Current-position panel: authorization state, one-shot fix, mono coordinate
|
|
76
|
+
* readout with copy, accuracy/altitude/speed details.
|
|
77
|
+
*/
|
|
78
|
+
function LocationPanel({ className, options, autoLocate = false, onLocation, ...props }) {
|
|
79
|
+
const location = useMoldableLocation(options);
|
|
80
|
+
const [copied, setCopied] = useState(false);
|
|
81
|
+
const [copyError, setCopyError] = useState(null);
|
|
82
|
+
const autoLocatedRef = useRef(false);
|
|
83
|
+
const copyTokenRef = useRef(0);
|
|
84
|
+
const copyTimeoutRef = useRef(null);
|
|
85
|
+
const { locate } = location;
|
|
86
|
+
const runLocate = useCallback(async () => {
|
|
87
|
+
const result = await locate();
|
|
88
|
+
if (result)
|
|
89
|
+
onLocation?.(result);
|
|
90
|
+
}, [locate, onLocation]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (autoLocate && !autoLocatedRef.current) {
|
|
93
|
+
autoLocatedRef.current = true;
|
|
94
|
+
void runLocate();
|
|
95
|
+
}
|
|
96
|
+
}, [autoLocate, runLocate]);
|
|
97
|
+
const { position } = location;
|
|
98
|
+
useEffect(() => () => {
|
|
99
|
+
copyTokenRef.current++;
|
|
100
|
+
if (copyTimeoutRef.current !== null) {
|
|
101
|
+
window.clearTimeout(copyTimeoutRef.current);
|
|
102
|
+
}
|
|
103
|
+
}, []);
|
|
104
|
+
const copyCoordinates = async () => {
|
|
105
|
+
if (!position)
|
|
106
|
+
return;
|
|
107
|
+
const token = ++copyTokenRef.current;
|
|
108
|
+
const text = `${position.coords.latitude}, ${position.coords.longitude}`;
|
|
109
|
+
setCopied(false);
|
|
110
|
+
setCopyError(null);
|
|
111
|
+
try {
|
|
112
|
+
await writeMoldableClipboardText(text);
|
|
113
|
+
}
|
|
114
|
+
catch (nativeCause) {
|
|
115
|
+
try {
|
|
116
|
+
await navigator.clipboard.writeText(text);
|
|
117
|
+
}
|
|
118
|
+
catch (browserCause) {
|
|
119
|
+
if (copyTokenRef.current !== token)
|
|
120
|
+
return;
|
|
121
|
+
const cause = browserCause instanceof Error
|
|
122
|
+
? browserCause
|
|
123
|
+
: nativeCause instanceof Error
|
|
124
|
+
? nativeCause
|
|
125
|
+
: new Error('Could not copy coordinates.');
|
|
126
|
+
setCopyError(cause);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (copyTokenRef.current !== token)
|
|
131
|
+
return;
|
|
132
|
+
setCopied(true);
|
|
133
|
+
if (copyTimeoutRef.current !== null) {
|
|
134
|
+
window.clearTimeout(copyTimeoutRef.current);
|
|
135
|
+
}
|
|
136
|
+
copyTimeoutRef.current = window.setTimeout(() => {
|
|
137
|
+
setCopied(false);
|
|
138
|
+
copyTimeoutRef.current = null;
|
|
139
|
+
}, 1500);
|
|
140
|
+
};
|
|
141
|
+
const locating = location.status === 'locating';
|
|
142
|
+
return (_jsxs(HardwarePanel, { "data-slot": "location-panel", "data-status": location.status, className: className, ...props, children: [_jsxs(HardwarePanelHeader, { children: [_jsx(HardwarePanelIcon, { children: _jsx(MapPin, {}) }), _jsx(HardwarePanelTitle, { children: "Location" }), _jsx(CapabilityBadge, { capability: "location" }), _jsx(HardwarePanelAction, { children: _jsxs(Button, { size: "sm", variant: position ? 'outline' : 'default', disabled: locating, onClick: () => void runLocate(), children: [locating ? _jsx(Spinner, {}) : _jsx(LocateFixed, {}), locating ? 'Locating…' : position ? 'Update' : 'Locate'] }) })] }), _jsx(HardwarePanelContent, { children: position ? (_jsxs("div", { className: "animate-in fade-in space-y-4 duration-300 motion-reduce:animate-none", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "font-mono text-xl font-medium tabular-nums tracking-tight sm:text-2xl", children: [formatCoordinate(position.coords.latitude, 'lat'), _jsx("span", { className: "text-muted-foreground/50 mx-2", children: "\u00B7" }), formatCoordinate(position.coords.longitude, 'lng')] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": copied
|
|
143
|
+
? 'Coordinates copied'
|
|
144
|
+
: copyError
|
|
145
|
+
? 'Copy coordinates failed; try again'
|
|
146
|
+
: 'Copy coordinates', "data-copy-status": copied ? 'success' : copyError ? 'error' : 'idle', title: copyError?.message, onClick: () => void copyCoordinates(), children: copied ? _jsx(Check, { className: "text-success" }) : _jsx(Copy, {}) })] }), _jsxs(Readout, { children: [_jsx(ReadoutItem, { label: "Accuracy", value: `± ${Math.round(position.coords.accuracy)}`, unit: "m" }), _jsx(ReadoutItem, { label: "Altitude", value: position.coords.altitude === null
|
|
147
|
+
? null
|
|
148
|
+
: Math.round(position.coords.altitude), unit: "m" }), _jsx(ReadoutItem, { label: "Speed", value: position.coords.speed === null
|
|
149
|
+
? null
|
|
150
|
+
: position.coords.speed.toFixed(1), unit: "m/s" })] })] })) : (_jsx("div", { className: "flex flex-col items-center gap-3 py-6 text-center", children: locating ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "relative flex size-10 items-center justify-center", children: [_jsx("span", { className: "border-primary/30 absolute inline-flex size-10 animate-ping rounded-full border motion-reduce:animate-none" }), _jsx("span", { className: "text-muted-foreground bg-background shadow-xs relative flex size-10 items-center justify-center rounded-full border [&_svg]:size-4", children: _jsx(LocateFixed, {}) })] }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Getting your position\u2026" })] })) : location.status === 'denied' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "Location access denied" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: "Enable Location Services for Moldable in System Settings, then try again." })] })) : location.status === 'error' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "Couldn't get a fix" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: location.error?.message ?? 'Location request failed.' })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground bg-background shadow-xs flex size-10 items-center justify-center rounded-lg border [&_svg]:size-5", children: _jsx(MapPin, {}) }), _jsx("p", { className: "text-muted-foreground text-xs", children: "No position yet" })] })) })) }), _jsxs(HardwarePanelFooter, { children: [_jsx("span", { className: "truncate", children: location.authorization
|
|
151
|
+
? `Authorization ${location.authorization.authorization}${location.authorization.servicesEnabled ? '' : ' · services off'}`
|
|
152
|
+
: 'Authorization unknown' }), position ? (_jsx("span", { className: "ml-auto shrink-0 font-mono tabular-nums", children: formatClock(position.timestamp) })) : null] })] }));
|
|
153
|
+
}
|
|
154
|
+
export { LocationPanel, formatCoordinate };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type MoldableMicrophoneStatus = 'idle' | 'starting' | 'live' | 'muted' | 'denied' | 'error';
|
|
3
|
+
export interface UseMoldableMicrophoneOptions {
|
|
4
|
+
/** Extra audio constraints merged over the processing defaults. */
|
|
5
|
+
constraints?: MediaTrackConstraints;
|
|
6
|
+
onStreamChange?: (stream: MediaStream | null) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Microphone state machine behind `MicrophoneMeter`. Use directly to build
|
|
10
|
+
* custom audio UI on the same permission/device/stream handling.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useMoldableMicrophone(options?: UseMoldableMicrophoneOptions): {
|
|
13
|
+
status: MoldableMicrophoneStatus;
|
|
14
|
+
stream: MediaStream | null;
|
|
15
|
+
devices: MediaDeviceInfo[];
|
|
16
|
+
deviceId: string | null;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
start: (nextDeviceId?: string) => Promise<void>;
|
|
19
|
+
stop: () => void;
|
|
20
|
+
selectDevice: (id: string) => void;
|
|
21
|
+
refreshDevices: () => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export interface MicrophoneMeterProps extends React.ComponentProps<'div'> {
|
|
24
|
+
constraints?: MediaTrackConstraints;
|
|
25
|
+
/** Start the microphone on mount. */
|
|
26
|
+
autoStart?: boolean;
|
|
27
|
+
onStreamChange?: (stream: MediaStream | null) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Microphone panel: permission flow, device switching, and a live frequency
|
|
31
|
+
* visualizer driven by an `AnalyserNode` without re-rendering per frame.
|
|
32
|
+
*/
|
|
33
|
+
declare function MicrophoneMeter({ className, constraints, autoStart, onStreamChange, ...props }: MicrophoneMeterProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export { MicrophoneMeter };
|
|
35
|
+
//# sourceMappingURL=microphone-meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"microphone-meter.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/microphone-meter.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA+B9B,MAAM,MAAM,wBAAwB,GAChC,MAAM,GACN,UAAU,GACV,MAAM,GACN,OAAO,GACP,QAAQ,GACR,OAAO,CAAA;AAEX,MAAM,WAAW,4BAA4B;IAC3C,mEAAmE;IACnE,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CACtD;AAuBD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC;;;;;;2BAgDlB,MAAM;;uBA4EvB,MAAM;;EA2Bd;AAKD,MAAM,WAAW,oBAAqB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACvE,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,qCAAqC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CACtD;AAED;;;GAGG;AACH,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,WAAW,EACX,SAAiB,EACjB,cAAc,EACd,GAAG,KAAK,EACT,EAAE,oBAAoB,2CAiNtB;AAED,OAAO,EAAE,eAAe,EAAE,CAAA"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Mic, MicOff, RefreshCw, Square } from 'lucide-react';
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { NativeHardwareBridgeError, enumerateMoldableMediaDevices, getMoldableMediaPermissionStatus, getMoldableMicrophoneStream, requestMoldableMediaPermission, stopMoldableMediaStream, } from '../../lib/native-hardware';
|
|
6
|
+
import { Button } from '../ui/button';
|
|
7
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/select';
|
|
8
|
+
import { Spinner } from '../ui/spinner';
|
|
9
|
+
import { CapabilityBadge } from './capability-badge';
|
|
10
|
+
import { HardwarePanel, HardwarePanelAction, HardwarePanelContent, HardwarePanelFooter, HardwarePanelHeader, HardwarePanelIcon, HardwarePanelTitle, } from './hardware-panel';
|
|
11
|
+
import { LiveIndicator } from './live-indicator';
|
|
12
|
+
function isPermissionDenied(error) {
|
|
13
|
+
if (error instanceof NativeHardwareBridgeError) {
|
|
14
|
+
return error.code === 'permission_denied';
|
|
15
|
+
}
|
|
16
|
+
return (error instanceof DOMException &&
|
|
17
|
+
(error.name === 'NotAllowedError' || error.name === 'SecurityError'));
|
|
18
|
+
}
|
|
19
|
+
function assertMediaPermissionGranted(status) {
|
|
20
|
+
if (status.status !== 'denied' && status.status !== 'restricted')
|
|
21
|
+
return;
|
|
22
|
+
throw new NativeHardwareBridgeError({
|
|
23
|
+
code: 'permission_denied',
|
|
24
|
+
message: status.detail ?? 'Microphone access was denied.',
|
|
25
|
+
retryable: false,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Microphone state machine behind `MicrophoneMeter`. Use directly to build
|
|
30
|
+
* custom audio UI on the same permission/device/stream handling.
|
|
31
|
+
*/
|
|
32
|
+
export function useMoldableMicrophone(options = {}) {
|
|
33
|
+
const [status, setStatus] = useState('idle');
|
|
34
|
+
const [stream, setStream] = useState(null);
|
|
35
|
+
const [devices, setDevices] = useState([]);
|
|
36
|
+
const [deviceId, setDeviceIdState] = useState(null);
|
|
37
|
+
const [error, setError] = useState(null);
|
|
38
|
+
const streamRef = useRef(null);
|
|
39
|
+
const deviceIdRef = useRef(null);
|
|
40
|
+
const tokenRef = useRef(0);
|
|
41
|
+
const constraintsRef = useRef(options.constraints);
|
|
42
|
+
const onStreamChangeRef = useRef(options.onStreamChange);
|
|
43
|
+
const detachTrackListenersRef = useRef(null);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
constraintsRef.current = options.constraints;
|
|
46
|
+
onStreamChangeRef.current = options.onStreamChange;
|
|
47
|
+
}, [options.constraints, options.onStreamChange]);
|
|
48
|
+
const dropStream = useCallback((updateState = true) => {
|
|
49
|
+
detachTrackListenersRef.current?.();
|
|
50
|
+
detachTrackListenersRef.current = null;
|
|
51
|
+
const previousStream = streamRef.current;
|
|
52
|
+
if (!previousStream)
|
|
53
|
+
return false;
|
|
54
|
+
streamRef.current = null;
|
|
55
|
+
stopMoldableMediaStream(previousStream);
|
|
56
|
+
if (updateState)
|
|
57
|
+
setStream(null);
|
|
58
|
+
onStreamChangeRef.current?.(null);
|
|
59
|
+
return true;
|
|
60
|
+
}, []);
|
|
61
|
+
const stop = useCallback(() => {
|
|
62
|
+
tokenRef.current++;
|
|
63
|
+
dropStream();
|
|
64
|
+
setStatus('idle');
|
|
65
|
+
}, [dropStream]);
|
|
66
|
+
const refreshDevices = useCallback(async () => {
|
|
67
|
+
try {
|
|
68
|
+
const all = await enumerateMoldableMediaDevices('microphone');
|
|
69
|
+
setDevices(all.filter((device) => device.kind === 'audioinput'));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Device labels are best-effort; enumeration can fail outside Moldable.
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
const start = useCallback(async (nextDeviceId) => {
|
|
76
|
+
const token = ++tokenRef.current;
|
|
77
|
+
dropStream();
|
|
78
|
+
setStatus('starting');
|
|
79
|
+
setError(null);
|
|
80
|
+
try {
|
|
81
|
+
// Nudge the native permission first so macOS prompts before the
|
|
82
|
+
// webview does. Outside Moldable this fails and the browser prompt
|
|
83
|
+
// covers it on getUserMedia.
|
|
84
|
+
try {
|
|
85
|
+
const permission = await getMoldableMediaPermissionStatus('microphone');
|
|
86
|
+
assertMediaPermissionGranted(permission);
|
|
87
|
+
if (permission.status === 'not-determined' && permission.canRequest) {
|
|
88
|
+
assertMediaPermissionGranted(await requestMoldableMediaPermission('microphone'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
if (isPermissionDenied(cause))
|
|
93
|
+
throw cause;
|
|
94
|
+
// Not in Moldable — proceed straight to the stream request.
|
|
95
|
+
}
|
|
96
|
+
if (tokenRef.current !== token)
|
|
97
|
+
return;
|
|
98
|
+
const target = nextDeviceId ?? deviceIdRef.current;
|
|
99
|
+
const media = await getMoldableMicrophoneStream({
|
|
100
|
+
echoCancellation: true,
|
|
101
|
+
noiseSuppression: true,
|
|
102
|
+
...constraintsRef.current,
|
|
103
|
+
...(target ? { deviceId: { exact: target } } : {}),
|
|
104
|
+
});
|
|
105
|
+
if (tokenRef.current !== token) {
|
|
106
|
+
stopMoldableMediaStream(media);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
streamRef.current = media;
|
|
110
|
+
const [track] = media.getAudioTracks();
|
|
111
|
+
if (track) {
|
|
112
|
+
const handleEnded = () => {
|
|
113
|
+
if (tokenRef.current !== token)
|
|
114
|
+
return;
|
|
115
|
+
tokenRef.current++;
|
|
116
|
+
dropStream();
|
|
117
|
+
setStatus('idle');
|
|
118
|
+
};
|
|
119
|
+
const handleMute = () => {
|
|
120
|
+
if (tokenRef.current === token)
|
|
121
|
+
setStatus('muted');
|
|
122
|
+
};
|
|
123
|
+
const handleUnmute = () => {
|
|
124
|
+
if (tokenRef.current === token)
|
|
125
|
+
setStatus('live');
|
|
126
|
+
};
|
|
127
|
+
track.addEventListener('ended', handleEnded, { once: true });
|
|
128
|
+
track.addEventListener('mute', handleMute);
|
|
129
|
+
track.addEventListener('unmute', handleUnmute);
|
|
130
|
+
detachTrackListenersRef.current = () => {
|
|
131
|
+
track.removeEventListener('ended', handleEnded);
|
|
132
|
+
track.removeEventListener('mute', handleMute);
|
|
133
|
+
track.removeEventListener('unmute', handleUnmute);
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
setStream(media);
|
|
137
|
+
if (nextDeviceId !== undefined) {
|
|
138
|
+
deviceIdRef.current = nextDeviceId;
|
|
139
|
+
setDeviceIdState(nextDeviceId);
|
|
140
|
+
}
|
|
141
|
+
setStatus('live');
|
|
142
|
+
onStreamChangeRef.current?.(media);
|
|
143
|
+
void refreshDevices();
|
|
144
|
+
}
|
|
145
|
+
catch (cause) {
|
|
146
|
+
if (tokenRef.current !== token)
|
|
147
|
+
return;
|
|
148
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
149
|
+
setStatus(isPermissionDenied(cause) ? 'denied' : 'error');
|
|
150
|
+
}
|
|
151
|
+
}, [dropStream, refreshDevices]);
|
|
152
|
+
const selectDevice = useCallback((id) => {
|
|
153
|
+
deviceIdRef.current = id;
|
|
154
|
+
setDeviceIdState(id);
|
|
155
|
+
if (streamRef.current)
|
|
156
|
+
void start(id);
|
|
157
|
+
}, [start]);
|
|
158
|
+
useEffect(() => () => {
|
|
159
|
+
tokenRef.current++;
|
|
160
|
+
dropStream(false);
|
|
161
|
+
}, [dropStream]);
|
|
162
|
+
return {
|
|
163
|
+
status,
|
|
164
|
+
stream,
|
|
165
|
+
devices,
|
|
166
|
+
deviceId,
|
|
167
|
+
error,
|
|
168
|
+
start,
|
|
169
|
+
stop,
|
|
170
|
+
selectDevice,
|
|
171
|
+
refreshDevices,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const BAR_COUNT = 56;
|
|
175
|
+
const BAR_REST_SCALE = 0.06;
|
|
176
|
+
/**
|
|
177
|
+
* Microphone panel: permission flow, device switching, and a live frequency
|
|
178
|
+
* visualizer driven by an `AnalyserNode` without re-rendering per frame.
|
|
179
|
+
*/
|
|
180
|
+
function MicrophoneMeter({ className, constraints, autoStart = false, onStreamChange, ...props }) {
|
|
181
|
+
const microphone = useMoldableMicrophone({ constraints, onStreamChange });
|
|
182
|
+
const barsRef = useRef([]);
|
|
183
|
+
const autoStartedRef = useRef(false);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (autoStart && !autoStartedRef.current) {
|
|
186
|
+
autoStartedRef.current = true;
|
|
187
|
+
void microphone.start();
|
|
188
|
+
}
|
|
189
|
+
}, [autoStart, microphone]);
|
|
190
|
+
const { stream } = microphone;
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!stream || typeof AudioContext === 'undefined')
|
|
193
|
+
return;
|
|
194
|
+
const bars = barsRef.current;
|
|
195
|
+
const context = new AudioContext();
|
|
196
|
+
const analyser = context.createAnalyser();
|
|
197
|
+
analyser.fftSize = 256;
|
|
198
|
+
analyser.smoothingTimeConstant = 0.7;
|
|
199
|
+
const source = context.createMediaStreamSource(stream);
|
|
200
|
+
source.connect(analyser);
|
|
201
|
+
const bins = new Uint8Array(analyser.frequencyBinCount);
|
|
202
|
+
const step = analyser.frequencyBinCount / BAR_COUNT;
|
|
203
|
+
let frame = 0;
|
|
204
|
+
const draw = () => {
|
|
205
|
+
analyser.getByteFrequencyData(bins);
|
|
206
|
+
for (let index = 0; index < BAR_COUNT; index++) {
|
|
207
|
+
const bar = bars[index];
|
|
208
|
+
if (!bar)
|
|
209
|
+
continue;
|
|
210
|
+
const value = bins[Math.floor(index * step)] ?? 0;
|
|
211
|
+
const scale = Math.max(BAR_REST_SCALE, value / 255);
|
|
212
|
+
bar.style.transform = `scaleY(${scale.toFixed(3)})`;
|
|
213
|
+
}
|
|
214
|
+
frame = requestAnimationFrame(draw);
|
|
215
|
+
};
|
|
216
|
+
frame = requestAnimationFrame(draw);
|
|
217
|
+
return () => {
|
|
218
|
+
cancelAnimationFrame(frame);
|
|
219
|
+
source.disconnect();
|
|
220
|
+
void context.close().catch(() => {
|
|
221
|
+
// The context may already be closed by the browser.
|
|
222
|
+
});
|
|
223
|
+
for (const bar of bars) {
|
|
224
|
+
if (bar)
|
|
225
|
+
bar.style.transform = `scaleY(${BAR_REST_SCALE})`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}, [stream]);
|
|
229
|
+
const live = microphone.status === 'live' || microphone.status === 'muted';
|
|
230
|
+
const starting = microphone.status === 'starting';
|
|
231
|
+
const settings = live ? stream?.getAudioTracks()[0]?.getSettings() : undefined;
|
|
232
|
+
return (_jsxs(HardwarePanel, { "data-slot": "microphone-meter", "data-status": microphone.status, className: className, ...props, children: [_jsxs(HardwarePanelHeader, { children: [_jsx(HardwarePanelIcon, { children: _jsx(Mic, {}) }), _jsx(HardwarePanelTitle, { children: "Microphone" }), _jsx(CapabilityBadge, { capability: "microphone" }), _jsx(HardwarePanelAction, { children: _jsxs(Button, { size: "sm", variant: live ? 'outline' : 'default', disabled: starting, onClick: () => {
|
|
233
|
+
if (live)
|
|
234
|
+
microphone.stop();
|
|
235
|
+
else
|
|
236
|
+
void microphone.start();
|
|
237
|
+
}, children: [starting ? (_jsx(Spinner, {})) : live ? (_jsx(Square, { className: "fill-current" })) : (_jsx(Mic, {})), starting ? 'Starting…' : live ? 'Stop' : 'Start'] }) })] }), _jsx(HardwarePanelContent, { children: live ? (_jsxs("div", { className: "animate-in fade-in space-y-3 duration-300 motion-reduce:animate-none", children: [_jsxs("div", { className: "bg-muted/30 relative overflow-hidden rounded-lg border p-3", children: [_jsx("div", { className: "absolute left-2.5 top-2.5 z-10", children: _jsx(LiveIndicator, { label: microphone.status === 'muted' ? 'Muted' : undefined }) }), _jsx("div", { "aria-hidden": true, className: "flex h-24 items-stretch gap-[3px]", children: Array.from({ length: BAR_COUNT }, (_, index) => (_jsxs("div", { className: "relative min-w-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "bg-muted-foreground/15 absolute inset-0" }), _jsx("div", { ref: (node) => {
|
|
238
|
+
barsRef.current[index] = node;
|
|
239
|
+
}, className: "bg-primary absolute inset-0 origin-bottom transition-transform duration-75 ease-out motion-reduce:transition-none", style: { transform: `scaleY(${BAR_REST_SCALE})` } })] }, index))) })] }), microphone.devices.length > 1 ? (_jsxs(Select, { value: microphone.deviceId ?? undefined, onValueChange: microphone.selectDevice, children: [_jsx(SelectTrigger, { size: "sm", className: "w-full", children: _jsx(SelectValue, { placeholder: "Microphone" }) }), _jsx(SelectContent, { children: microphone.devices.map((device, index) => (_jsx(SelectItem, { value: device.deviceId, children: device.label || `Microphone ${index + 1}` }, device.deviceId))) })] })) : null] })) : (_jsx("div", { className: "animate-in fade-in flex flex-col items-center justify-center gap-3 py-6 text-center duration-300 motion-reduce:animate-none", children: starting ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "text-muted-foreground" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Starting microphone\u2026" })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground bg-background shadow-xs flex size-10 items-center justify-center rounded-lg border [&_svg]:size-5", children: microphone.status === 'idle' ? _jsx(Mic, {}) : _jsx(MicOff, {}) }), microphone.status === 'idle' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Microphone is off" }), _jsxs(Button, { size: "sm", onClick: () => void microphone.start(), children: [_jsx(Mic, {}), " Start microphone"] })] })) : microphone.status === 'denied' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "Microphone access denied" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: "Enable microphone access for Moldable in System Settings, then try again." }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void microphone.start(), children: [_jsx(RefreshCw, {}), " Try again"] })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "Microphone unavailable" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: microphone.error?.message ??
|
|
240
|
+
'The microphone could not be started.' }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void microphone.start(), children: [_jsx(RefreshCw, {}), " Try again"] })] }))] })) })) }), _jsxs(HardwarePanelFooter, { children: [_jsx("span", { className: "font-mono tabular-nums", children: settings?.sampleRate
|
|
241
|
+
? `${settings.sampleRate.toLocaleString()} Hz`
|
|
242
|
+
: '— Hz' }), _jsx("span", { className: "ml-auto shrink-0 font-mono tabular-nums", children: settings?.channelCount ? `${settings.channelCount} ch` : '— ch' })] })] }));
|
|
243
|
+
}
|
|
244
|
+
export { MicrophoneMeter };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type MoldableMIDITransport } from '../../lib/native-hardware';
|
|
3
|
+
export type MoldableMidiStatus = 'idle' | 'connecting' | 'connected' | 'error';
|
|
4
|
+
/**
|
|
5
|
+
* Human-readable summary of a raw MIDI message: note on/off with pitch name,
|
|
6
|
+
* control change, program change, and pitch bend; anything else as hex.
|
|
7
|
+
*/
|
|
8
|
+
declare function formatMidiMessage(data: Uint8Array | number[]): string;
|
|
9
|
+
export interface MoldableMidiPortInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
manufacturer?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface UseMoldableMidiOptions {
|
|
15
|
+
onMessage?: (data: Uint8Array, portId: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* MIDI state machine behind `MidiMonitor`. Handles both transports returned
|
|
19
|
+
* by `requestMoldableMIDIWithFallback`: Web MIDI (full port enumeration and
|
|
20
|
+
* state observation) and the desktop bridge (host-owned per-port chooser via
|
|
21
|
+
* `requestPort`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function useMoldableMidi(options?: UseMoldableMidiOptions): {
|
|
24
|
+
status: MoldableMidiStatus;
|
|
25
|
+
access: MoldableMIDITransport | null;
|
|
26
|
+
inputs: MoldableMidiPortInfo[];
|
|
27
|
+
outputs: MoldableMidiPortInfo[];
|
|
28
|
+
openInputIds: string[];
|
|
29
|
+
sysexEnabled: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
entries: import("./stream-log").StreamLogEntry[];
|
|
32
|
+
connect: (connectOptions?: {
|
|
33
|
+
sysex?: boolean;
|
|
34
|
+
}) => Promise<void>;
|
|
35
|
+
disconnect: () => Promise<void>;
|
|
36
|
+
requestPort: (kind: "input" | "output") => Promise<void>;
|
|
37
|
+
openInput: (id: string) => Promise<void>;
|
|
38
|
+
closeInput: (id: string) => Promise<void>;
|
|
39
|
+
sendNote: (outputId: string, note?: number, velocity?: number) => Promise<void>;
|
|
40
|
+
sendSystemExclusive: (outputId: string, data?: Iterable<number>) => Promise<void>;
|
|
41
|
+
clearEntries: () => void;
|
|
42
|
+
};
|
|
43
|
+
export interface MidiMonitorProps extends React.ComponentProps<'div'> {
|
|
44
|
+
/** Request System Exclusive access when connecting. */
|
|
45
|
+
sysex?: boolean;
|
|
46
|
+
onMessage?: (data: Uint8Array, portId: string) => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* MIDI monitor panel: connect either transport, monitor inputs, fire a test
|
|
50
|
+
* note at outputs, and read a live parsed-message log.
|
|
51
|
+
*/
|
|
52
|
+
declare function MidiMonitor({ className, sysex, onMessage, ...props }: MidiMonitorProps): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export { MidiMonitor, formatMidiMessage };
|
|
54
|
+
//# sourceMappingURL=midi-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"midi-monitor.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/midi-monitor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EACL,KAAK,qBAAqB,EAQ3B,MAAM,2BAA2B,CAAA;AAiBlC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAA;AAgC9E;;;GAGG;AACH,iBAAS,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,MAAM,CAmB9D;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CACvD;AAwBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B;;;;;;;;;+BAgDzC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;;wBAmCC,OAAO,GAAG,QAAQ;oBAuClD,MAAM;qBAwCuB,MAAM;yBA+F7B,MAAM;oCAoDX,MAAM,SACV,QAAQ,CAAC,MAAM,CAAC;;EAqJ3B;AAkDD,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACnE,uDAAuD;IACvD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CACvD;AAED;;;GAGG;AACH,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,KAAa,EACb,SAAS,EACT,GAAG,KAAK,EACT,EAAE,gBAAgB,2CA+JlB;AAED,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAA"}
|