@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,534 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Music, Plus, Unplug } from 'lucide-react';
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { getMoldableMIDIInputs, getMoldableMIDIOutputs, observeMoldableMIDIState, openMoldableMIDIInput, openMoldableMIDIOutput, requestMoldableMIDIWithFallback, } from '../../lib/native-hardware';
|
|
6
|
+
import { Badge } from '../ui/badge';
|
|
7
|
+
import { Button } from '../ui/button';
|
|
8
|
+
import { Spinner } from '../ui/spinner';
|
|
9
|
+
import { CapabilityBadge } from './capability-badge';
|
|
10
|
+
import { DeviceList, DeviceListEmpty, DeviceListItem } from './device-list';
|
|
11
|
+
import { HardwarePanel, HardwarePanelAction, HardwarePanelContent, HardwarePanelHeader, HardwarePanelIcon, HardwarePanelTitle, } from './hardware-panel';
|
|
12
|
+
import { LiveIndicator } from './live-indicator';
|
|
13
|
+
import { StreamLog, useStreamLog } from './stream-log';
|
|
14
|
+
const NOTE_NAMES = [
|
|
15
|
+
'C',
|
|
16
|
+
'C#',
|
|
17
|
+
'D',
|
|
18
|
+
'D#',
|
|
19
|
+
'E',
|
|
20
|
+
'F',
|
|
21
|
+
'F#',
|
|
22
|
+
'G',
|
|
23
|
+
'G#',
|
|
24
|
+
'A',
|
|
25
|
+
'A#',
|
|
26
|
+
'B',
|
|
27
|
+
];
|
|
28
|
+
function toError(cause) {
|
|
29
|
+
return cause instanceof Error ? cause : new Error(String(cause));
|
|
30
|
+
}
|
|
31
|
+
/** Scientific pitch name for a MIDI note number (middle C, 60, is C4). */
|
|
32
|
+
function pitchName(note) {
|
|
33
|
+
return `${NOTE_NAMES[((note % 12) + 12) % 12]}${Math.floor(note / 12) - 1}`;
|
|
34
|
+
}
|
|
35
|
+
function hexDump(bytes) {
|
|
36
|
+
return bytes
|
|
37
|
+
.map((byte) => byte.toString(16).padStart(2, '0').toUpperCase())
|
|
38
|
+
.join(' ');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Human-readable summary of a raw MIDI message: note on/off with pitch name,
|
|
42
|
+
* control change, program change, and pitch bend; anything else as hex.
|
|
43
|
+
*/
|
|
44
|
+
function formatMidiMessage(data) {
|
|
45
|
+
const bytes = Array.from(data);
|
|
46
|
+
if (bytes.length === 0)
|
|
47
|
+
return '(empty)';
|
|
48
|
+
const [status = 0, first = 0, second = 0] = bytes;
|
|
49
|
+
if (status < 0x80 || status >= 0xf0)
|
|
50
|
+
return hexDump(bytes);
|
|
51
|
+
const channel = (status & 0x0f) + 1;
|
|
52
|
+
const kind = status & 0xf0;
|
|
53
|
+
if (kind === 0x90 && second > 0) {
|
|
54
|
+
return `Note On ${pitchName(first)} (${first}) vel ${second} ch ${channel}`;
|
|
55
|
+
}
|
|
56
|
+
if (kind === 0x80 || kind === 0x90) {
|
|
57
|
+
return `Note Off ${pitchName(first)} (${first}) ch ${channel}`;
|
|
58
|
+
}
|
|
59
|
+
if (kind === 0xb0)
|
|
60
|
+
return `CC ${first} = ${second} ch ${channel}`;
|
|
61
|
+
if (kind === 0xc0)
|
|
62
|
+
return `Program Change ${first} ch ${channel}`;
|
|
63
|
+
if (kind === 0xe0) {
|
|
64
|
+
return `Pitch Bend ${((second << 7) | first) - 8192} ch ${channel}`;
|
|
65
|
+
}
|
|
66
|
+
return hexDump(bytes);
|
|
67
|
+
}
|
|
68
|
+
const DEFAULT_SYSEX_MESSAGE = [0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7];
|
|
69
|
+
const ALL_NOTES_OFF_MESSAGE = [0xb0, 0x7b, 0];
|
|
70
|
+
function systemExclusiveBytes(data) {
|
|
71
|
+
const bytes = Array.from(data);
|
|
72
|
+
if (bytes.length < 2 ||
|
|
73
|
+
bytes[0] !== 0xf0 ||
|
|
74
|
+
bytes.at(-1) !== 0xf7 ||
|
|
75
|
+
bytes.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
|
|
76
|
+
throw new TypeError('SysEx data must start with F0 and end with F7.');
|
|
77
|
+
}
|
|
78
|
+
return bytes;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* MIDI state machine behind `MidiMonitor`. Handles both transports returned
|
|
82
|
+
* by `requestMoldableMIDIWithFallback`: Web MIDI (full port enumeration and
|
|
83
|
+
* state observation) and the desktop bridge (host-owned per-port chooser via
|
|
84
|
+
* `requestPort`).
|
|
85
|
+
*/
|
|
86
|
+
export function useMoldableMidi(options = {}) {
|
|
87
|
+
const [status, setStatus] = useState('idle');
|
|
88
|
+
const [access, setAccess] = useState(null);
|
|
89
|
+
const [inputs, setInputs] = useState([]);
|
|
90
|
+
const [outputs, setOutputs] = useState([]);
|
|
91
|
+
const [openInputIds, setOpenInputIds] = useState([]);
|
|
92
|
+
const [sysexEnabled, setSysexEnabled] = useState(false);
|
|
93
|
+
const [error, setError] = useState(null);
|
|
94
|
+
const { entries, append, clear } = useStreamLog();
|
|
95
|
+
const generationRef = useRef(0);
|
|
96
|
+
const transportRef = useRef(null);
|
|
97
|
+
const openInputsRef = useRef(new Map());
|
|
98
|
+
const outputsRef = useRef(new Map());
|
|
99
|
+
const outputClosingRef = useRef(new Map());
|
|
100
|
+
const nativePortsRef = useRef(new Map());
|
|
101
|
+
const noteTimersRef = useRef(new Set());
|
|
102
|
+
const pendingNotesRef = useRef(new Map());
|
|
103
|
+
const stopStateRef = useRef(null);
|
|
104
|
+
const onMessageRef = useRef(options.onMessage);
|
|
105
|
+
onMessageRef.current = options.onMessage;
|
|
106
|
+
const handleMessage = useCallback((portId, data) => {
|
|
107
|
+
append({ direction: 'in', data: formatMidiMessage(data) });
|
|
108
|
+
onMessageRef.current?.(data, portId);
|
|
109
|
+
}, [append]);
|
|
110
|
+
const refreshPorts = useCallback((accessValue) => {
|
|
111
|
+
setInputs(getMoldableMIDIInputs(accessValue).map((input) => ({
|
|
112
|
+
id: input.id,
|
|
113
|
+
name: input.name || 'MIDI input',
|
|
114
|
+
manufacturer: input.manufacturer || undefined,
|
|
115
|
+
})));
|
|
116
|
+
setOutputs(getMoldableMIDIOutputs(accessValue).map((output) => ({
|
|
117
|
+
id: output.id,
|
|
118
|
+
name: output.name || 'MIDI output',
|
|
119
|
+
manufacturer: output.manufacturer || undefined,
|
|
120
|
+
})));
|
|
121
|
+
}, []);
|
|
122
|
+
const connect = useCallback(async (connectOptions = {}) => {
|
|
123
|
+
if (transportRef.current)
|
|
124
|
+
return;
|
|
125
|
+
const generation = generationRef.current;
|
|
126
|
+
setStatus('connecting');
|
|
127
|
+
setError(null);
|
|
128
|
+
try {
|
|
129
|
+
const transport = await requestMoldableMIDIWithFallback({
|
|
130
|
+
sysex: connectOptions.sysex === true,
|
|
131
|
+
});
|
|
132
|
+
if (generationRef.current !== generation)
|
|
133
|
+
return;
|
|
134
|
+
transportRef.current = transport;
|
|
135
|
+
setAccess(transport);
|
|
136
|
+
if (transport.kind === 'web-midi') {
|
|
137
|
+
setSysexEnabled(transport.access.sysexEnabled);
|
|
138
|
+
refreshPorts(transport.access);
|
|
139
|
+
stopStateRef.current = observeMoldableMIDIState(transport.access, () => refreshPorts(transport.access));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// The desktop bridge cannot enumerate ports; they arrive one at a
|
|
143
|
+
// time through the host-owned chooser (`requestPort`).
|
|
144
|
+
setSysexEnabled(connectOptions.sysex === true);
|
|
145
|
+
}
|
|
146
|
+
setStatus('connected');
|
|
147
|
+
}
|
|
148
|
+
catch (cause) {
|
|
149
|
+
if (generationRef.current !== generation)
|
|
150
|
+
return;
|
|
151
|
+
setError(toError(cause));
|
|
152
|
+
setStatus('error');
|
|
153
|
+
}
|
|
154
|
+
}, [refreshPorts]);
|
|
155
|
+
/** Desktop-bridge only: opens the host chooser for one input or output. */
|
|
156
|
+
const requestPort = useCallback(async (kind) => {
|
|
157
|
+
const transport = transportRef.current;
|
|
158
|
+
if (!transport || transport.kind !== 'desktop-bridge')
|
|
159
|
+
return;
|
|
160
|
+
const generation = generationRef.current;
|
|
161
|
+
setError(null);
|
|
162
|
+
try {
|
|
163
|
+
const port = await transport.access.requestPort(kind);
|
|
164
|
+
if (generationRef.current !== generation) {
|
|
165
|
+
void port.close().catch(() => undefined);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const previousPort = nativePortsRef.current.get(port.id);
|
|
169
|
+
if (previousPort && previousPort !== port) {
|
|
170
|
+
try {
|
|
171
|
+
await previousPort.close();
|
|
172
|
+
}
|
|
173
|
+
catch (cause) {
|
|
174
|
+
void port.close().catch(() => undefined);
|
|
175
|
+
if (generationRef.current === generation)
|
|
176
|
+
setError(toError(cause));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
nativePortsRef.current.set(port.id, port);
|
|
181
|
+
const info = {
|
|
182
|
+
id: port.id,
|
|
183
|
+
name: port.name || `MIDI ${kind}`,
|
|
184
|
+
};
|
|
185
|
+
const update = (previous) => [
|
|
186
|
+
...previous.filter((item) => item.id !== info.id),
|
|
187
|
+
info,
|
|
188
|
+
];
|
|
189
|
+
if (kind === 'input')
|
|
190
|
+
setInputs(update);
|
|
191
|
+
else
|
|
192
|
+
setOutputs(update);
|
|
193
|
+
}
|
|
194
|
+
catch (cause) {
|
|
195
|
+
if (generationRef.current !== generation)
|
|
196
|
+
return;
|
|
197
|
+
setError(toError(cause));
|
|
198
|
+
}
|
|
199
|
+
}, []);
|
|
200
|
+
const openInput = useCallback(async (id) => {
|
|
201
|
+
const transport = transportRef.current;
|
|
202
|
+
if (!transport || openInputsRef.current.has(id))
|
|
203
|
+
return;
|
|
204
|
+
const generation = generationRef.current;
|
|
205
|
+
setError(null);
|
|
206
|
+
try {
|
|
207
|
+
if (transport.kind === 'web-midi') {
|
|
208
|
+
const connection = await openMoldableMIDIInput(transport.access, id, (event) => {
|
|
209
|
+
if (event.data)
|
|
210
|
+
handleMessage(id, new Uint8Array(event.data));
|
|
211
|
+
});
|
|
212
|
+
if (generationRef.current !== generation) {
|
|
213
|
+
void connection.close().catch(() => undefined);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
openInputsRef.current.set(id, () => connection.close());
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const port = nativePortsRef.current.get(id);
|
|
220
|
+
if (!port)
|
|
221
|
+
return;
|
|
222
|
+
const stopObserving = port.observeMessages((data) => handleMessage(id, data));
|
|
223
|
+
openInputsRef.current.set(id, async () => {
|
|
224
|
+
stopObserving();
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
setOpenInputIds((previous) => previous.includes(id) ? previous : [...previous, id]);
|
|
228
|
+
}
|
|
229
|
+
catch (cause) {
|
|
230
|
+
if (generationRef.current !== generation)
|
|
231
|
+
return;
|
|
232
|
+
setError(toError(cause));
|
|
233
|
+
}
|
|
234
|
+
}, [handleMessage]);
|
|
235
|
+
const closeInput = useCallback(async (id) => {
|
|
236
|
+
const stop = openInputsRef.current.get(id);
|
|
237
|
+
if (!stop)
|
|
238
|
+
return;
|
|
239
|
+
try {
|
|
240
|
+
await stop();
|
|
241
|
+
if (openInputsRef.current.get(id) !== stop)
|
|
242
|
+
return;
|
|
243
|
+
openInputsRef.current.delete(id);
|
|
244
|
+
setOpenInputIds((previous) => previous.filter((value) => value !== id));
|
|
245
|
+
}
|
|
246
|
+
catch (cause) {
|
|
247
|
+
setError(toError(cause));
|
|
248
|
+
}
|
|
249
|
+
}, []);
|
|
250
|
+
const openOutput = useCallback(async (outputId, generation) => {
|
|
251
|
+
const transport = transportRef.current;
|
|
252
|
+
if (!transport)
|
|
253
|
+
return undefined;
|
|
254
|
+
const existing = outputsRef.current.get(outputId);
|
|
255
|
+
if (existing)
|
|
256
|
+
return existing;
|
|
257
|
+
let handle;
|
|
258
|
+
if (transport.kind === 'web-midi') {
|
|
259
|
+
const connection = await openMoldableMIDIOutput(transport.access, outputId);
|
|
260
|
+
if (generationRef.current !== generation) {
|
|
261
|
+
void connection.close().catch(() => undefined);
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
handle = {
|
|
265
|
+
send: (data) => connection.send(data),
|
|
266
|
+
close: () => connection.close(),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const port = nativePortsRef.current.get(outputId);
|
|
271
|
+
if (!port)
|
|
272
|
+
return undefined;
|
|
273
|
+
handle = {
|
|
274
|
+
send: (data) => port.send(data),
|
|
275
|
+
close: () => port.close(),
|
|
276
|
+
nativePort: port,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const raced = outputsRef.current.get(outputId);
|
|
280
|
+
if (raced) {
|
|
281
|
+
void handle.close().catch(() => undefined);
|
|
282
|
+
return raced;
|
|
283
|
+
}
|
|
284
|
+
outputsRef.current.set(outputId, handle);
|
|
285
|
+
return handle;
|
|
286
|
+
}, []);
|
|
287
|
+
const releaseOutput = useCallback((outputId) => {
|
|
288
|
+
const pending = outputClosingRef.current.get(outputId);
|
|
289
|
+
if (pending)
|
|
290
|
+
return pending;
|
|
291
|
+
const handle = outputsRef.current.get(outputId);
|
|
292
|
+
if (!handle)
|
|
293
|
+
return Promise.resolve(null);
|
|
294
|
+
const operation = (async () => {
|
|
295
|
+
try {
|
|
296
|
+
await handle.close();
|
|
297
|
+
if (outputsRef.current.get(outputId) !== handle)
|
|
298
|
+
return null;
|
|
299
|
+
outputsRef.current.delete(outputId);
|
|
300
|
+
if (handle.nativePort &&
|
|
301
|
+
nativePortsRef.current.get(outputId) === handle.nativePort) {
|
|
302
|
+
nativePortsRef.current.delete(outputId);
|
|
303
|
+
setOutputs((previous) => previous.filter((port) => port.id !== outputId));
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
catch (cause) {
|
|
308
|
+
const failure = toError(cause);
|
|
309
|
+
setError(failure);
|
|
310
|
+
return failure;
|
|
311
|
+
}
|
|
312
|
+
})();
|
|
313
|
+
const closing = operation.finally(() => {
|
|
314
|
+
if (outputClosingRef.current.get(outputId) === closing) {
|
|
315
|
+
outputClosingRef.current.delete(outputId);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
outputClosingRef.current.set(outputId, closing);
|
|
319
|
+
return closing;
|
|
320
|
+
}, []);
|
|
321
|
+
const sendNote = useCallback(async (outputId, note = 60, velocity = 100) => {
|
|
322
|
+
const generation = generationRef.current;
|
|
323
|
+
setError(null);
|
|
324
|
+
try {
|
|
325
|
+
const handle = await openOutput(outputId, generation);
|
|
326
|
+
if (!handle)
|
|
327
|
+
return;
|
|
328
|
+
const noteOn = [0x90, note, velocity];
|
|
329
|
+
await handle.send(noteOn);
|
|
330
|
+
if (generationRef.current !== generation)
|
|
331
|
+
return;
|
|
332
|
+
pendingNotesRef.current.set(outputId, (pendingNotesRef.current.get(outputId) ?? 0) + 1);
|
|
333
|
+
append({ direction: 'out', data: formatMidiMessage(noteOn) });
|
|
334
|
+
const timer = window.setTimeout(() => {
|
|
335
|
+
noteTimersRef.current.delete(timer);
|
|
336
|
+
if (generationRef.current !== generation)
|
|
337
|
+
return;
|
|
338
|
+
const active = outputsRef.current.get(outputId);
|
|
339
|
+
void (async () => {
|
|
340
|
+
try {
|
|
341
|
+
if (active) {
|
|
342
|
+
const noteOff = [0x80, note, 0];
|
|
343
|
+
await active.send(noteOff);
|
|
344
|
+
append({ direction: 'out', data: formatMidiMessage(noteOff) });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch (cause) {
|
|
348
|
+
if (generationRef.current === generation)
|
|
349
|
+
setError(toError(cause));
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
const pending = Math.max(0, (pendingNotesRef.current.get(outputId) ?? 1) - 1);
|
|
353
|
+
if (pending === 0) {
|
|
354
|
+
pendingNotesRef.current.delete(outputId);
|
|
355
|
+
await releaseOutput(outputId);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
pendingNotesRef.current.set(outputId, pending);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
})();
|
|
362
|
+
}, 200);
|
|
363
|
+
noteTimersRef.current.add(timer);
|
|
364
|
+
}
|
|
365
|
+
catch (cause) {
|
|
366
|
+
if (generationRef.current !== generation)
|
|
367
|
+
return;
|
|
368
|
+
setError(toError(cause));
|
|
369
|
+
}
|
|
370
|
+
}, [append, openOutput, releaseOutput]);
|
|
371
|
+
const sendSystemExclusive = useCallback(async (outputId, data = DEFAULT_SYSEX_MESSAGE) => {
|
|
372
|
+
const generation = generationRef.current;
|
|
373
|
+
setError(null);
|
|
374
|
+
try {
|
|
375
|
+
if (!sysexEnabled) {
|
|
376
|
+
throw new DOMException('Reconnect with SysEx enabled before sending SysEx data.', 'NotAllowedError');
|
|
377
|
+
}
|
|
378
|
+
const bytes = systemExclusiveBytes(data);
|
|
379
|
+
const handle = await openOutput(outputId, generation);
|
|
380
|
+
if (!handle)
|
|
381
|
+
return;
|
|
382
|
+
await handle.send(bytes);
|
|
383
|
+
if (generationRef.current !== generation)
|
|
384
|
+
return;
|
|
385
|
+
append({ direction: 'out', data: formatMidiMessage(bytes) });
|
|
386
|
+
if ((pendingNotesRef.current.get(outputId) ?? 0) === 0) {
|
|
387
|
+
await releaseOutput(outputId);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (cause) {
|
|
391
|
+
if (generationRef.current !== generation)
|
|
392
|
+
return;
|
|
393
|
+
setError(toError(cause));
|
|
394
|
+
}
|
|
395
|
+
}, [append, openOutput, releaseOutput, sysexEnabled]);
|
|
396
|
+
const disconnect = useCallback(async () => {
|
|
397
|
+
generationRef.current++;
|
|
398
|
+
for (const timer of noteTimersRef.current)
|
|
399
|
+
window.clearTimeout(timer);
|
|
400
|
+
noteTimersRef.current.clear();
|
|
401
|
+
pendingNotesRef.current.clear();
|
|
402
|
+
stopStateRef.current?.();
|
|
403
|
+
stopStateRef.current = null;
|
|
404
|
+
const failures = [];
|
|
405
|
+
for (const [id, stop] of openInputsRef.current) {
|
|
406
|
+
try {
|
|
407
|
+
await stop();
|
|
408
|
+
if (openInputsRef.current.get(id) === stop) {
|
|
409
|
+
openInputsRef.current.delete(id);
|
|
410
|
+
setOpenInputIds((previous) => previous.filter((value) => value !== id));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (cause) {
|
|
414
|
+
failures.push(toError(cause));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (const id of [...outputsRef.current.keys()]) {
|
|
418
|
+
const handle = outputsRef.current.get(id);
|
|
419
|
+
if (handle) {
|
|
420
|
+
try {
|
|
421
|
+
await handle.send(ALL_NOTES_OFF_MESSAGE);
|
|
422
|
+
append({
|
|
423
|
+
direction: 'out',
|
|
424
|
+
data: formatMidiMessage(ALL_NOTES_OFF_MESSAGE),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
catch (cause) {
|
|
428
|
+
failures.push(toError(cause));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const failure = await releaseOutput(id);
|
|
432
|
+
if (failure)
|
|
433
|
+
failures.push(failure);
|
|
434
|
+
}
|
|
435
|
+
for (const [id, port] of nativePortsRef.current) {
|
|
436
|
+
try {
|
|
437
|
+
await port.close();
|
|
438
|
+
if (nativePortsRef.current.get(id) === port) {
|
|
439
|
+
nativePortsRef.current.delete(id);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch (cause) {
|
|
443
|
+
failures.push(toError(cause));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (failures.length > 0) {
|
|
447
|
+
setError(failures[0]);
|
|
448
|
+
setStatus('error');
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
transportRef.current = null;
|
|
452
|
+
setAccess(null);
|
|
453
|
+
setInputs([]);
|
|
454
|
+
setOutputs([]);
|
|
455
|
+
setOpenInputIds([]);
|
|
456
|
+
setSysexEnabled(false);
|
|
457
|
+
setError(null);
|
|
458
|
+
setStatus('idle');
|
|
459
|
+
}, [append, releaseOutput]);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
const liveGeneration = generationRef;
|
|
462
|
+
const liveInputs = openInputsRef;
|
|
463
|
+
const liveOutputs = outputsRef;
|
|
464
|
+
const livePorts = nativePortsRef;
|
|
465
|
+
const liveStopState = stopStateRef;
|
|
466
|
+
const liveTimers = noteTimersRef;
|
|
467
|
+
const livePendingNotes = pendingNotesRef;
|
|
468
|
+
return () => {
|
|
469
|
+
liveGeneration.current++;
|
|
470
|
+
for (const timer of liveTimers.current)
|
|
471
|
+
window.clearTimeout(timer);
|
|
472
|
+
liveTimers.current.clear();
|
|
473
|
+
livePendingNotes.current.clear();
|
|
474
|
+
liveStopState.current?.();
|
|
475
|
+
liveStopState.current = null;
|
|
476
|
+
for (const stop of liveInputs.current.values()) {
|
|
477
|
+
void stop().catch(() => undefined);
|
|
478
|
+
}
|
|
479
|
+
liveInputs.current.clear();
|
|
480
|
+
for (const handle of liveOutputs.current.values()) {
|
|
481
|
+
void Promise.resolve(handle.send(ALL_NOTES_OFF_MESSAGE))
|
|
482
|
+
.catch(() => undefined)
|
|
483
|
+
.then(() => handle.close())
|
|
484
|
+
.catch(() => undefined);
|
|
485
|
+
}
|
|
486
|
+
const outputPorts = new Set([...liveOutputs.current.values()].flatMap((handle) => handle.nativePort ? [handle.nativePort] : []));
|
|
487
|
+
for (const port of livePorts.current.values()) {
|
|
488
|
+
if (outputPorts.has(port))
|
|
489
|
+
continue;
|
|
490
|
+
void port.close().catch(() => undefined);
|
|
491
|
+
}
|
|
492
|
+
liveOutputs.current.clear();
|
|
493
|
+
livePorts.current.clear();
|
|
494
|
+
};
|
|
495
|
+
}, []);
|
|
496
|
+
return {
|
|
497
|
+
status,
|
|
498
|
+
access,
|
|
499
|
+
inputs,
|
|
500
|
+
outputs,
|
|
501
|
+
openInputIds,
|
|
502
|
+
sysexEnabled,
|
|
503
|
+
error,
|
|
504
|
+
entries,
|
|
505
|
+
connect,
|
|
506
|
+
disconnect,
|
|
507
|
+
requestPort,
|
|
508
|
+
openInput,
|
|
509
|
+
closeInput,
|
|
510
|
+
sendNote,
|
|
511
|
+
sendSystemExclusive,
|
|
512
|
+
clearEntries: clear,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function MidiPortSection({ label, ports, emptyLabel, onRequestPort, renderAction, isConnected, }) {
|
|
516
|
+
return (_jsxs("section", { "data-slot": "midi-port-section", className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-[10px] font-medium uppercase tracking-wider", children: label }), onRequestPort ? (_jsxs(Button, { variant: "ghost", size: "xs", onClick: onRequestPort, children: [_jsx(Plus, {}), " Add"] })) : null] }), _jsx(DeviceList, { children: ports.length === 0 ? (_jsx(DeviceListEmpty, { children: emptyLabel })) : (ports.map((port) => (_jsx(DeviceListItem, { name: port.name, description: port.manufacturer, connected: isConnected?.(port) ?? false, action: renderAction(port) }, port.id)))) })] }));
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* MIDI monitor panel: connect either transport, monitor inputs, fire a test
|
|
520
|
+
* note at outputs, and read a live parsed-message log.
|
|
521
|
+
*/
|
|
522
|
+
function MidiMonitor({ className, sysex = false, onMessage, ...props }) {
|
|
523
|
+
const midi = useMoldableMidi({ onMessage });
|
|
524
|
+
const connecting = midi.status === 'connecting';
|
|
525
|
+
const connected = midi.access !== null;
|
|
526
|
+
const native = midi.access?.kind === 'desktop-bridge';
|
|
527
|
+
return (_jsxs(HardwarePanel, { "data-slot": "midi-monitor", "data-status": midi.status, className: className, ...props, children: [_jsxs(HardwarePanelHeader, { children: [_jsx(HardwarePanelIcon, { children: _jsx(Music, {}) }), _jsx(HardwarePanelTitle, { children: "MIDI" }), _jsx(CapabilityBadge, { capability: "midi" }), _jsx(HardwarePanelAction, { children: connected ? (_jsxs(_Fragment, { children: [midi.sysexEnabled ? (_jsx(Badge, { variant: "outline", children: "SysEx" })) : null, _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void midi.disconnect(), children: [_jsx(Unplug, {}), midi.status === 'error' ? 'Retry cleanup' : 'Disconnect'] })] })) : (_jsxs(Button, { size: "sm", disabled: connecting, onClick: () => void midi.connect({ sysex }), children: [connecting ? _jsx(Spinner, {}) : null, connecting ? 'Connecting…' : 'Connect'] })) })] }), _jsx(HardwarePanelContent, { children: connected ? (_jsxs("div", { className: "animate-in fade-in space-y-4 duration-300 motion-reduce:animate-none", children: [_jsx(MidiPortSection, { label: "Inputs", ports: midi.inputs, emptyLabel: "No inputs", onRequestPort: native ? () => void midi.requestPort('input') : undefined, isConnected: (port) => midi.openInputIds.includes(port.id), renderAction: (port) => {
|
|
528
|
+
const open = midi.openInputIds.includes(port.id);
|
|
529
|
+
return (_jsx(Button, { variant: "outline", size: "xs", onClick: () => void (open
|
|
530
|
+
? midi.closeInput(port.id)
|
|
531
|
+
: midi.openInput(port.id)), children: open ? 'Stop' : 'Monitor' }));
|
|
532
|
+
} }), _jsx(MidiPortSection, { label: "Outputs", ports: midi.outputs, emptyLabel: "No outputs", onRequestPort: native ? () => void midi.requestPort('output') : undefined, renderAction: (port) => (_jsxs("div", { className: "flex items-center gap-1.5", children: [midi.sysexEnabled ? (_jsx(Button, { variant: "ghost", size: "xs", onClick: () => void midi.sendSystemExclusive(port.id), children: "Send SysEx" })) : null, _jsx(Button, { variant: "outline", size: "xs", onClick: () => void midi.sendNote(port.id, 48 + Math.floor(Math.random() * 25), 96), children: "Send note" })] })) }), midi.error ? (_jsx("p", { className: "text-status-error text-xs", children: midi.error.message })) : null, _jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-[10px] font-medium uppercase tracking-wider", children: "Messages" }), midi.openInputIds.length > 0 ? _jsx(LiveIndicator, {}) : null] }), _jsx(StreamLog, { entries: midi.entries, maxHeight: 200, emptyLabel: "Waiting for MIDI messages" })] })] })) : (_jsx("div", { className: "animate-in fade-in flex flex-col items-center gap-3 py-6 text-center duration-300 motion-reduce:animate-none", children: connecting ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "text-muted-foreground" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Requesting MIDI access\u2026" })] })) : midi.status === 'error' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm font-medium", children: "MIDI access failed" }), _jsx("p", { className: "text-muted-foreground max-w-64 text-xs", children: midi.error?.message ?? 'The MIDI 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(Music, {}) }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Connect to list MIDI inputs and outputs." })] })) })) })] }));
|
|
533
|
+
}
|
|
534
|
+
export { MidiMonitor, formatMidiMessage };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type MoldableNotificationOptions } from '../../lib/native-hardware';
|
|
3
|
+
export type MoldableNotificationPermissionState = 'granted' | 'denied' | 'prompt' | 'unknown';
|
|
4
|
+
export type MoldableNotificationSendStatus = 'idle' | 'sending' | 'sent' | 'error';
|
|
5
|
+
/**
|
|
6
|
+
* Notification state machine behind `NotificationsPanel`: permission loading,
|
|
7
|
+
* explicit permission requests, and sends with the library's length limits
|
|
8
|
+
* enforced by truncation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useMoldableNotifications(): {
|
|
11
|
+
permission: MoldableNotificationPermissionState;
|
|
12
|
+
status: MoldableNotificationSendStatus;
|
|
13
|
+
sentAt: number | null;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
request: () => Promise<MoldableNotificationPermissionState | null>;
|
|
16
|
+
send: (options: MoldableNotificationOptions) => Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Notification composer panel: permission chip with an inline allow flow, a
|
|
20
|
+
* compact title/body/silent composer, and delivery feedback.
|
|
21
|
+
*/
|
|
22
|
+
declare function NotificationsPanel({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export { NotificationsPanel };
|
|
24
|
+
//# sourceMappingURL=notification-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-panel.d.ts","sourceRoot":"","sources":["../../../src/components/hardware/notification-panel.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EACL,KAAK,2BAA2B,EAOjC,MAAM,2BAA2B,CAAA;AAoBlC,MAAM,MAAM,mCAAmC,GAC3C,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,SAAS,CAAA;AAEb,MAAM,MAAM,8BAA8B,GACtC,MAAM,GACN,SAAS,GACT,MAAM,GACN,OAAO,CAAA;AAEX;;;;GAIG;AACH,wBAAgB,wBAAwB;;;;;mBAuBd,OAAO,CAAC,mCAAmC,GAAG,IAAI,CAAC;oBAazD,2BAA2B,KAAG,OAAO,CAAC,OAAO,CAAC;EAiCjE;AAiBD;;;GAGG;AACH,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAiH7B;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|