@legendapp/spark 0.0.1-next.2 → 0.0.1-next.3
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/LICENSE +21 -0
- package/README.md +10 -4
- package/ai-codex.ts +1 -0
- package/ai.ts +1 -0
- package/app-documents.ts +1 -0
- package/config-plugin.d.cts +2 -0
- package/contracts.ts +2 -0
- package/diagnostics.ts +1 -0
- package/expo-config.d.cts +1 -0
- package/metro.cjs +4 -1
- package/metro.d.cts +12 -0
- package/native.cjs +3 -1
- package/native.d.cts +6 -0
- package/package.json +82 -14
- package/schema.json +276 -58
- package/settings-observable.ts +1 -0
- package/settings-window.ts +1 -0
- package/shortcuts-commands.ts +1 -0
- package/shortcuts-keyboard.ts +3 -0
- package/ui-classnames.ts +1 -0
- package/ui-glass.ts +1 -0
- package/ui-search.ts +1 -0
- package/ui-sidebar.ts +1 -0
- package/ui-split-view.ts +1 -0
- package/ui-symbol.ts +1 -0
- package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
- package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
- package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
- package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
- package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
- package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
- package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
- package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
- package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
- package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
- package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
- package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
- package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
- package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
- package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
- package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
- package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
- package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
- package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
- package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
- package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
- package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
- package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
- package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
- package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
- package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
- package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
- package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
- package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
- package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
- package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
- package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
- package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
- package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
- package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
- package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
- package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
- package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
- package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
- package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
- package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
- package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
- package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
- package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
- package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
- package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
- package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
- package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
- package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
- package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
- package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
- package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
- package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
- package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
- package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
- package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
- package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
- package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
- package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
- package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
- package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
- package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
- package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
- package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
- package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
- package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
- package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
- package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
- package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
- package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
- package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
- package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
- package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
- package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
- package/windows-macos.ts +1 -0
- package/expo-metro.cjs +0 -1
- package/message-dialog.ts +0 -1
- package/universal.cjs +0 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
|
|
2
|
+
import { createDatabase } from "./database";
|
|
3
|
+
import type { Database, DatabaseOptions } from "./types";
|
|
4
|
+
export type { Database, DatabaseOptions, IntegerMode, SqlExecutor, SqlRow, SqlValue, RunResult } from "./types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Open a project-scoped database. Backends deliver INTEGER values as JS numbers, so
|
|
8
|
+
* `integers: "text"` reads values outside 2^53−1 as text instead of rejecting them;
|
|
9
|
+
* exact digits beyond the double's precision still require `CAST(col AS TEXT)`.
|
|
10
|
+
*/
|
|
11
|
+
export async function openDatabase(name: string, options: DatabaseOptions = {}): Promise<Database> {
|
|
12
|
+
if (!/^[a-zA-Z0-9_-]+\.sqlite$/.test(name)) throw new SparkError("E_INVALID_ARGUMENT", "Database name must be a simple .sqlite filename");
|
|
13
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected database options");
|
|
14
|
+
for (const key of Object.keys(options)) if (key !== "readOnly" && key !== "integers") throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported database option: ${key}`);
|
|
15
|
+
if (options.readOnly !== undefined && typeof options.readOnly !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "readOnly must be a boolean");
|
|
16
|
+
if (options.integers !== undefined && options.integers !== "number" && options.integers !== "text") throw new SparkError("E_INVALID_ARGUMENT", "integers must be number or text");
|
|
17
|
+
const [{ open }, { getDirectory }] = await Promise.all([import("@op-engineering/op-sqlite"), import("@legendapp/spark-file-system")]);
|
|
18
|
+
const native = open({ name, location: await getDirectory("data"), readOnly: options.readOnly });
|
|
19
|
+
return createDatabase({ execute: (sql, params) => native.execute(sql, params as import("@op-engineering/op-sqlite").Scalar[]), close: () => native.close() }, options.integers ?? "number");
|
|
7
20
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type SqlValue = null | string | number | Uint8Array;
|
|
2
|
+
export type SqlRow = Record<string, SqlValue>;
|
|
3
|
+
export type IntegerMode = "number" | "text";
|
|
4
|
+
export type RunResult = {
|
|
5
|
+
changes: number;
|
|
6
|
+
/** Connection's last insert row ID; null when the backend omits it. Text in `integers: "text"` mode. */
|
|
7
|
+
lastInsertRowId: number | string | null;
|
|
8
|
+
};
|
|
9
|
+
export interface SqlExecutor {
|
|
10
|
+
run(sql: string, params?: readonly SqlValue[]): Promise<RunResult>;
|
|
11
|
+
getAll(sql: string, params?: readonly SqlValue[]): Promise<SqlRow[]>;
|
|
12
|
+
getFirst(sql: string, params?: readonly SqlValue[]): Promise<SqlRow | null>;
|
|
13
|
+
}
|
|
14
|
+
export interface Database extends SqlExecutor {
|
|
15
|
+
/** Row mode chosen at open. Text rows carry integers beyond 2^53−1 as strings. */
|
|
16
|
+
readonly integers: IntegerMode;
|
|
17
|
+
/** Use tx inside the callback. Direct database operations reject while a transaction is active. */
|
|
18
|
+
transaction<T>(operation: (tx: SqlExecutor) => Promise<T>): Promise<T>;
|
|
19
|
+
/** Waits for already queued work. Repeated calls share completion. */
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export type DatabaseOptions = { readOnly?: boolean; integers?: IntegerMode };
|
|
@@ -23,6 +23,7 @@ static NSDictionary *Power(void) {
|
|
|
23
23
|
static void PowerChanged(void *context) { SparkEmit(@{ @"type": @"powerChanged" }); }
|
|
24
24
|
@interface RNDesktopSystem ()
|
|
25
25
|
@property NSMutableArray *observers;
|
|
26
|
+
@property NSMutableSet *subscriptions;
|
|
26
27
|
@property NSMutableSet *assertions;
|
|
27
28
|
@property NSMutableSet *attention;
|
|
28
29
|
@property CFRunLoopSourceRef powerSource;
|
|
@@ -32,7 +33,7 @@ static void PowerChanged(void *context) { SparkEmit(@{ @"type": @"powerChanged"
|
|
|
32
33
|
@implementation RNDesktopSystem
|
|
33
34
|
RCT_EXPORT_MODULE(NativeDesktopSystem)
|
|
34
35
|
+ (BOOL)requiresMainQueueSetup { return YES; }
|
|
35
|
-
- (instancetype)init { if (self = [super init]) { _observers = [NSMutableArray new]; _assertions = [NSMutableSet new]; _attention = [NSMutableSet new]; } return self; }
|
|
36
|
+
- (instancetype)init { if (self = [super init]) { _observers = [NSMutableArray new]; _subscriptions = [NSMutableSet new]; _assertions = [NSMutableSet new]; _attention = [NSMutableSet new]; } return self; }
|
|
36
37
|
- (void)observe {
|
|
37
38
|
if (self.observing) return; self.observing = YES;
|
|
38
39
|
NSArray *definitions = @[
|
|
@@ -51,18 +52,45 @@ RCT_EXPORT_MODULE(NativeDesktopSystem)
|
|
|
51
52
|
self.powerSource = IOPSNotificationCreateRunLoopSource(PowerChanged, NULL);
|
|
52
53
|
if (self.powerSource) CFRunLoopAddSource(CFRunLoopGetMain(), self.powerSource, kCFRunLoopCommonModes);
|
|
53
54
|
}
|
|
54
|
-
- (void)
|
|
55
|
+
- (void)stopObserving {
|
|
56
|
+
for (NSArray *entry in self.observers) [entry[0] removeObserver:entry[1]];
|
|
57
|
+
[self.observers removeAllObjects]; self.observing = NO;
|
|
58
|
+
if (self.powerSource) { CFRunLoopRemoveSource(CFRunLoopGetMain(), self.powerSource, kCFRunLoopCommonModes); CFRelease(self.powerSource); self.powerSource = NULL; }
|
|
59
|
+
}
|
|
60
|
+
- (NSMenu *)menu:(NSArray *)items {
|
|
61
|
+
NSMenu *menu = [NSMenu new]; menu.autoenablesItems = NO;
|
|
62
|
+
for (NSDictionary *item in items) {
|
|
63
|
+
if ([item[@"separator"] boolValue]) { [menu addItem:NSMenuItem.separatorItem]; continue; }
|
|
64
|
+
NSMenuItem *entry = [[NSMenuItem alloc] initWithTitle:item[@"title"] action:@selector(dockAction:) keyEquivalent:@""];
|
|
65
|
+
entry.target = self; entry.representedObject = @{ @"id": item[@"id"], @"owner": self.dockOwner };
|
|
66
|
+
entry.enabled = !item[@"enabled"] || [item[@"enabled"] boolValue];
|
|
67
|
+
entry.state = [item[@"checked"] boolValue] ? NSControlStateValueOn : NSControlStateValueOff;
|
|
68
|
+
if (item[@"items"]) entry.submenu = [self menu:item[@"items"]];
|
|
69
|
+
[menu addItem:entry];
|
|
70
|
+
}
|
|
71
|
+
return menu;
|
|
72
|
+
}
|
|
73
|
+
- (void)dockAction:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"dockAction", @"id": sender.representedObject[@"id"], @"owner": sender.representedObject[@"owner"] }); }
|
|
55
74
|
- (void)call:(NSString *)method args:(NSString *)json resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
56
75
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
57
76
|
NSDictionary *args = SparkArgs(json);
|
|
58
77
|
if ([method isEqual:@"info"]) {
|
|
59
78
|
NSMutableDictionary *value = [Power() mutableCopy];
|
|
60
|
-
[value addEntriesFromDictionary:@{ @"osVersion": NSProcessInfo.processInfo.operatingSystemVersionString, @"architecture":
|
|
79
|
+
[value addEntriesFromDictionary:@{ @"osVersion": NSProcessInfo.processInfo.operatingSystemVersionString, @"architecture":
|
|
80
|
+
#if defined(__arm64__)
|
|
81
|
+
@"arm64",
|
|
82
|
+
#elif defined(__x86_64__)
|
|
83
|
+
@"x64",
|
|
84
|
+
#else
|
|
85
|
+
@"unknown",
|
|
86
|
+
#endif
|
|
87
|
+
@"locale": NSLocale.currentLocale.localeIdentifier,
|
|
61
88
|
@"dark": @([[NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] isEqual:NSAppearanceNameDarkAqua]),
|
|
62
89
|
@"idleSeconds": @(CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType)) }];
|
|
63
90
|
resolve(SparkJSON(value)); return;
|
|
64
91
|
}
|
|
65
|
-
if ([method isEqual:@"observe"]) [self observe];
|
|
92
|
+
if ([method isEqual:@"observe"]) { [self.subscriptions addObject:args[@"id"]]; [self observe]; }
|
|
93
|
+
else if ([method isEqual:@"unobserve"]) { [self.subscriptions removeObject:args[@"id"]]; if (!self.subscriptions.count) [self stopObserving]; }
|
|
66
94
|
else if ([method isEqual:@"loginStatus"] || [method isEqual:@"login"]) {
|
|
67
95
|
BOOL available = [SparkContext()[@"runtime"][@"mode"] isEqual:@"release"];
|
|
68
96
|
if ([method isEqual:@"loginStatus"]) {
|
|
@@ -80,11 +108,9 @@ RCT_EXPORT_MODULE(NativeDesktopSystem)
|
|
|
80
108
|
else if ([method isEqual:@"cancelAttention"]) { if ([self.attention containsObject:args[@"id"]]) [NSApp cancelUserAttentionRequest:[args[@"id"] integerValue]]; [self.attention removeObject:args[@"id"]]; }
|
|
81
109
|
else if ([method isEqual:@"clearDockMenu"]) { if ([self.dockOwner isEqual:args[@"owner"]]) { SparkDockMenu = nil; self.dockOwner = nil; } }
|
|
82
110
|
else if ([method isEqual:@"dockMenu"]) {
|
|
83
|
-
if (self.dockOwner) { reject(@"
|
|
111
|
+
if (self.dockOwner) { reject(@"E_ALREADY_EXISTS", @"Remove the existing Dock menu before replacing it", nil); return; }
|
|
84
112
|
self.dockOwner = args[@"owner"];
|
|
85
|
-
|
|
86
|
-
for (NSDictionary *item in args[@"items"]) { NSMenuItem *entry = [[NSMenuItem alloc] initWithTitle:item[@"title"] action:@selector(dockAction:) keyEquivalent:@""]; entry.target = self; entry.representedObject = item[@"id"]; entry.enabled = !item[@"enabled"] || [item[@"enabled"] boolValue]; entry.state = [item[@"checked"] boolValue] ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:entry]; }
|
|
87
|
-
SparkDockMenu = menu;
|
|
113
|
+
SparkDockMenu = [self menu:args[@"items"]];
|
|
88
114
|
}
|
|
89
115
|
else if ([method isEqual:@"preventSleep"]) {
|
|
90
116
|
IOPMAssertionID assertion; CFStringRef kind = [args[@"kind"] isEqual:@"system"] ? kIOPMAssertionTypePreventUserIdleSystemSleep : kIOPMAssertionTypePreventUserIdleDisplaySleep;
|
|
@@ -92,14 +118,13 @@ RCT_EXPORT_MODULE(NativeDesktopSystem)
|
|
|
92
118
|
if (status != kIOReturnSuccess) { reject(@"E_POWER", @"Could not create sleep assertion", nil); return; }
|
|
93
119
|
[self.assertions addObject:@(assertion)]; resolve(SparkJSON(@(assertion))); return;
|
|
94
120
|
}
|
|
95
|
-
else if ([method isEqual:@"allowSleep"]) { if ([self.assertions containsObject:args[@"id"]]
|
|
121
|
+
else if ([method isEqual:@"allowSleep"]) { if ([self.assertions containsObject:args[@"id"]] && IOPMAssertionRelease([args[@"id"] unsignedIntValue]) != kIOReturnSuccess) { reject(@"E_NATIVE", @"Could not release sleep assertion", nil); return; } [self.assertions removeObject:args[@"id"]]; }
|
|
96
122
|
else { SparkInvalid(reject, @"Unknown system operation"); return; }
|
|
97
123
|
resolve(@"null");
|
|
98
124
|
});
|
|
99
125
|
}
|
|
100
126
|
- (void)invalidate { dispatch_async(dispatch_get_main_queue(), ^{
|
|
101
|
-
|
|
102
|
-
if (self.powerSource) { CFRunLoopRemoveSource(CFRunLoopGetMain(), self.powerSource, kCFRunLoopCommonModes); CFRelease(self.powerSource); self.powerSource = NULL; }
|
|
127
|
+
[self stopObserving]; [self.subscriptions removeAllObjects];
|
|
103
128
|
for (NSNumber *value in self.assertions) IOPMAssertionRelease(value.unsignedIntValue); [self.assertions removeAllObjects];
|
|
104
129
|
for (NSNumber *value in self.attention) [NSApp cancelUserAttentionRequest:value.integerValue]; [self.attention removeAllObjects];
|
|
105
130
|
SparkDockMenu = nil; NSApp.dockTile.badgeLabel = nil;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@legendapp/spark-system",
|
|
3
|
-
"version": "0.0.1-next.
|
|
3
|
+
"version": "0.0.1-next.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
]
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@legendapp/spark-desktop-app": "0.0.1-next.
|
|
43
|
+
"@legendapp/spark-desktop-app": "0.0.1-next.3"
|
|
44
44
|
},
|
|
45
45
|
"private": true
|
|
46
46
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { TurboModuleRegistry, type TurboModule } from "react-native";
|
|
2
2
|
export interface Spec extends TurboModule { call(method: string, args: string): Promise<string>; }
|
|
3
|
-
export default TurboModuleRegistry.
|
|
3
|
+
export default TurboModuleRegistry.get<Spec>("NativeDesktopSystem");
|
|
@@ -1,35 +1,92 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
1
2
|
import Native from "./NativeDesktopSystem";
|
|
2
|
-
import { onDesktopEvent } from "@legendapp/spark-desktop-app";
|
|
3
|
-
|
|
3
|
+
import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
|
|
4
|
+
import { SparkError, asyncRegistration, invokeNative, parseNativeResult, type Availability, type AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
|
|
5
|
+
import { menuItems, selectableMenuIds, type MenuItem } from "@legendapp/spark-desktop-app/src/contracts/menu";
|
|
6
|
+
export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
|
|
7
|
+
type SupportedDockLeaf = Exclude<MenuItem, { type: "submenu" | "role" | "slider" }> extends infer Item
|
|
8
|
+
? Item extends unknown ? Omit<Item, "target" | "placement" | "icon" | "shortcut" | "hidden"> : never
|
|
9
|
+
: never;
|
|
10
|
+
export type DockMenuItem = SupportedDockLeaf | (Omit<Extract<MenuItem, { type: "submenu" }>, "items" | "target" | "placement" | "icon" | "shortcut" | "hidden"> & { items: readonly DockMenuItem[] });
|
|
11
|
+
type SupportedTaskbarLeaf = Extract<MenuItem, { type: "action" | "checkbox" }> extends infer Item
|
|
12
|
+
? Item extends unknown ? Omit<Item, "target" | "placement" | "icon" | "shortcut" | "hidden" | "disabled"> & { disabled?: false } : never
|
|
13
|
+
: never;
|
|
14
|
+
export type TaskbarMenuItem = SupportedTaskbarLeaf;
|
|
15
|
+
type LauncherMenuOptions = { items: readonly MenuItem[]; onAction: (event: { type: "action"; itemId: string }) => void };
|
|
16
|
+
export interface DockMenuOptions { items: readonly DockMenuItem[]; onAction: (event: { type: "action"; itemId: string }) => void }
|
|
17
|
+
export interface TaskbarMenuOptions { items: readonly TaskbarMenuItem[]; onAction: (event: { type: "action"; itemId: string }) => void }
|
|
18
|
+
export interface SystemInfo { osVersion: string; architecture: string; locale: string; dark: boolean; idleSeconds: number; onBattery: boolean; batteryLevel: number | null }
|
|
4
19
|
export type SystemEvent = { type: "sleep" | "wake" | "lock" | "unlock" | "powerChanged" | "appearanceChanged" | "displaysChanged" };
|
|
5
|
-
export type
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
export type LoginItemStatus = "enabled" | "disabled" | "requiresApproval" | "notFound" | "unavailable";
|
|
21
|
+
export interface AttentionOptions { kind?: "informational" | "critical" }
|
|
22
|
+
export interface PreventSleepOptions { reason: string; kind?: "display" | "system" }
|
|
23
|
+
let sequence = 0;
|
|
24
|
+
const token = () => `system-${Date.now()}-${++sequence}`;
|
|
25
|
+
export function getSystemAvailability(): Availability {
|
|
26
|
+
if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
|
|
27
|
+
return Native ? { available: true } : { available: false, reason: "missing-module" };
|
|
28
|
+
}
|
|
29
|
+
async function call<T>(method: string, args: object, validate: (value: unknown) => value is T): Promise<T> {
|
|
30
|
+
const available = getSystemAvailability();
|
|
31
|
+
if (!available.available) throw new SparkError(available.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "System APIs are unavailable");
|
|
32
|
+
return parseNativeResult(await invokeNative(() => Native!.call(method, JSON.stringify(args))), validate);
|
|
33
|
+
}
|
|
34
|
+
async function command(method: string, args: object = {}): Promise<void> { await call(method, args, (value): value is null => value === null); }
|
|
35
|
+
function options(value: object, keys: readonly string[]) {
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new SparkError("E_INVALID_ARGUMENT", "Expected options");
|
|
37
|
+
for (const key of Object.keys(value)) if (!keys.includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported system option: ${key}`);
|
|
38
|
+
}
|
|
39
|
+
function isInfo(value: unknown): value is SystemInfo {
|
|
40
|
+
if (!value || typeof value !== "object") return false;
|
|
41
|
+
const info = value as SystemInfo;
|
|
42
|
+
return [info.osVersion, info.architecture, info.locale].every(value => typeof value === "string" && value.length > 0) && typeof info.dark === "boolean" && typeof info.onBattery === "boolean" && Number.isFinite(info.idleSeconds) && info.idleSeconds >= 0 && (info.batteryLevel === null || (typeof info.batteryLevel === "number" && Number.isFinite(info.batteryLevel) && info.batteryLevel >= 0 && info.batteryLevel <= 1));
|
|
43
|
+
}
|
|
44
|
+
export function getSystemInfo(): Promise<SystemInfo> { return call("info", {}, isInfo); }
|
|
45
|
+
export function getLoginItemStatus(): Promise<LoginItemStatus> { return call("loginStatus", {}, (value): value is LoginItemStatus => typeof value === "string" && ["enabled", "disabled", "requiresApproval", "notFound", "unavailable"].includes(value)); }
|
|
46
|
+
export async function setLaunchAtLogin(enabled: boolean): Promise<void> {
|
|
47
|
+
if (typeof enabled !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "Expected enabled boolean");
|
|
48
|
+
await command("login", { enabled });
|
|
49
|
+
}
|
|
50
|
+
/** Dock badge on macOS, taskbar overlay on Windows; empty string clears it. */
|
|
51
|
+
export async function setAppBadge(label: string): Promise<void> {
|
|
52
|
+
if (typeof label !== "string" || label.includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", "Expected badge text without NUL");
|
|
53
|
+
await command("badge", { label });
|
|
54
|
+
}
|
|
55
|
+
export async function requestAttention(value: AttentionOptions = {}): Promise<AsyncRegistration> {
|
|
56
|
+
options(value, ["kind"]);
|
|
57
|
+
if (value.kind !== undefined && value.kind !== "informational" && value.kind !== "critical") throw new SparkError("E_INVALID_ARGUMENT", "Unknown attention kind");
|
|
58
|
+
// AppKit can return a negative request token when no attention is necessary.
|
|
59
|
+
const id = await call("attention", { critical: value.kind === "critical" }, (value): value is number => typeof value === "number" && Number.isSafeInteger(value));
|
|
60
|
+
return asyncRegistration(() => {}, () => command("cancelAttention", { id }));
|
|
61
|
+
}
|
|
62
|
+
export async function preventSleep(value: PreventSleepOptions): Promise<AsyncRegistration> {
|
|
63
|
+
options(value, ["reason", "kind"]);
|
|
64
|
+
if (typeof value.reason !== "string" || !value.reason.trim() || value.reason.includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", "A reason is required");
|
|
65
|
+
if (value.kind !== undefined && value.kind !== "display" && value.kind !== "system") throw new SparkError("E_INVALID_ARGUMENT", "Unknown sleep prevention kind");
|
|
66
|
+
const id = await call("preventSleep", { reason: value.reason, kind: value.kind ?? "display" }, (value): value is number => typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
|
|
67
|
+
return asyncRegistration(() => {}, () => command("allowSleep", { id }));
|
|
68
|
+
}
|
|
69
|
+
async function createLauncherMenu(value: LauncherMenuOptions, platform: "macos" | "windows"): Promise<AsyncRegistration> {
|
|
70
|
+
if (Platform.OS !== platform) throw new SparkError("E_UNSUPPORTED_PLATFORM", `${platform === "macos" ? "Dock" : "Taskbar"} menus require ${platform}`);
|
|
71
|
+
options(value, ["items", "onAction"]);
|
|
72
|
+
if (typeof value.onAction !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected onAction callback");
|
|
73
|
+
// Jump Lists cannot render native submenus, shortcuts, icons or disabled tasks.
|
|
74
|
+
const items = menuItems(value.items, { types: platform === "macos" ? ["action", "checkbox", "separator", "submenu"] : ["action", "checkbox"] }, platform);
|
|
75
|
+
const ids = selectableMenuIds(items), owner = token(), onAction = value.onAction;
|
|
76
|
+
let stopped = false, ready = false;
|
|
77
|
+
const subscription = onDesktopEvent(event => {
|
|
78
|
+
if (ready && !stopped && event.type === "dockAction" && event.owner === owner && typeof event.id === "string" && ids.has(event.id)) onAction({ type: "action", itemId: event.id });
|
|
79
|
+
}, { types: ["dockAction"], target: { field: "owner", value: owner } });
|
|
80
|
+
try { await command("dockMenu", { items, owner }); ready = true; } catch (error) { subscription.remove(); throw error; }
|
|
81
|
+
return asyncRegistration(() => { stopped = true; subscription.remove(); }, () => command("clearDockMenu", { owner }));
|
|
82
|
+
}
|
|
83
|
+
export function createDockMenu(options: DockMenuOptions): Promise<AsyncRegistration> { return createLauncherMenu(options, "macos"); }
|
|
84
|
+
export function createTaskbarMenu(options: TaskbarMenuOptions): Promise<AsyncRegistration> { return createLauncherMenu(options, "windows"); }
|
|
85
|
+
export async function onSystemEvent(handler: (event: SystemEvent) => void): Promise<AsyncRegistration> {
|
|
86
|
+
if (typeof handler !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected system event handler");
|
|
87
|
+
const types: readonly SystemEvent["type"][] = ["sleep", "wake", "lock", "unlock", "powerChanged", "appearanceChanged", "displaysChanged"];
|
|
88
|
+
const id = token(); let stopped = false;
|
|
89
|
+
const subscription = onDesktopEvent(event => { if (!stopped && types.includes(event.type as SystemEvent["type"])) handler({ type: event.type as SystemEvent["type"] }); }, { types });
|
|
90
|
+
try { await command("observe", { id }); } catch (error) { subscription.remove(); throw error; }
|
|
91
|
+
return asyncRegistration(() => { stopped = true; subscription.remove(); }, () => command("unobserve", { id }));
|
|
35
92
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include "NativeModules.h"
|
|
3
3
|
#include <algorithm>
|
|
4
4
|
#include <map>
|
|
5
|
+
#include <set>
|
|
5
6
|
#include <memory>
|
|
6
7
|
#include <filesystem>
|
|
7
8
|
#include <powrprof.h>
|
|
@@ -38,6 +39,7 @@ inline std::wstring AppId() {
|
|
|
38
39
|
struct SystemState {
|
|
39
40
|
React::ReactContext context;
|
|
40
41
|
HWND hwnd = nullptr; bool observing = false;
|
|
42
|
+
std::set<std::string> subscriptions;
|
|
41
43
|
uint64_t sequence = 0;
|
|
42
44
|
std::map<uint64_t, std::pair<handle, POWER_REQUEST_TYPE>> requests;
|
|
43
45
|
std::map<uint64_t, bool> attention;
|
|
@@ -110,7 +112,7 @@ struct SystemState {
|
|
|
110
112
|
Json::JsonObject result; auto version = std::stoull(std::wstring(Windows::System::Profile::AnalyticsInfo::VersionInfo().DeviceFamilyVersion()));
|
|
111
113
|
auto number = [&](wchar_t const *key, double value) { result.SetNamedValue(key, Json::JsonValue::CreateNumberValue(value)); };
|
|
112
114
|
result.SetNamedValue(L"osVersion", Json::JsonValue::CreateStringValue(std::to_wstring(version >> 48) + L"." + std::to_wstring((version >> 32) & 0xffff) + L"." + std::to_wstring((version >> 16) & 0xffff)));
|
|
113
|
-
SYSTEM_INFO info{};
|
|
115
|
+
SYSTEM_INFO info{}; ::GetSystemInfo(&info); result.SetNamedValue(L"architecture", Json::JsonValue::CreateStringValue(info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64 ? L"arm64" : info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? L"x64" : L"x86"));
|
|
114
116
|
wchar_t locale[LOCALE_NAME_MAX_LENGTH]{}; GetUserDefaultLocaleName(locale, LOCALE_NAME_MAX_LENGTH); result.SetNamedValue(L"locale", Json::JsonValue::CreateStringValue(locale));
|
|
115
117
|
Windows::UI::ViewManagement::UISettings settings; auto color = settings.GetColorValue(Windows::UI::ViewManagement::UIColorType::Background); result.SetNamedValue(L"dark", Json::JsonValue::CreateBooleanValue(color.R + color.G + color.B < 384));
|
|
116
118
|
LASTINPUTINFO input{sizeof(LASTINPUTINFO)}; if (!GetLastInputInfo(&input)) throw_last_error(); number(L"idleSeconds", static_cast<DWORD>(GetTickCount() - input.dwTime) / 1000.0);
|
|
@@ -130,7 +132,8 @@ struct SparkSystem {
|
|
|
130
132
|
try {
|
|
131
133
|
auto args = Json::JsonObject::Parse(to_hstring(encoded));
|
|
132
134
|
if (method == "info") { promise.Resolve(to_string(SystemState::Info().Stringify())); return; }
|
|
133
|
-
if (method == "observe") { current->EnsureWindow(); current->observing = true; }
|
|
135
|
+
if (method == "observe") { current->EnsureWindow(); current->subscriptions.insert(to_string(args.GetNamedString(L"id"))); current->observing = true; }
|
|
136
|
+
else if (method == "unobserve") { current->subscriptions.erase(to_string(args.GetNamedString(L"id"))); current->observing = !current->subscriptions.empty(); if (!current->observing && current->hwnd) { WTSUnRegisterSessionNotification(current->hwnd); DestroyWindow(current->hwnd); current->hwnd = nullptr; } }
|
|
134
137
|
else if (method == "loginStatus") { promise.Resolve("\"unavailable\""); return; }
|
|
135
138
|
else if (method == "login") { promise.Reject(React::ReactError{"E_UNAVAILABLE", "Login startup requires a standalone distribution app"}); return; }
|
|
136
139
|
else if (method == "badge") { auto label = args.GetNamedString(L"label"); HICON icon = label.empty() ? nullptr : SystemState::Badge(std::wstring(label)); auto result = SystemState::Taskbar()->SetOverlayIcon(MainWindow(), icon, label.c_str()); if (icon) DestroyIcon(icon); check_hresult(result); current->badge = !label.empty(); }
|
|
@@ -141,8 +144,9 @@ struct SparkSystem {
|
|
|
141
144
|
handle request{PowerCreateRequest(&description)}; if (!request || request.get() == INVALID_HANDLE_VALUE) { request.detach(); throw_last_error(); } auto kind = args.GetNamedString(L"kind") == L"system" ? PowerRequestSystemRequired : PowerRequestDisplayRequired;
|
|
142
145
|
if (!PowerSetRequest(request.get(), kind)) throw_last_error(); if (kind == PowerRequestDisplayRequired && !PowerSetRequest(request.get(), PowerRequestSystemRequired)) throw_last_error();
|
|
143
146
|
auto id = ++current->sequence; current->requests.emplace(id, std::make_pair(std::move(request), kind)); promise.Resolve(std::to_string(id)); return;
|
|
144
|
-
}
|
|
145
|
-
else if (method == "
|
|
147
|
+
} // Closing the dedicated handle releases every request held by this registration.
|
|
148
|
+
else if (method == "allowSleep") { current->requests.erase(static_cast<uint64_t>(args.GetNamedNumber(L"id"))); }
|
|
149
|
+
else if (method == "dockMenu") { if (!current->dockOwner.empty()) { promise.Reject(React::ReactError{"E_ALREADY_EXISTS", "Remove the existing taskbar menu before replacing it"}); return; } current->DockMenu(args); }
|
|
146
150
|
else if (method == "clearDockMenu") { if (current->dockOwner == std::wstring(args.GetNamedString(L"owner"))) current->ClearDock(); }
|
|
147
151
|
else throw hresult_invalid_argument(L"Unknown system operation");
|
|
148
152
|
promise.Resolve("null");
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
@interface SparkTrayItem : NSObject
|
|
5
5
|
@property NSString *identifier;
|
|
6
|
+
@property NSString *instanceId;
|
|
6
7
|
@property NSStatusItem *item;
|
|
7
8
|
@property NSDictionary *options;
|
|
8
9
|
- (void)clicked:(id)sender;
|
|
9
10
|
- (void)selected:(NSMenuItem *)sender;
|
|
10
11
|
@end
|
|
11
12
|
@implementation SparkTrayItem
|
|
12
|
-
- (void)clicked:(id)sender { SparkEmit(@{ @"type": @"trayClick", @"trayId": self.identifier }); }
|
|
13
|
-
- (void)selected:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"trayAction", @"trayId": self.identifier, @"itemId": sender.representedObject }); }
|
|
13
|
+
- (void)clicked:(id)sender { SparkEmit(@{ @"type": @"trayClick", @"trayId": self.identifier, @"instanceId": self.instanceId }); }
|
|
14
|
+
- (void)selected:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"trayAction", @"trayId": self.identifier, @"instanceId": self.instanceId, @"itemId": sender.representedObject }); }
|
|
14
15
|
@end
|
|
15
16
|
static NSMenu *Menu(NSArray *items, SparkTrayItem *owner) {
|
|
16
17
|
NSMenu *menu = [NSMenu new]; menu.autoenablesItems = NO;
|
|
@@ -36,25 +37,36 @@ RCT_EXPORT_MODULE(NativeDesktopTray)
|
|
|
36
37
|
NSDictionary *args = SparkArgs(json); NSString *key = args[@"id"];
|
|
37
38
|
if (![key isKindOfClass:NSString.class] || !key.length) { SparkInvalid(reject, @"Tray needs an id"); return; }
|
|
38
39
|
SparkTrayItem *owner = self.items[key];
|
|
40
|
+
if (owner && ![owner.instanceId isEqual:args[@"instanceId"]]) { if ([method isEqual:@"remove"]) resolve(@"null"); else reject([method isEqual:@"create"] ? @"E_ALREADY_EXISTS" : @"E_NOT_FOUND", @"Tray belongs to a different instance", nil); return; }
|
|
39
41
|
if ([method isEqual:@"remove"]) { if (owner) [NSStatusBar.systemStatusBar removeStatusItem:owner.item]; [self.items removeObjectForKey:key]; resolve(@"null"); return; }
|
|
40
42
|
if (![method isEqual:@"create"] && ![method isEqual:@"update"]) { SparkInvalid(reject, @"Unknown tray operation"); return; }
|
|
41
|
-
if ([method isEqual:@"create"] && owner) { reject(@"
|
|
43
|
+
if ([method isEqual:@"create"] && owner) { reject(@"E_ALREADY_EXISTS", @"Tray id already exists", nil); return; }
|
|
42
44
|
if ([method isEqual:@"update"] && !owner) { reject(@"E_NOT_FOUND", @"Tray was removed", nil); return; }
|
|
43
45
|
NSMutableDictionary *options = [owner.options mutableCopy] ?: [NSMutableDictionary new]; [options addEntriesFromDictionary:args];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
BOOL imageChanged = !owner || args[@"symbol"] != nil || args[@"imagePath"] != nil;
|
|
47
|
+
BOOL menuChanged = !owner || args[@"menu"] != nil;
|
|
48
|
+
NSImage *image = imageChanged ? nil : owner.item.button.image;
|
|
49
|
+
if (imageChanged && [options[@"symbol"] length]) {
|
|
46
50
|
image = [NSImage imageWithSystemSymbolName:options[@"symbol"] accessibilityDescription:options[@"tooltip"] ?: options[@"title"]];
|
|
47
51
|
if (!image) { SparkInvalid(reject, @"Unknown SF Symbol name"); return; }
|
|
48
52
|
[image setTemplate:YES];
|
|
49
53
|
}
|
|
54
|
+
if (imageChanged && [options[@"imagePath"] length]) {
|
|
55
|
+
image = [[NSImage alloc] initWithContentsOfFile:options[@"imagePath"]];
|
|
56
|
+
if (!image) { reject(@"E_INVALID_DATA", @"Tray image could not be decoded", nil); return; }
|
|
57
|
+
NSSize size = image.size; CGFloat height = MAX(1, NSStatusBar.systemStatusBar.thickness - 4);
|
|
58
|
+
image.size = NSMakeSize(size.height > 0 ? height * size.width / size.height : height, height);
|
|
59
|
+
}
|
|
50
60
|
if (![options[@"title"] length] && !image) { SparkInvalid(reject, @"Tray needs a title or symbol"); return; }
|
|
51
|
-
if (!owner) { owner = [SparkTrayItem new]; owner.identifier = key; owner.item = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; self.items[key] = owner; }
|
|
61
|
+
if (!owner) { owner = [SparkTrayItem new]; owner.identifier = key; owner.instanceId = args[@"instanceId"]; owner.item = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; self.items[key] = owner; }
|
|
62
|
+
NSMenu *menu = menuChanged && [options[@"menu"] count] ? Menu(options[@"menu"], owner) : nil;
|
|
52
63
|
owner.options = options;
|
|
53
|
-
owner.item.button.title = options[@"title"] ?: @"";
|
|
64
|
+
owner.item.button.title = options[@"title"] ?: @"";
|
|
65
|
+
if (imageChanged) owner.item.button.image = image;
|
|
54
66
|
owner.item.button.imagePosition = NSImageLeft; owner.item.button.toolTip = options[@"tooltip"];
|
|
55
67
|
owner.item.button.target = owner; owner.item.button.action = @selector(clicked:);
|
|
56
68
|
owner.item.button.accessibilityIdentifier = [@"spark.tray." stringByAppendingString:key];
|
|
57
|
-
owner.item.menu =
|
|
69
|
+
if (menuChanged) owner.item.menu = menu;
|
|
58
70
|
resolve(@"null");
|
|
59
71
|
});
|
|
60
72
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@legendapp/spark-tray",
|
|
3
|
-
"version": "0.0.1-next.
|
|
3
|
+
"version": "0.0.1-next.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
]
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@legendapp/spark-desktop-app": "0.0.1-next.
|
|
43
|
+
"@legendapp/spark-desktop-app": "0.0.1-next.3"
|
|
44
44
|
},
|
|
45
45
|
"private": true
|
|
46
46
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { TurboModule } from "react-native";
|
|
2
2
|
import { TurboModuleRegistry } from "react-native";
|
|
3
3
|
export interface Spec extends TurboModule { call(method: string, args: string): Promise<string>; }
|
|
4
|
-
export default TurboModuleRegistry.
|
|
4
|
+
export default TurboModuleRegistry.get<Spec>("NativeDesktopTray");
|
|
@@ -1,46 +1,103 @@
|
|
|
1
|
+
import { nativePath } from "@legendapp/spark-desktop-app/src/contracts/path";
|
|
2
|
+
import { Platform } from "react-native";
|
|
1
3
|
import Native from "./NativeDesktopTray";
|
|
2
|
-
import { onDesktopEvent } from "@legendapp/spark-desktop-app";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
|
|
5
|
+
import { SparkError, asyncRegistration, invokeNative, parseNativeResult, type Availability, type AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
|
|
6
|
+
import { menuItems, selectableMenuIds, type MenuItem as SharedMenuItem, type MenuWireItem } from "@legendapp/spark-desktop-app/src/contracts/menu";
|
|
7
|
+
type TrayEntry = { id: string; label: string; disabled?: boolean; hidden?: boolean };
|
|
8
|
+
type TrayLeaf = { type: "separator" } | (TrayEntry & { type: "action" }) | (TrayEntry & { type: "checkbox"; checked: boolean });
|
|
9
|
+
export type MenuItem = TrayLeaf | (TrayEntry & { type: "submenu"; items: readonly MenuItem[] });
|
|
10
|
+
export type MenuAction = Extract<import("@legendapp/spark-desktop-app/src/contracts/menu").MenuAction, { type: "action" }>;
|
|
11
|
+
export type { MenuIcon } from "@legendapp/spark-desktop-app/src/contracts/menu";
|
|
12
|
+
export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
|
|
13
|
+
export type TrayAction = { type: "click" } | { type: "action"; itemId: string };
|
|
14
|
+
export interface TrayUpdate {
|
|
15
|
+
/** Visible text on macOS; accessible tooltip fallback on Windows. */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Local image file; PNG is supported on both desktop hosts. Null clears it. */
|
|
18
|
+
image?: { path: string } | null;
|
|
19
|
+
tooltip?: string;
|
|
20
|
+
/** Replaces the entire menu; [] removes it. */
|
|
21
|
+
menu?: readonly MenuItem[];
|
|
22
|
+
/** Replaces macOS presentation options; null clears the symbol. */
|
|
23
|
+
macos?: { symbol?: string | null };
|
|
24
|
+
}
|
|
25
|
+
export interface TrayOptions extends TrayUpdate { id: string; onAction?: (action: TrayAction) => void }
|
|
26
|
+
export interface Tray extends AsyncRegistration { readonly id: string; update(changes: TrayUpdate): Promise<void> }
|
|
27
|
+
interface Wire { id: string; instanceId: string; imagePath: string; title: string; tooltip?: string; symbol: string; menu: MenuWireItem[] }
|
|
28
|
+
export function getTrayAvailability(): Availability {
|
|
29
|
+
if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
|
|
30
|
+
return Native ? { available: true } : { available: false, reason: "missing-module" };
|
|
31
|
+
}
|
|
32
|
+
async function call(method: string, args: object): Promise<void> {
|
|
33
|
+
const available = getTrayAvailability();
|
|
34
|
+
if (!available.available) throw new SparkError(available.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Tray is unavailable");
|
|
35
|
+
parseNativeResult(await invokeNative(() => Native!.call(method, JSON.stringify(args))), (value): value is null => value === null);
|
|
36
|
+
}
|
|
37
|
+
function validate(options: TrayUpdate, creating = false): Partial<Wire> {
|
|
38
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected tray options");
|
|
39
|
+
for (const key of Object.keys(options)) if (!["title", "tooltip", "image", "menu", "macos", ...(creating ? ["id", "onAction"] : [])].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported tray option: ${key}`);
|
|
40
|
+
const result: Partial<Wire> = {};
|
|
41
|
+
for (const key of ["title", "tooltip"] as const) if (options[key] !== undefined) {
|
|
42
|
+
if (typeof options[key] !== "string" || options[key].includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", `Tray ${key} must be a string without NUL`);
|
|
43
|
+
result[key] = options[key];
|
|
44
|
+
}
|
|
45
|
+
if (options.image !== undefined) {
|
|
46
|
+
if (options.image === null) result.imagePath = "";
|
|
47
|
+
else {
|
|
48
|
+
if (!options.image || typeof options.image !== "object" || Array.isArray(options.image) || Object.keys(options.image).some(key => key !== "path")) throw new SparkError("E_INVALID_ARGUMENT", "Expected an image path or null");
|
|
49
|
+
result.imagePath = nativePath(options.image.path, Platform.OS);
|
|
19
50
|
}
|
|
20
51
|
}
|
|
21
|
-
if (options.
|
|
52
|
+
if (options.macos !== undefined && Platform.OS === "macos") {
|
|
53
|
+
if (!options.macos || typeof options.macos !== "object" || Array.isArray(options.macos) || Object.keys(options.macos).some(key => key !== "symbol")) throw new SparkError("E_INVALID_ARGUMENT", "Expected macos tray options");
|
|
54
|
+
const symbol = options.macos.symbol;
|
|
55
|
+
if (symbol != null && (typeof symbol !== "string" || !symbol.trim() || symbol.includes("\0"))) throw new SparkError("E_INVALID_ARGUMENT", "Expected an SF Symbol name or null");
|
|
56
|
+
result.symbol = symbol ?? "";
|
|
57
|
+
}
|
|
58
|
+
if (options.menu !== undefined) result.menu = menuItems(options.menu, { types: ["action", "checkbox", "separator", "submenu"] }, Platform.OS === "windows" ? "windows" : "macos");
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
function presentation(options: Wire) {
|
|
62
|
+
if (options.imagePath && options.symbol) throw new SparkError("E_INVALID_ARGUMENT", "Choose a tray image or macOS symbol");
|
|
63
|
+
if (!options.title.trim() && !options.symbol && !options.imagePath) throw new SparkError("E_INVALID_ARGUMENT", "Tray needs a title, image or macOS symbol");
|
|
22
64
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
65
|
+
let sequence = 0;
|
|
66
|
+
export async function createTray(options: TrayOptions): Promise<Tray> {
|
|
67
|
+
const changes = validate(options, true);
|
|
68
|
+
if (typeof options.id !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(options.id)) throw new SparkError("E_INVALID_ARGUMENT", "Invalid tray id");
|
|
69
|
+
if (options.onAction !== undefined && typeof options.onAction !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected onAction callback");
|
|
70
|
+
const id = options.id, onAction = options.onAction;
|
|
71
|
+
const instanceId = `tray-${Date.now()}-${++sequence}-${Math.random().toString(36).slice(2)}`;
|
|
72
|
+
let current: Wire = { id, instanceId, imagePath: "", title: "", symbol: "", menu: [], ...changes };
|
|
73
|
+
presentation(current);
|
|
74
|
+
const available = getTrayAvailability();
|
|
75
|
+
if (!available.available) throw new SparkError(available.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Tray is unavailable");
|
|
76
|
+
let stopped = false, ready = false;
|
|
77
|
+
let selected = selectableMenuIds(current.menu);
|
|
27
78
|
let queue: Promise<unknown> = Promise.resolve();
|
|
28
79
|
const sub = onDesktopEvent(event => {
|
|
29
|
-
if (!
|
|
30
|
-
|
|
31
|
-
|
|
80
|
+
if (!ready || stopped || event.trayId !== id || event.instanceId !== instanceId) return;
|
|
81
|
+
if (event.type === "trayClick") onAction?.({ type: "click" });
|
|
82
|
+
else if (event.type === "trayAction" && typeof event.itemId === "string" && selected.has(event.itemId)) onAction?.({ type: "action", itemId: event.itemId });
|
|
83
|
+
}, { types: ["trayClick", "trayAction"], target: { field: "trayId", value: id } });
|
|
84
|
+
try { await call("create", current); ready = true; } catch (error) { sub.remove(); throw error; }
|
|
85
|
+
const registration = asyncRegistration(() => { stopped = true; sub.remove(); }, async () => { await queue; await call("remove", { id, instanceId }); });
|
|
32
86
|
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
87
|
+
id,
|
|
88
|
+
async update(changes) {
|
|
89
|
+
if (stopped) throw new SparkError("E_CLOSED", "Tray was removed");
|
|
90
|
+
const snapshot = validate(changes);
|
|
91
|
+
const next = queue.then(async () => {
|
|
92
|
+
const options = { ...current, ...snapshot };
|
|
93
|
+
presentation(options);
|
|
94
|
+
await call("update", { id, instanceId, ...snapshot });
|
|
95
|
+
current = options;
|
|
96
|
+
if (snapshot.menu !== undefined) selected = selectableMenuIds(options.menu);
|
|
97
|
+
});
|
|
37
98
|
queue = next.catch(() => {});
|
|
38
99
|
await next;
|
|
39
100
|
},
|
|
40
|
-
|
|
41
|
-
if (removed) return;
|
|
42
|
-
removed = true; sub.remove();
|
|
43
|
-
await queue; await call("remove", { id: options.id });
|
|
44
|
-
},
|
|
101
|
+
remove: registration.remove,
|
|
45
102
|
};
|
|
46
103
|
}
|