gloomberb 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -1
- package/src/api-client/auth.ts +1 -0
- package/src/api-client/index.test.ts +359 -108
- package/src/api-client/index.ts +24 -13
- package/src/api-client/socket.ts +70 -35
- package/src/api-client/types.ts +1 -1
- package/src/app/language-observer.test.tsx +82 -0
- package/src/app/language-observer.tsx +13 -0
- package/src/app/pane-runtime/index.tsx +2 -2
- package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
- package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
- package/src/app/pane-runtime/plugin-bindings.ts +2 -2
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
- package/src/app/runtime/broker-import.ts +2 -2
- package/src/app/runtime/plugin-bindings.ts +2 -2
- package/src/app/runtime/startup.ts +2 -2
- package/src/app.tsx +16 -7
- package/src/brokers/account-cache.ts +6 -6
- package/src/brokers/broker-ticker-sync.ts +3 -3
- package/src/brokers/remote-broker-adapter.ts +2 -2
- package/src/brokers/sync-broker-instance.ts +5 -6
- package/src/cli/commands/api.ts +3 -6
- package/src/cli/commands/automation.ts +7 -15
- package/src/cli/commands/market.ts +26 -61
- package/src/cli/commands/overview.ts +34 -45
- package/src/cli/commands/search.ts +25 -27
- package/src/cli/commands/system.ts +27 -57
- package/src/cli/commands/ticker.ts +62 -63
- package/src/cli/context.test.ts +63 -0
- package/src/cli/context.ts +15 -1
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/data.ts +2 -19
- package/src/cli/pane-functions/index.ts +26 -25
- package/src/cli/pane-functions/report.ts +1 -1
- package/src/cli/pane-functions/screenshot.ts +3 -3
- package/src/cli/scoped-context.ts +41 -0
- package/src/components/chart/composite/composite-chart.test.tsx +615 -18
- package/src/components/chart/composite/composite-chart.tsx +179 -79
- package/src/components/chart/composite/format.test.ts +20 -7
- package/src/components/chart/composite/index.ts +0 -21
- package/src/components/chart/composite/interactions.test.ts +90 -3
- package/src/components/chart/composite/interactions.ts +189 -24
- package/src/components/chart/composite/price-series.ts +0 -2
- package/src/components/chart/composite/rasterizer.ts +10 -9
- package/src/components/chart/composite/renderers.test.ts +108 -2
- package/src/components/chart/composite/scene.test.ts +36 -0
- package/src/components/chart/composite/scene.ts +5 -2
- package/src/components/chart/composite/text-renderer.ts +37 -25
- package/src/components/chart/composite/time-axis.test.ts +243 -0
- package/src/components/chart/composite/time-axis.ts +608 -0
- package/src/components/chart/composite/types.ts +12 -1
- package/src/components/chart/core/data.ts +2 -159
- package/src/components/chart/core/date-window.ts +6 -225
- package/src/components/chart/core/palette.ts +1 -12
- package/src/components/chart/core/pointer.ts +0 -142
- package/src/components/chart/core/types.ts +1 -58
- package/src/components/chart/cursor-motion.ts +2 -4
- package/src/components/chart/indicators/types.ts +0 -9
- package/src/components/chart/market-session.ts +0 -11
- package/src/components/chart/native/kitty/protocol.ts +1 -1
- package/src/components/chart/native/raster/layout.ts +3 -10
- package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
- package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
- package/src/components/chart/static/chart/surface.tsx +1 -2
- package/src/components/command-bar/action-runtime.ts +2 -2
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +2 -2
- package/src/components/command-bar/panel/index.tsx +11 -9
- package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
- package/src/components/command-bar/surface/index.tsx +2 -2
- package/src/components/command-bar/workflow/body.tsx +3 -3
- package/src/components/command-bar/workflow/collection-actions.ts +2 -2
- package/src/components/command-bar/workflow/coordinator.ts +2 -2
- package/src/components/command-bar/workflow/field-row.tsx +25 -11
- package/src/components/command-bar/workflow/tickers.ts +2 -2
- package/src/components/feed-data-table/stack-view.test.tsx +28 -36
- package/src/components/feed-data-table/stack-view.tsx +3 -1
- package/src/components/layout/detached-pane-shell.tsx +6 -6
- package/src/components/layout/header.tsx +13 -9
- package/src/components/layout/pane/content.test.tsx +42 -3
- package/src/components/layout/pane/content.tsx +3 -1
- package/src/components/layout/pane/footer/index.test.tsx +45 -2
- package/src/components/layout/pane/footer/registration.tsx +3 -1
- package/src/components/layout/shell/index.tsx +1 -2
- package/src/components/layout/shell/native/surfaces.ts +1 -1
- package/src/components/layout/shell/native/window-state.ts +1 -1
- package/src/components/layout/status-bar.tsx +12 -8
- package/src/components/layout/window-controls.tsx +8 -6
- package/src/components/onboarding/onboarding-wizard.tsx +3 -1
- package/src/components/pane-settings-dialog/desktop.tsx +0 -2
- package/src/components/pane-settings-dialog.tsx +0 -2
- package/src/components/pane-template-wizard.tsx +10 -9
- package/src/components/price-performance.tsx +2 -113
- package/src/components/price-sparkline/model.ts +15 -17
- package/src/components/price-sparkline/view.tsx +8 -7
- package/src/components/speedometer-gauge/desktop.tsx +2 -1
- package/src/components/speedometer-gauge/index.tsx +0 -2
- package/src/components/speedometer-gauge/terminal.tsx +24 -5
- package/src/components/ticker/badge/index.tsx +1 -18
- package/src/components/ui/data-table/opentui/index.tsx +3 -3
- package/src/components/ui/frame.tsx +1 -2
- package/src/components/ui/page-stack-view.test.tsx +126 -0
- package/src/components/ui/page-stack-view.tsx +24 -12
- package/src/components/ui/tabs.tsx +7 -3
- package/src/components/ui/ui.test.tsx +23 -2
- package/src/components/use-external-link-footer.ts +4 -2
- package/src/core/app-service-ports.ts +46 -0
- package/src/core/app-services.test.ts +66 -0
- package/src/core/app-services.ts +17 -15
- package/src/data/config/chart-settings.ts +3 -10
- package/src/data/config/store/index.test.ts +99 -2
- package/src/data/config/store/migrations.ts +278 -0
- package/src/data/config/store/normalize.ts +44 -186
- package/src/data/resource-store.ts +73 -12
- package/src/data/sqlite/cache.test.ts +104 -15
- package/src/i18n/index.test.ts +17 -0
- package/src/i18n/index.ts +23 -6
- package/src/i18n/ja.ts +13 -0
- package/src/i18n/ko.ts +13 -0
- package/src/i18n/react.ts +6 -0
- package/src/i18n/zh-cn.ts +13 -0
- package/src/i18n/zh-tw.ts +13 -0
- package/src/market-data/coordinator/auxiliary.ts +7 -3
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/index.test.ts +26 -0
- package/src/market-data/coordinator/index.ts +11 -5
- package/src/market-data/coordinator/quotes.ts +4 -40
- package/src/market-data/coordinator/subscriptions.test.ts +89 -1
- package/src/market-data/hooks.tsx +47 -1
- package/src/market-data/market/status.ts +6 -6
- package/src/market-data/performance.ts +1 -1
- package/src/market-data/quote-subscription-target.test.ts +37 -0
- package/src/market-data/quote-subscription-target.ts +60 -0
- package/src/market-data/quotes/freshness.ts +1 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/request-types.ts +3 -3
- package/src/news/aggregator.test.ts +97 -1
- package/src/news/aggregator.ts +100 -57
- package/src/news/hooks.test.tsx +64 -0
- package/src/news/hooks.ts +18 -25
- package/src/plugins/builtin/account-management/footer.ts +4 -2
- package/src/plugins/builtin/account-management/pane.tsx +9 -4
- package/src/plugins/builtin/ai/agent-history.ts +11 -7
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
- package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
- package/src/plugins/builtin/ai/index.test.ts +2 -2
- package/src/plugins/builtin/ai/index.tsx +10 -11
- package/src/plugins/builtin/ai/pane-settings.ts +1 -1
- package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +0 -6
- package/src/plugins/builtin/ai/screener/footer.ts +4 -1
- package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
- package/src/plugins/builtin/ai/workspace/model.ts +2 -27
- package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
- package/src/plugins/builtin/analytics/metrics.ts +1 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
- package/src/plugins/builtin/broker-manager/index.tsx +4 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
- package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
- package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
- package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
- package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
- package/src/plugins/builtin/chart-composer/index.tsx +0 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
- package/src/plugins/builtin/chart-composer/presets.ts +1 -19
- package/src/plugins/builtin/chart-composer/settings.ts +1 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
- package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
- package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
- package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
- package/src/plugins/builtin/chat/controller/session.ts +2 -0
- package/src/plugins/builtin/chat/controller/state.ts +7 -0
- package/src/plugins/builtin/chat/controller.test.ts +73 -20
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
- package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
- package/src/plugins/builtin/correlation/compute.ts +4 -10
- package/src/plugins/builtin/econ/index.tsx +17 -19
- package/src/plugins/builtin/holders/types.ts +0 -1
- package/src/plugins/builtin/insider/index.tsx +6 -31
- package/src/plugins/builtin/news/index.tsx +5 -7
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
- package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
- package/src/plugins/builtin/news/wire/read-state.ts +18 -28
- package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
- package/src/plugins/builtin/options/footer.test.ts +115 -0
- package/src/plugins/builtin/options/footer.ts +110 -0
- package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
- package/src/plugins/builtin/options/live-quotes.ts +147 -0
- package/src/plugins/builtin/options/view.test.tsx +71 -1
- package/src/plugins/builtin/options/view.tsx +39 -24
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
- package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
- package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
- package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
- package/src/plugins/builtin/sec/filing-content.ts +94 -0
- package/src/plugins/builtin/sec/index.tsx +21 -104
- package/src/plugins/builtin/sectors/sector-model.ts +1 -7
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
- package/src/plugins/builtin/shared/pane-footer.ts +82 -0
- package/src/plugins/builtin/shared/read-state.ts +64 -3
- package/src/plugins/builtin/substack/read-state.ts +16 -41
- package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
- package/src/plugins/builtin/yield-curve/index.tsx +11 -9
- package/src/plugins/ibkr/dialogs.tsx +3 -3
- package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
- package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
- package/src/plugins/ibkr/gateway/history.ts +2 -2
- package/src/plugins/ibkr/gateway/selection.ts +2 -2
- package/src/plugins/ibkr/gateway/service/index.ts +2 -2
- package/src/plugins/ibkr/gateway/service/native.ts +2 -2
- package/src/plugins/plugin-persistence.ts +3 -4
- package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
- package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
- package/src/plugins/prediction-markets/detail/book.tsx +2 -2
- package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
- package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
- package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
- package/src/plugins/registry/context.ts +3 -4
- package/src/plugins/registry/index.ts +5 -6
- package/src/react/input.test.ts +126 -3
- package/src/react/input.ts +120 -1
- package/src/remote/app-host.tsx +2 -13
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
- package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
- package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
- package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
- package/src/renderers/electrobun/bun/index.ts +123 -83
- package/src/renderers/electrobun/shared/protocol.ts +146 -7
- package/src/renderers/electrobun/view/app-services.ts +5 -8
- package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
- package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
- package/src/renderers/electrobun/view/config-host.ts +1 -1
- package/src/renderers/electrobun/view/context-menu.ts +1 -17
- package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
- package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
- package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
- package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
- package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
- package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
- package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
- package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
- package/src/renderers/electrobun/view/host/box.tsx +0 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
- package/src/renderers/electrobun/view/host/input.tsx +0 -1
- package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
- package/src/renderers/electrobun/view/host/native.ts +0 -1
- package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
- package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
- package/src/renderers/electrobun/view/host/text.tsx +0 -1
- package/src/renderers/electrobun/view/http-fetch.ts +3 -11
- package/src/renderers/electrobun/view/input-host.tsx +10 -53
- package/src/renderers/electrobun/view/key-event.ts +0 -1
- package/src/renderers/electrobun/view/main.tsx +3 -3
- package/src/renderers/electrobun/view/native-renderer.ts +0 -1
- package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
- package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
- package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
- package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
- package/src/renderers/electrobun/view/toast-host.tsx +0 -1
- package/src/renderers/electrobun/view/ui-host.tsx +1 -2
- package/src/renderers/electrobun/view/update-host.ts +1 -1
- package/src/renderers/opentui/chart-surface.tsx +2 -1
- package/src/renderers/opentui/dialog-host.tsx +20 -4
- package/src/renderers/opentui/image/loader.ts +12 -2
- package/src/renderers/opentui/input-host.tsx +7 -49
- package/src/renderers/opentui/start.tsx +2 -0
- package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
- package/src/sources/gloomberb-cloud/index.test.ts +173 -0
- package/src/sources/gloomberb-cloud/index.ts +42 -20
- package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
- package/src/sources/provider-router/cache.ts +1 -1
- package/src/sources/provider-router/document-routes.ts +10 -5
- package/src/sources/provider-router/financials.test.ts +14 -0
- package/src/sources/provider-router/financials.ts +6 -1
- package/src/sources/provider-router/history.test.ts +163 -1
- package/src/sources/provider-router/history.ts +278 -347
- package/src/sources/provider-router/index.ts +2 -2
- package/src/sources/provider-router/routing.test.ts +39 -0
- package/src/sources/provider-router/routing.ts +24 -11
- package/src/sources/provider-router/supplemental.ts +37 -17
- package/src/sources/sec-edgar/content.ts +2 -12
- package/src/sources/sec-edgar.ts +2 -13
- package/src/sources/yahoo-finance/history.ts +2 -2
- package/src/sources/yahoo-finance/options.ts +5 -0
- package/src/sources/yahoo-finance.ts +2 -2
- package/src/state/app/bootstrap.ts +2 -2
- package/src/state/app/context/index.tsx +2 -2
- package/src/state/hooks/quote-streaming.ts +66 -1
- package/src/state/hooks/session-snapshot.ts +2 -2
- package/src/state/selectors.ts +0 -9
- package/src/sync/controller.test.ts +98 -0
- package/src/sync/controller.ts +29 -5
- package/src/sync/react.ts +2 -2
- package/src/sync/types.ts +2 -2
- package/src/theme/colors.test.ts +10 -0
- package/src/theme/colors.ts +75 -78
- package/src/theme/theme-context.test.tsx +67 -0
- package/src/theme/theme-context.tsx +9 -5
- package/src/tickers/open-target.ts +2 -2
- package/src/tickers/search/upsert.ts +2 -2
- package/src/time-series/alignment.ts +0 -7
- package/src/time-series/chart-data.ts +145 -0
- package/src/time-series/date-window.ts +44 -0
- package/src/time-series/history-window.ts +21 -0
- package/src/time-series/hooks.test.ts +59 -0
- package/src/time-series/hooks.ts +33 -12
- package/src/time-series/market-resolution.ts +2 -2
- package/src/time-series/market.ts +1 -1
- package/src/time-series/range.ts +9 -0
- package/src/{components/chart/core → time-series}/resolution.ts +3 -102
- package/src/time-series/resolve.test.ts +203 -1
- package/src/time-series/resolve.ts +96 -18
- package/src/time-series/spec-studies.test.ts +15 -0
- package/src/time-series/spec.ts +2 -2
- package/src/time-series/studies.ts +5 -2
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.test.tsx +358 -0
- package/src/time-series/use-chart-resolution.ts +46 -4
- package/src/types/broker.ts +2 -2
- package/src/types/data-provider.ts +3 -3
- package/src/types/financials.ts +12 -0
- package/src/types/plugin.ts +2 -2
- package/src/ui/context-menu.tsx +3 -2
- package/src/ui/dialog.tsx +2 -2
- package/src/utils/html-entities.ts +3 -5
- package/src/utils/main-thread-monitor.ts +1 -1
- package/src/utils/price-history.ts +5 -5
- package/src/version.ts +3 -1
- package/src/components/chart/core/viewport.test.ts +0 -8
- package/src/components/chart/core/viewport.ts +0 -24
- package/src/time-series/index.ts +0 -14
- /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
package/src/react/input.test.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createShortcutRegistry,
|
|
4
|
+
shouldDeliverShortcut,
|
|
5
|
+
type KeyEventLike,
|
|
6
|
+
type ShortcutOptions,
|
|
7
|
+
type ShortcutRegistry,
|
|
8
|
+
} from "./input";
|
|
3
9
|
|
|
4
10
|
function keyEvent(overrides: Partial<KeyEventLike> = {}): KeyEventLike {
|
|
11
|
+
let defaultPrevented = false;
|
|
12
|
+
let propagationStopped = false;
|
|
5
13
|
return {
|
|
6
14
|
key: "k",
|
|
7
15
|
name: "k",
|
|
@@ -9,12 +17,36 @@ function keyEvent(overrides: Partial<KeyEventLike> = {}): KeyEventLike {
|
|
|
9
17
|
shift: false,
|
|
10
18
|
alt: false,
|
|
11
19
|
meta: false,
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
get defaultPrevented() {
|
|
21
|
+
return defaultPrevented;
|
|
22
|
+
},
|
|
23
|
+
get propagationStopped() {
|
|
24
|
+
return propagationStopped;
|
|
25
|
+
},
|
|
26
|
+
preventDefault: () => {
|
|
27
|
+
defaultPrevented = true;
|
|
28
|
+
},
|
|
29
|
+
stopPropagation: () => {
|
|
30
|
+
propagationStopped = true;
|
|
31
|
+
},
|
|
14
32
|
...overrides,
|
|
15
33
|
};
|
|
16
34
|
}
|
|
17
35
|
|
|
36
|
+
function registerShortcut(
|
|
37
|
+
registry: ShortcutRegistry,
|
|
38
|
+
handler: (event: KeyEventLike) => void,
|
|
39
|
+
options: ShortcutOptions & { isEnabled?: () => boolean } = {},
|
|
40
|
+
): () => void {
|
|
41
|
+
return registry.register({
|
|
42
|
+
handler,
|
|
43
|
+
isEnabled: options.isEnabled ?? (() => options.enabled !== false),
|
|
44
|
+
allowsEditable: () => options.allowEditable === true,
|
|
45
|
+
phase: options.phase ?? "normal",
|
|
46
|
+
scope: options.scope,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
18
50
|
describe("shouldDeliverShortcut", () => {
|
|
19
51
|
test("keeps plain app shortcuts out of editable targets by default", () => {
|
|
20
52
|
expect(shouldDeliverShortcut(keyEvent({ targetEditable: true }), false)).toBe(false);
|
|
@@ -27,3 +59,94 @@ describe("shouldDeliverShortcut", () => {
|
|
|
27
59
|
expect(shouldDeliverShortcut(keyEvent({ targetEditable: true, meta: true }), false)).toBe(true);
|
|
28
60
|
});
|
|
29
61
|
});
|
|
62
|
+
|
|
63
|
+
describe("shortcut registry", () => {
|
|
64
|
+
test("dispatches capture, normal, and bubble phases in order", () => {
|
|
65
|
+
const calls: string[] = [];
|
|
66
|
+
const registry = createShortcutRegistry();
|
|
67
|
+
registerShortcut(registry, () => calls.push("normal-1"));
|
|
68
|
+
registerShortcut(registry, () => calls.push("after"), { phase: "after" });
|
|
69
|
+
registerShortcut(registry, () => calls.push("before"), { phase: "before" });
|
|
70
|
+
registerShortcut(registry, () => calls.push("normal-2"));
|
|
71
|
+
|
|
72
|
+
registry.dispatch(keyEvent());
|
|
73
|
+
|
|
74
|
+
expect(calls).toEqual(["before", "normal-1", "normal-2", "after"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("skips the bubble phase after an earlier handler prevents the default action", () => {
|
|
78
|
+
const calls: string[] = [];
|
|
79
|
+
const registry = createShortcutRegistry();
|
|
80
|
+
registerShortcut(registry, (event) => {
|
|
81
|
+
calls.push("normal");
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
});
|
|
84
|
+
registerShortcut(registry, () => calls.push("after"), { phase: "after" });
|
|
85
|
+
|
|
86
|
+
registry.dispatch(keyEvent());
|
|
87
|
+
|
|
88
|
+
expect(calls).toEqual(["normal"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("gives the newest scope first refusal and keeps a consumed event in that scope", () => {
|
|
92
|
+
const calls: string[] = [];
|
|
93
|
+
const registry = createShortcutRegistry();
|
|
94
|
+
registerShortcut(registry, () => calls.push("older"), { scope: "older-dialog" });
|
|
95
|
+
registerShortcut(registry, () => calls.push("global"));
|
|
96
|
+
registerShortcut(registry, (event) => {
|
|
97
|
+
calls.push("newer-1");
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
}, { scope: "newer-dialog" });
|
|
100
|
+
registerShortcut(registry, () => calls.push("newer-2"), { scope: "newer-dialog" });
|
|
101
|
+
|
|
102
|
+
registry.dispatch(keyEvent());
|
|
103
|
+
|
|
104
|
+
expect(calls).toEqual(["newer-1", "newer-2"]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("falls through scopes until a handler consumes the event", () => {
|
|
108
|
+
const calls: string[] = [];
|
|
109
|
+
const registry = createShortcutRegistry();
|
|
110
|
+
registerShortcut(registry, () => calls.push("older"), { scope: "older" });
|
|
111
|
+
registerShortcut(registry, () => calls.push("global"));
|
|
112
|
+
registerShortcut(registry, () => calls.push("newer"), { scope: "newer" });
|
|
113
|
+
|
|
114
|
+
registry.dispatch(keyEvent());
|
|
115
|
+
|
|
116
|
+
expect(calls).toEqual(["newer", "older", "global"]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("applies editable and enabled gates before scope ordering", () => {
|
|
120
|
+
const calls: string[] = [];
|
|
121
|
+
const registry = createShortcutRegistry();
|
|
122
|
+
registerShortcut(registry, () => calls.push("editable-blocked"), { scope: "newest" });
|
|
123
|
+
registerShortcut(registry, () => calls.push("disabled"), {
|
|
124
|
+
scope: "newest",
|
|
125
|
+
enabled: false,
|
|
126
|
+
});
|
|
127
|
+
registerShortcut(registry, () => calls.push("allowed"), {
|
|
128
|
+
scope: "older",
|
|
129
|
+
allowEditable: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
registry.dispatch(keyEvent({ targetEditable: true }));
|
|
133
|
+
|
|
134
|
+
expect(calls).toEqual(["allowed"]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("stops propagation immediately and removes cleaned-up handlers", () => {
|
|
138
|
+
const calls: string[] = [];
|
|
139
|
+
const registry = createShortcutRegistry();
|
|
140
|
+
const unregister = registerShortcut(registry, () => calls.push("removed"));
|
|
141
|
+
unregister();
|
|
142
|
+
registerShortcut(registry, (event) => {
|
|
143
|
+
calls.push("stop");
|
|
144
|
+
event.stopPropagation();
|
|
145
|
+
});
|
|
146
|
+
registerShortcut(registry, () => calls.push("skipped"));
|
|
147
|
+
|
|
148
|
+
registry.dispatch(keyEvent());
|
|
149
|
+
|
|
150
|
+
expect(calls).toEqual(["stop"]);
|
|
151
|
+
});
|
|
152
|
+
});
|
package/src/react/input.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createElement,
|
|
4
|
+
useContext,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
2
9
|
|
|
3
10
|
export interface KeyEventLike {
|
|
4
11
|
key: string;
|
|
@@ -18,11 +25,123 @@ export interface KeyEventLike {
|
|
|
18
25
|
|
|
19
26
|
export interface ShortcutOptions {
|
|
20
27
|
enabled?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Groups related handlers into one interaction surface. Within each phase,
|
|
30
|
+
* newer enabled scopes receive events before older scopes and unscoped
|
|
31
|
+
* handlers. Once a scoped handler prevents the default action, only that
|
|
32
|
+
* scope receives the rest of the dispatch.
|
|
33
|
+
*/
|
|
21
34
|
scope?: string;
|
|
22
35
|
phase?: "before" | "normal" | "after";
|
|
23
36
|
allowEditable?: boolean;
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
interface ShortcutRegistration {
|
|
40
|
+
handler(event: KeyEventLike): void;
|
|
41
|
+
isEnabled(): boolean;
|
|
42
|
+
allowsEditable(): boolean;
|
|
43
|
+
scope?: string;
|
|
44
|
+
phase: NonNullable<ShortcutOptions["phase"]>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ShortcutEntry extends ShortcutRegistration {
|
|
48
|
+
order: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ShortcutRegistry {
|
|
52
|
+
register(registration: ShortcutRegistration): () => void;
|
|
53
|
+
dispatch(event: KeyEventLike): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const SHORTCUT_PHASES: ReadonlyArray<ShortcutEntry["phase"]> = [
|
|
57
|
+
"before",
|
|
58
|
+
"normal",
|
|
59
|
+
"after",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function normalizeShortcutScope(scope: string | undefined): string | undefined {
|
|
63
|
+
const normalized = scope?.trim();
|
|
64
|
+
return normalized || undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function orderShortcutEntries(entries: ShortcutEntry[]): ShortcutEntry[] {
|
|
68
|
+
const scopePriority = new Map<string, number>();
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (!entry.scope) continue;
|
|
71
|
+
scopePriority.set(entry.scope, Math.max(scopePriority.get(entry.scope) ?? 0, entry.order));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return entries.sort((left, right) => {
|
|
75
|
+
if (left.scope === right.scope) return left.order - right.order;
|
|
76
|
+
if (!left.scope) return 1;
|
|
77
|
+
if (!right.scope) return -1;
|
|
78
|
+
return (scopePriority.get(right.scope) ?? 0) - (scopePriority.get(left.scope) ?? 0);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createShortcutRegistry(): ShortcutRegistry {
|
|
83
|
+
let nextOrder = 1;
|
|
84
|
+
const entries = new Map<number, ShortcutEntry>();
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
register(registration) {
|
|
88
|
+
const order = nextOrder++;
|
|
89
|
+
entries.set(order, {
|
|
90
|
+
...registration,
|
|
91
|
+
scope: normalizeShortcutScope(registration.scope),
|
|
92
|
+
order,
|
|
93
|
+
});
|
|
94
|
+
return () => {
|
|
95
|
+
entries.delete(order);
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
dispatch(event) {
|
|
99
|
+
const eligible = orderShortcutEntries(
|
|
100
|
+
[...entries.values()].filter((entry) => (
|
|
101
|
+
entry.isEnabled() && shouldDeliverShortcut(event, entry.allowsEditable())
|
|
102
|
+
)),
|
|
103
|
+
);
|
|
104
|
+
let claimedScope: string | undefined;
|
|
105
|
+
|
|
106
|
+
for (const phase of SHORTCUT_PHASES) {
|
|
107
|
+
if (phase === "after" && (event.defaultPrevented || event.propagationStopped)) return;
|
|
108
|
+
for (const entry of eligible) {
|
|
109
|
+
if (entry.phase !== phase) continue;
|
|
110
|
+
if (claimedScope && entry.scope !== claimedScope) continue;
|
|
111
|
+
|
|
112
|
+
const wasDefaultPrevented = event.defaultPrevented === true;
|
|
113
|
+
entry.handler(event);
|
|
114
|
+
if (!wasDefaultPrevented && event.defaultPrevented && entry.scope) {
|
|
115
|
+
claimedScope = entry.scope;
|
|
116
|
+
}
|
|
117
|
+
if (event.propagationStopped) return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function useRegisteredShortcut(
|
|
125
|
+
registry: ShortcutRegistry,
|
|
126
|
+
handler: (event: KeyEventLike) => void,
|
|
127
|
+
options?: ShortcutOptions,
|
|
128
|
+
): void {
|
|
129
|
+
const handlerRef = useRef(handler);
|
|
130
|
+
const enabledRef = useRef(options?.enabled !== false);
|
|
131
|
+
const allowEditableRef = useRef(options?.allowEditable === true);
|
|
132
|
+
handlerRef.current = handler;
|
|
133
|
+
enabledRef.current = options?.enabled !== false;
|
|
134
|
+
allowEditableRef.current = options?.allowEditable === true;
|
|
135
|
+
|
|
136
|
+
useLayoutEffect(() => registry.register({
|
|
137
|
+
handler: (event) => handlerRef.current(event),
|
|
138
|
+
isEnabled: () => enabledRef.current,
|
|
139
|
+
allowsEditable: () => allowEditableRef.current,
|
|
140
|
+
phase: options?.phase ?? "normal",
|
|
141
|
+
scope: options?.scope,
|
|
142
|
+
}), [options?.phase, options?.scope, registry]);
|
|
143
|
+
}
|
|
144
|
+
|
|
26
145
|
export interface InputHost {
|
|
27
146
|
useShortcut(
|
|
28
147
|
handler: (event: KeyEventLike) => void,
|
package/src/remote/app-host.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
2
|
import type { Dispatch, ReactNode } from "react";
|
|
3
3
|
import type { PluginRegistry } from "../plugins/registry";
|
|
4
4
|
import type { AppAction, AppState } from "../state/app/context";
|
|
@@ -9,14 +9,7 @@ import type { RemoteControlRequest, RemoteControlResponse } from "./types";
|
|
|
9
9
|
|
|
10
10
|
export type RemoteControlHandler = (request: RemoteControlRequest) => Promise<RemoteControlResponse>;
|
|
11
11
|
|
|
12
|
-
const RemoteControlHandlerContext = createContext<RemoteControlHandler | null>(null);
|
|
13
|
-
|
|
14
|
-
export function useRemoteControlHandler(): RemoteControlHandler | null {
|
|
15
|
-
return useContext(RemoteControlHandlerContext);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
12
|
export interface RemoteControlAdapter {
|
|
19
|
-
handleRequest?: RemoteControlHandler;
|
|
20
13
|
startServer?(options: { dataDir: string; handle: RemoteControlHandler }): void | (() => void | Promise<void>);
|
|
21
14
|
registerHandler?(handler: RemoteControlHandler | null): void | (() => void);
|
|
22
15
|
}
|
|
@@ -82,9 +75,5 @@ export function RemoteControlHost({
|
|
|
82
75
|
};
|
|
83
76
|
}, [adapter, controller]);
|
|
84
77
|
|
|
85
|
-
return
|
|
86
|
-
<RemoteControlHandlerContext value={adapter?.handleRequest ?? (adapter ? controller.handle : null)}>
|
|
87
|
-
{children}
|
|
88
|
-
</RemoteControlHandlerContext>
|
|
89
|
-
);
|
|
78
|
+
return children;
|
|
90
79
|
}
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
} from "../../../../data/config/store";
|
|
10
10
|
import type { AppConfig } from "../../../../types/config";
|
|
11
11
|
import type { DesktopSharedStateSnapshot } from "../../../../types/desktop-window";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
DesktopBackendRequestPayload,
|
|
14
|
+
DesktopBackendRequestResponse,
|
|
15
|
+
DesktopCoreRequest,
|
|
16
|
+
} from "../../shared/protocol";
|
|
13
17
|
import {
|
|
14
18
|
checkElectrobunDesktopUpdate,
|
|
15
19
|
} from "./update";
|
|
@@ -18,22 +22,6 @@ import {
|
|
|
18
22
|
type DesktopWorkspace,
|
|
19
23
|
} from "./workspace";
|
|
20
24
|
|
|
21
|
-
const UNHANDLED_BACKEND_REQUEST = Symbol("unhandled backend request");
|
|
22
|
-
|
|
23
|
-
interface DesktopBackendRequestHandled {
|
|
24
|
-
handled: true;
|
|
25
|
-
value: unknown;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface DesktopBackendRequestUnhandled {
|
|
29
|
-
handled: false;
|
|
30
|
-
value: typeof UNHANDLED_BACKEND_REQUEST;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type DesktopBackendRequestResult =
|
|
34
|
-
| DesktopBackendRequestHandled
|
|
35
|
-
| DesktopBackendRequestUnhandled;
|
|
36
|
-
|
|
37
25
|
interface DesktopBackendRequestOptions {
|
|
38
26
|
clearCurrentConfig: () => void;
|
|
39
27
|
closeAllDetachedWindows: () => void;
|
|
@@ -42,8 +30,7 @@ interface DesktopBackendRequestOptions {
|
|
|
42
30
|
getDesktopWorkspace: () => DesktopWorkspace | null;
|
|
43
31
|
getServices: () => AppServices;
|
|
44
32
|
getSessionSnapshot: () => AppSessionSnapshot | null;
|
|
45
|
-
|
|
46
|
-
payload: Record<string, unknown>;
|
|
33
|
+
request: DesktopCoreRequest;
|
|
47
34
|
reconcileDetachedWindows: () => void;
|
|
48
35
|
registerCoreCapabilities: () => void;
|
|
49
36
|
sendDesktopState: (snapshot: DesktopSharedStateSnapshot) => void;
|
|
@@ -55,19 +42,10 @@ interface DesktopBackendRequestOptions {
|
|
|
55
42
|
teardownServices: () => void;
|
|
56
43
|
}
|
|
57
44
|
|
|
58
|
-
function handled(value: unknown): DesktopBackendRequestHandled {
|
|
59
|
-
return { handled: true, value };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function unhandled(): DesktopBackendRequestUnhandled {
|
|
63
|
-
return { handled: false, value: UNHANDLED_BACKEND_REQUEST };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
45
|
async function importDesktopConfig({
|
|
67
46
|
closeAllDetachedWindows,
|
|
68
47
|
getConfig,
|
|
69
48
|
getSessionSnapshot,
|
|
70
|
-
payload,
|
|
71
49
|
reconcileDetachedWindows,
|
|
72
50
|
registerCoreCapabilities,
|
|
73
51
|
sendDesktopState,
|
|
@@ -76,11 +54,13 @@ async function importDesktopConfig({
|
|
|
76
54
|
setServices,
|
|
77
55
|
syncConfigAccessors,
|
|
78
56
|
teardownServices,
|
|
79
|
-
}: DesktopBackendRequestOptions
|
|
57
|
+
}: DesktopBackendRequestOptions,
|
|
58
|
+
payload: DesktopBackendRequestPayload<"config.import">,
|
|
59
|
+
): Promise<AppConfig> {
|
|
80
60
|
closeAllDetachedWindows();
|
|
81
61
|
setDesktopWorkspace(null);
|
|
82
62
|
teardownServices();
|
|
83
|
-
setCurrentConfig(await importConfig(payload.dataDir
|
|
63
|
+
setCurrentConfig(await importConfig(payload.dataDir, payload.srcPath));
|
|
84
64
|
setServices(createAppServices({
|
|
85
65
|
config: getConfig(),
|
|
86
66
|
plugins: getDesktopBackendPlugins(),
|
|
@@ -96,7 +76,7 @@ async function importDesktopConfig({
|
|
|
96
76
|
|
|
97
77
|
export async function handleDesktopBackendRequest(
|
|
98
78
|
options: DesktopBackendRequestOptions,
|
|
99
|
-
): Promise<
|
|
79
|
+
): Promise<DesktopBackendRequestResponse<DesktopCoreRequest["method"]>> {
|
|
100
80
|
const {
|
|
101
81
|
clearCurrentConfig,
|
|
102
82
|
closeAllDetachedWindows,
|
|
@@ -104,64 +84,68 @@ export async function handleDesktopBackendRequest(
|
|
|
104
84
|
getConfig,
|
|
105
85
|
getDesktopWorkspace,
|
|
106
86
|
getServices,
|
|
107
|
-
|
|
108
|
-
payload,
|
|
87
|
+
request,
|
|
109
88
|
setCurrentConfig,
|
|
110
89
|
setDesktopWorkspace,
|
|
111
90
|
startUpdate,
|
|
112
91
|
teardownServices,
|
|
113
92
|
} = options;
|
|
114
93
|
|
|
115
|
-
switch (method) {
|
|
94
|
+
switch (request.method) {
|
|
116
95
|
case "update.check":
|
|
117
|
-
return
|
|
118
|
-
typeof payload.currentVersion === "string" ? payload.currentVersion : "",
|
|
119
|
-
)
|
|
96
|
+
return checkElectrobunDesktopUpdate(
|
|
97
|
+
typeof request.payload.currentVersion === "string" ? request.payload.currentVersion : "",
|
|
98
|
+
);
|
|
120
99
|
case "update.start": {
|
|
121
|
-
const release =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
startUpdate(typeof payload.currentVersion === "string" ? payload.currentVersion : release?.version ?? "");
|
|
125
|
-
return handled(null);
|
|
100
|
+
const { release, currentVersion } = request.payload;
|
|
101
|
+
startUpdate(typeof currentVersion === "string" ? currentVersion : release.version);
|
|
102
|
+
return null;
|
|
126
103
|
}
|
|
127
104
|
case "ticker.loadAll":
|
|
128
|
-
return
|
|
105
|
+
return getServices().tickerRepository.loadAllTickers();
|
|
129
106
|
case "ticker.load":
|
|
130
|
-
return
|
|
107
|
+
return getServices().tickerRepository.loadTicker(request.payload.symbol);
|
|
131
108
|
case "ticker.save":
|
|
132
|
-
|
|
109
|
+
await getServices().tickerRepository.saveTicker(request.payload.ticker);
|
|
110
|
+
return null;
|
|
133
111
|
case "ticker.delete":
|
|
134
|
-
|
|
112
|
+
await getServices().tickerRepository.deleteTicker(request.payload.symbol);
|
|
113
|
+
return null;
|
|
135
114
|
case "config.save": {
|
|
136
|
-
setCurrentConfig(payload.config
|
|
115
|
+
setCurrentConfig(request.payload.config);
|
|
137
116
|
const desktopWorkspace = getDesktopWorkspace();
|
|
138
117
|
if (desktopWorkspace) {
|
|
139
118
|
await commitDesktopSnapshot(desktopWorkspace.replaceConfig(getConfig(), { layoutChanged: true }));
|
|
140
|
-
return
|
|
119
|
+
return null;
|
|
141
120
|
}
|
|
142
|
-
|
|
121
|
+
await saveConfig(getConfig());
|
|
122
|
+
return null;
|
|
143
123
|
}
|
|
144
124
|
case "config.resetAllData":
|
|
145
125
|
closeAllDetachedWindows();
|
|
146
126
|
setDesktopWorkspace(null);
|
|
147
127
|
teardownServices();
|
|
148
128
|
clearCurrentConfig();
|
|
149
|
-
|
|
129
|
+
await resetAllData(request.payload.dataDir);
|
|
130
|
+
return null;
|
|
150
131
|
case "config.export":
|
|
151
|
-
|
|
132
|
+
await exportConfig(request.payload.config, request.payload.destPath);
|
|
133
|
+
return null;
|
|
152
134
|
case "config.import":
|
|
153
|
-
return
|
|
135
|
+
return importDesktopConfig(options, request.payload);
|
|
154
136
|
case "session.set":
|
|
155
137
|
getServices().persistence.sessions.set(
|
|
156
|
-
payload.sessionId
|
|
157
|
-
payload.value,
|
|
158
|
-
payload.schemaVersion
|
|
138
|
+
request.payload.sessionId,
|
|
139
|
+
request.payload.value,
|
|
140
|
+
request.payload.schemaVersion,
|
|
159
141
|
);
|
|
160
|
-
return
|
|
142
|
+
return null;
|
|
161
143
|
case "session.delete":
|
|
162
|
-
getServices().persistence.sessions.delete(payload.sessionId
|
|
163
|
-
return
|
|
164
|
-
default:
|
|
165
|
-
|
|
144
|
+
getServices().persistence.sessions.delete(request.payload.sessionId);
|
|
145
|
+
return null;
|
|
146
|
+
default: {
|
|
147
|
+
const exhaustive: never = request;
|
|
148
|
+
throw new Error(`Unknown core backend method: ${String(exhaustive)}`);
|
|
149
|
+
}
|
|
166
150
|
}
|
|
167
151
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AppServices } from "../../../../core/app-services";
|
|
2
|
+
import type { DesktopCapabilityRequest } from "../../shared/protocol";
|
|
2
3
|
import { encodeRpcValue } from "../../view/rpc-codec";
|
|
3
4
|
|
|
4
5
|
type CapabilityRegistry = AppServices["pluginRegistry"]["capabilities"];
|
|
@@ -45,26 +46,25 @@ export class DesktopCapabilityBridge<Rpc extends DesktopCapabilityRpc> {
|
|
|
45
46
|
|
|
46
47
|
async handle(
|
|
47
48
|
rpc: Rpc,
|
|
48
|
-
|
|
49
|
-
payload: Record<string, unknown>,
|
|
49
|
+
request: DesktopCapabilityRequest,
|
|
50
50
|
): Promise<unknown> {
|
|
51
51
|
const registry = this.options.getRegistry();
|
|
52
|
-
switch (method) {
|
|
52
|
+
switch (request.method) {
|
|
53
53
|
case "capability.invoke":
|
|
54
54
|
return registry.invoke(
|
|
55
|
-
payload.capabilityId
|
|
56
|
-
payload.operationId
|
|
57
|
-
payload.payload,
|
|
55
|
+
request.payload.capabilityId,
|
|
56
|
+
request.payload.operationId,
|
|
57
|
+
request.payload.payload,
|
|
58
58
|
{ renderer: true },
|
|
59
59
|
);
|
|
60
60
|
case "capability.subscribe": {
|
|
61
|
-
const clientSubscriptionId = payload.subscriptionId
|
|
61
|
+
const clientSubscriptionId = request.payload.subscriptionId;
|
|
62
62
|
const scopedSubscriptionId = this.scopeClientId(rpc, clientSubscriptionId);
|
|
63
63
|
this.subscriptions.get(scopedSubscriptionId)?.();
|
|
64
64
|
await registry.subscribe(
|
|
65
|
-
payload.capabilityId
|
|
66
|
-
payload.operationId
|
|
67
|
-
payload.payload,
|
|
65
|
+
request.payload.capabilityId,
|
|
66
|
+
request.payload.operationId,
|
|
67
|
+
request.payload.payload,
|
|
68
68
|
(event) => {
|
|
69
69
|
rpc.send["capability.event"]({
|
|
70
70
|
subscriptionId: clientSubscriptionId,
|
|
@@ -77,13 +77,15 @@ export class DesktopCapabilityBridge<Rpc extends DesktopCapabilityRpc> {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
case "capability.unsubscribe": {
|
|
80
|
-
const scopedSubscriptionId = this.scopeClientId(rpc, payload.subscriptionId
|
|
80
|
+
const scopedSubscriptionId = this.scopeClientId(rpc, request.payload.subscriptionId);
|
|
81
81
|
this.subscriptions.get(scopedSubscriptionId)?.();
|
|
82
82
|
this.subscriptions.delete(scopedSubscriptionId);
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
|
-
default:
|
|
86
|
-
|
|
85
|
+
default: {
|
|
86
|
+
const exhaustive: never = request;
|
|
87
|
+
throw new Error(`Unknown capability method: ${String(exhaustive)}`);
|
|
88
|
+
}
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import { ContextMenu, Utils, type BrowserWindow } from "electrobun/bun";
|
|
3
3
|
import { buildSoundCommand } from "../../../../notifications/app-notifier";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
DesktopBackendRequestResponse,
|
|
6
|
+
DesktopHostRequest,
|
|
7
|
+
DesktopRestartMessage,
|
|
8
|
+
DesktopWindowControlAction,
|
|
9
|
+
} from "../../shared/protocol";
|
|
5
10
|
import { getContextMenuRequestId, normalizeContextMenuItems } from "../context-menu/normalize";
|
|
6
11
|
import { MAIN_WINDOW_RPC_KEY } from "../window/focus";
|
|
7
|
-
import type { DesktopWindowControlAction } from "./window-controls";
|
|
8
12
|
|
|
9
13
|
interface DesktopHostRequestOptions<TRpc> {
|
|
10
14
|
clearMainWindow: () => void;
|
|
@@ -13,8 +17,7 @@ interface DesktopHostRequestOptions<TRpc> {
|
|
|
13
17
|
focusWindowForRpcKey: (windowKey: string) => void;
|
|
14
18
|
getMainWindow: () => BrowserWindow | null;
|
|
15
19
|
getRpcWindowKey: (rpc: TRpc) => string | undefined;
|
|
16
|
-
|
|
17
|
-
payload: Record<string, unknown>;
|
|
20
|
+
request: DesktopHostRequest;
|
|
18
21
|
restartDesktopApp: (message?: DesktopRestartMessage) => void;
|
|
19
22
|
rpc: TRpc;
|
|
20
23
|
teardownServices: () => void;
|
|
@@ -54,18 +57,17 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
54
57
|
focusWindowForRpcKey,
|
|
55
58
|
getMainWindow,
|
|
56
59
|
getRpcWindowKey,
|
|
57
|
-
|
|
58
|
-
payload,
|
|
60
|
+
request,
|
|
59
61
|
restartDesktopApp,
|
|
60
62
|
rpc,
|
|
61
63
|
teardownServices,
|
|
62
64
|
trackContextMenuRequest,
|
|
63
|
-
}: DesktopHostRequestOptions<TRpc>):
|
|
64
|
-
switch (method) {
|
|
65
|
+
}: DesktopHostRequestOptions<TRpc>): DesktopBackendRequestResponse<DesktopHostRequest["method"]> {
|
|
66
|
+
switch (request.method) {
|
|
65
67
|
case "host.restart":
|
|
66
68
|
restartDesktopApp({
|
|
67
|
-
reason: typeof payload.reason === "string" ? payload.reason : undefined,
|
|
68
|
-
source: typeof payload.source === "string" ? payload.source : "backend-request",
|
|
69
|
+
reason: typeof request.payload.reason === "string" ? request.payload.reason : undefined,
|
|
70
|
+
source: typeof request.payload.source === "string" ? request.payload.source : "backend-request",
|
|
69
71
|
});
|
|
70
72
|
return null;
|
|
71
73
|
case "host.exit": {
|
|
@@ -81,7 +83,7 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
83
85
|
case "host.windowControl": {
|
|
84
|
-
const action = normalizeWindowControlAction(payload.action);
|
|
86
|
+
const action = normalizeWindowControlAction(request.payload.action);
|
|
85
87
|
const windowKey = getRpcWindowKey(rpc);
|
|
86
88
|
if (action === "close" && windowKey === MAIN_WINDOW_RPC_KEY) {
|
|
87
89
|
closeAllDetachedWindows();
|
|
@@ -101,13 +103,13 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
101
103
|
return null;
|
|
102
104
|
}
|
|
103
105
|
case "host.openExternal":
|
|
104
|
-
if (typeof payload.url !== "string") {
|
|
106
|
+
if (typeof request.payload.url !== "string") {
|
|
105
107
|
throw new Error("host.openExternal requires a URL.");
|
|
106
108
|
}
|
|
107
|
-
Utils.openExternal(payload.url);
|
|
109
|
+
Utils.openExternal(request.payload.url);
|
|
108
110
|
return null;
|
|
109
111
|
case "host.copyText":
|
|
110
|
-
Utils.clipboardWriteText(normalizeText(payload.text) ?? "");
|
|
112
|
+
Utils.clipboardWriteText(normalizeText(request.payload.text) ?? "");
|
|
111
113
|
return null;
|
|
112
114
|
case "host.focusWindow": {
|
|
113
115
|
const windowKey = getRpcWindowKey(rpc);
|
|
@@ -115,7 +117,7 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
115
117
|
return null;
|
|
116
118
|
}
|
|
117
119
|
case "host.copyPngImage": {
|
|
118
|
-
const pngBase64 = normalizeText(payload.pngBase64);
|
|
120
|
+
const pngBase64 = normalizeText(request.payload.pngBase64);
|
|
119
121
|
if (!pngBase64) throw new Error("host.copyPngImage requires PNG data.");
|
|
120
122
|
Utils.clipboardWriteImage(new Uint8Array(Buffer.from(pngBase64, "base64")));
|
|
121
123
|
return null;
|
|
@@ -123,23 +125,25 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
123
125
|
case "host.readText":
|
|
124
126
|
return Utils.clipboardReadText() ?? "";
|
|
125
127
|
case "host.notify":
|
|
126
|
-
playNotificationSound(normalizeText(payload.sound));
|
|
128
|
+
playNotificationSound(normalizeText(request.payload.sound));
|
|
127
129
|
Utils.showNotification({
|
|
128
|
-
title: normalizeText(payload.title) ?? "Gloomberb",
|
|
129
|
-
body: normalizeText(payload.body),
|
|
130
|
-
subtitle: normalizeText(payload.subtitle),
|
|
130
|
+
title: normalizeText(request.payload.title) ?? "Gloomberb",
|
|
131
|
+
body: normalizeText(request.payload.body),
|
|
132
|
+
subtitle: normalizeText(request.payload.subtitle),
|
|
131
133
|
silent: true,
|
|
132
134
|
});
|
|
133
135
|
return null;
|
|
134
136
|
case "host.showContextMenu": {
|
|
135
|
-
const menu = normalizeContextMenuItems(payload.menu);
|
|
137
|
+
const menu = normalizeContextMenuItems(request.payload.menu);
|
|
136
138
|
if (menu.length === 0) return false;
|
|
137
139
|
const requestId = getContextMenuRequestId(menu);
|
|
138
140
|
if (requestId) trackContextMenuRequest(requestId, rpc);
|
|
139
141
|
ContextMenu.showContextMenu(menu as never);
|
|
140
142
|
return true;
|
|
141
143
|
}
|
|
142
|
-
default:
|
|
143
|
-
|
|
144
|
+
default: {
|
|
145
|
+
const exhaustive: never = request;
|
|
146
|
+
throw new Error(`Unknown host method: ${String(exhaustive)}`);
|
|
147
|
+
}
|
|
144
148
|
}
|
|
145
149
|
}
|