gloomberb 0.11.1 → 0.11.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/README.md +10 -9
- package/README.zh-CN.md +6 -6
- package/package.json +1 -1
- package/src/api-client/data.ts +7 -0
- package/src/api-client/index.test.ts +47 -0
- package/src/api-client/index.ts +61 -0
- package/src/api-client/paths.ts +16 -0
- package/src/api-client/types.ts +35 -0
- package/src/app/pane-runtime/index.tsx +4 -1
- package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
- package/src/app/pane-runtime/plugin-bindings.ts +25 -4
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
- package/src/app/runtime/desktop-deeplink.test.ts +16 -0
- package/src/app/runtime/desktop-deeplink.ts +66 -2
- package/src/app/runtime/desktop-menu.ts +3 -0
- package/src/app/runtime/plugin-bindings.ts +30 -28
- package/src/app.tsx +9 -2
- package/src/cli/commands/automation.test.ts +9 -0
- package/src/cli/commands/automation.ts +22 -73
- package/src/cli/index.ts +0 -2
- package/src/components/chart/composite/composite-chart.test.tsx +45 -2
- package/src/components/chart/composite/composite-chart.tsx +12 -9
- package/src/components/chart/composite/scene.ts +0 -21
- package/src/components/chart/composite/tools.ts +49 -0
- package/src/components/command-bar/commands/direct/index.ts +4 -0
- package/src/components/command-bar/commands/registry.ts +8 -1
- package/src/components/command-bar/layout-items/current-layout.ts +1 -1
- package/src/components/command-bar/pane-templates/workflow.ts +4 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
- package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
- package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
- package/src/components/command-bar/surface/index.test.tsx +6 -6
- package/src/components/command-bar/surface/pane.test.tsx +66 -2
- package/src/components/command-bar/view-model.test.ts +2 -1
- package/src/components/command-bar/workflow/ops.test.ts +54 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/layout/detached-pane-shell.tsx +74 -10
- package/src/components/layout/pane/title.ts +31 -3
- package/src/components/layout/shell/active-drag.ts +4 -11
- package/src/components/layout/shell/drag/index.ts +4 -20
- package/src/components/layout/shell/drag/runtime.ts +3 -4
- package/src/components/layout/shell/index.test.tsx +17 -3
- package/src/components/layout/shell/index.tsx +86 -30
- package/src/components/layout/shell/menu.test.ts +37 -1
- package/src/components/layout/shell/menu.ts +14 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
- package/src/components/layout/shell/pane/actions.ts +0 -5
- package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
- package/src/components/layout/shell/shortcuts.ts +6 -3
- package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
- package/src/components/layout/shell/ticker-link-menu.ts +78 -0
- package/src/components/layout/status-bar.test.tsx +62 -61
- package/src/components/layout/status-bar.tsx +53 -75
- package/src/core/state/app/layout-reducer.ts +36 -0
- package/src/core/state/app/layout.ts +10 -5
- package/src/core/state/app/state.ts +0 -13
- package/src/core/state/app/types.ts +6 -4
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/config/layout.ts +2 -0
- package/src/data/config/store/index.test.ts +27 -6
- package/src/data/config/store/normalize.ts +3 -5
- package/src/data/plugin-state-store.ts +11 -0
- package/src/data/sqlite/cache.test.ts +10 -0
- package/src/i18n/es.ts +3 -4
- package/src/i18n/ja.ts +3 -4
- package/src/i18n/ko.ts +3 -4
- package/src/i18n/zh-cn.ts +3 -4
- package/src/i18n/zh-tw.ts +3 -4
- package/src/layout-marketplace/api.test.ts +54 -0
- package/src/layout-marketplace/api.ts +65 -0
- package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
- package/src/layout-marketplace/gallery-desktop.tsx +375 -0
- package/src/layout-marketplace/gallery-terminal.tsx +376 -0
- package/src/layout-marketplace/gallery.test.tsx +84 -0
- package/src/layout-marketplace/gallery.tsx +323 -0
- package/src/layout-marketplace/geometry.test.ts +47 -0
- package/src/layout-marketplace/geometry.ts +80 -0
- package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
- package/src/layout-marketplace/mini-workspace.tsx +428 -0
- package/src/layout-marketplace/model.test.ts +118 -0
- package/src/layout-marketplace/model.ts +171 -0
- package/src/layout-marketplace/name-dialog.tsx +60 -0
- package/src/layout-marketplace/pane-imagery.ts +85 -0
- package/src/layout-marketplace/pane.tsx +20 -0
- package/src/layout-marketplace/payload.test.ts +246 -0
- package/src/layout-marketplace/payload.ts +624 -0
- package/src/layout-marketplace/use-marketplace.ts +63 -0
- package/src/market-data/hooks.tsx +0 -41
- package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
- package/src/plugins/broker-sync/normalize.ts +9 -2
- package/src/plugins/builtin/ai/index.tsx +18 -2
- package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
- package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
- package/src/plugins/builtin/alerts/alert-engine.test.ts +16 -1
- package/src/plugins/builtin/alerts/alert-engine.ts +4 -0
- package/src/plugins/builtin/alerts/index.tsx +4 -0
- package/src/plugins/builtin/alerts/pane.tsx +2 -8
- package/src/plugins/builtin/analytics/index.tsx +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +3 -0
- package/src/plugins/builtin/cds/client.test.ts +83 -0
- package/src/plugins/builtin/cds/client.ts +67 -0
- package/src/plugins/builtin/cds/index.tsx +41 -0
- package/src/plugins/builtin/cds/model.test.ts +232 -0
- package/src/plugins/builtin/cds/model.ts +401 -0
- package/src/plugins/builtin/cds/pane.test.tsx +132 -0
- package/src/plugins/builtin/cds/pane.tsx +358 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
- package/src/plugins/builtin/chart-composer/index.tsx +88 -2
- package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +81 -14
- package/src/plugins/builtin/chat/content/index.tsx +4 -1
- package/src/plugins/builtin/chat/controller/index.ts +8 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +14 -2
- package/src/plugins/builtin/chat/controller/utils.ts +4 -6
- package/src/plugins/builtin/chat/controller.test.ts +64 -14
- package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
- package/src/plugins/builtin/chat/profile-popover.ts +53 -22
- package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
- package/src/plugins/builtin/chat/sidebar.tsx +63 -0
- package/src/plugins/builtin/chat/test-harness.tsx +29 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +0 -4
- package/src/plugins/builtin/cloud/auth-model.ts +4 -1
- package/src/plugins/builtin/cloud/plugin.tsx +11 -1
- package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
- package/src/plugins/builtin/cloud-tweets/registration.ts +44 -5
- package/src/plugins/builtin/composite-plugins.ts +3 -1
- package/src/plugins/builtin/connections/index.ts +3 -0
- package/src/plugins/builtin/econ/calendar-model.ts +0 -5
- package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
- package/src/plugins/builtin/layout-manager/index.tsx +17 -4
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
- package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/options/index.tsx +1 -0
- package/src/plugins/builtin/options/view.test.tsx +25 -3
- package/src/plugins/builtin/options/view.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +2 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
- package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
- package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
- package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +33 -7
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +109 -12
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
- package/src/plugins/builtin/shared/public-pane.ts +9 -0
- package/src/plugins/builtin/shared/ticker-source.ts +32 -0
- package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
- package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
- package/src/plugins/builtin/world-indices/index.tsx +2 -0
- package/src/plugins/catalog-browser.test.ts +10 -0
- package/src/plugins/catalog-browser.ts +3 -1
- package/src/plugins/gridlock-notification.ts +2 -1
- package/src/plugins/pane-manager/gridlock.ts +87 -3
- package/src/plugins/pane-manager.test.ts +30 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +2 -2
- package/src/plugins/prediction-markets/controller/index.ts +5 -1
- package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
- package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
- package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
- package/src/plugins/prediction-markets/detail.test.tsx +29 -2
- package/src/plugins/prediction-markets/index.tsx +8 -4
- package/src/plugins/prediction-markets/navigation.ts +0 -1
- package/src/plugins/prediction-markets/pane.test.tsx +77 -0
- package/src/plugins/prediction-markets/pane.tsx +4 -0
- package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
- package/src/plugins/registry/index.ts +2 -0
- package/src/plugins/ticker-navigation.test.ts +24 -0
- package/src/plugins/ticker-navigation.ts +40 -0
- package/src/remote/schema.ts +1 -1
- package/src/renderers/browser/cloud-transport.test.ts +36 -0
- package/src/renderers/browser/cloud-transport.ts +19 -0
- package/src/renderers/browser/config-host.ts +0 -4
- package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
- package/src/renderers/browser/deeplink-bridge.ts +22 -0
- package/src/renderers/browser/main.tsx +3 -0
- package/src/renderers/cloudflare/worker.test.ts +8 -5
- package/src/renderers/cloudflare/worker.ts +6 -5
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/host/native.ts +0 -5
- package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
- package/src/renderers/electrobun/view/styles.css +107 -7
- package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
- package/src/renderers/electrobun/view/toast-host.tsx +69 -43
- package/src/renderers/electrobun/view/ui-host.tsx +4 -1
- package/src/renderers/share/layout-view.test.tsx +58 -0
- package/src/renderers/share/layout-view.tsx +133 -0
- package/src/renderers/share/main.tsx +54 -6
- package/src/renderers/share/styles.css +35 -0
- package/src/renderers/share/view.test.tsx +78 -0
- package/src/renderers/share/view.tsx +71 -0
- package/src/shares/api.test.ts +52 -3
- package/src/shares/api.ts +39 -15
- package/src/shares/location.test.ts +19 -0
- package/src/shares/location.ts +11 -0
- package/src/shares/pane.test.ts +110 -0
- package/src/shares/pane.ts +54 -0
- package/src/shares/payload.ts +74 -1
- package/src/state/app/bootstrap.ts +6 -7
- package/src/state/app/context/index.test.ts +41 -1
- package/src/state/app/context/index.test.tsx +34 -16
- package/src/state/selectors-ui.ts +0 -8
- package/src/sync/controller.test.ts +57 -1
- package/src/sync/controller.ts +22 -11
- package/src/sync/react.ts +13 -0
- package/src/theme/font-scale.ts +0 -11
- package/src/types/config.ts +35 -5
- package/src/types/desktop-menu.ts +1 -0
- package/src/types/plugin.ts +36 -0
- package/src/ui/context-menu.tsx +1 -1
- package/src/ui/toast.tsx +2 -0
- package/src/utils/back-navigation.ts +0 -4
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ bun run web:build
|
|
|
46
46
|
bunx wrangler dev
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Validate the public artifacts with `bun run web:audit` and `bun run cloudflare:dry-run`. `wrangler.jsonc`
|
|
49
|
+
Validate the public artifacts with `bun run web:audit` and `bun run cloudflare:dry-run`. `wrangler.jsonc` remains generic for local checks. After verification passes on `main`, GitHub Actions deploys `term.gloom.sh` with `wrangler.production.jsonc`. The private Gloom Cloud API is deployed separately.
|
|
50
50
|
|
|
51
51
|
## Install
|
|
52
52
|
|
|
@@ -166,10 +166,9 @@ Human-readable output is the default. Automation can opt into structured output
|
|
|
166
166
|
| `gloomberb portfolio [action]` | Manage manual portfolios |
|
|
167
167
|
| `gloomberb watchlist [action]` | Manage watchlists |
|
|
168
168
|
| `gloomberb notes|alerts [action]` | Manage local notes and alerts |
|
|
169
|
-
| `gloomberb broker|ibkr [action]` | Inspect broker
|
|
170
|
-
| `gloomberb ai providers|ask
|
|
169
|
+
| `gloomberb broker|ibkr [action]` | Inspect broker profiles |
|
|
170
|
+
| `gloomberb ai providers|ask` | Use configured AI providers |
|
|
171
171
|
| `gloomberb rss fetch <url>` | Fetch an RSS feed |
|
|
172
|
-
| `gloomberb buildout|congress|substack|x-feed|tweets` | Access cloud and social data sources when an existing session is available |
|
|
173
172
|
| `gloomberb provider status` | Inspect enabled data providers |
|
|
174
173
|
| `gloomberb config|cache|plugin|layout|pane|debug|doctor|version|changelog` | Inspect and manage local app state |
|
|
175
174
|
| `gloomberb fn [...]` | Run a pane-backed report command |
|
|
@@ -180,8 +179,6 @@ Human-readable output is the default. Automation can opt into structured output
|
|
|
180
179
|
| `gloomberb remove <name>` | Remove an installed plugin |
|
|
181
180
|
| `gloomberb update [name]` | Update plugins |
|
|
182
181
|
|
|
183
|
-
Commands that need a signed-in cloud session may return `auth_required`; sign-in, account management, and chat workflows are still handled in the app UI for now.
|
|
184
|
-
|
|
185
182
|
## Plugins
|
|
186
183
|
|
|
187
184
|
Everything from the portfolio list to broker integrations is a plugin. Plugins can add panes, tabs, columns, command bar commands, CLI commands, status bar widgets, and data providers.
|
|
@@ -206,7 +203,7 @@ See [PLUGINS.md](PLUGINS.md) for the plugin API and the shared UI surface availa
|
|
|
206
203
|
| `Ctrl+Shift+M` | Move focused window (`WIN resize` starts resize mode) |
|
|
207
204
|
| `Ctrl+Shift+D` | Dock or float focused pane |
|
|
208
205
|
| `Ctrl+Shift+L` | Layout actions |
|
|
209
|
-
| `Ctrl+Shift+G` |
|
|
206
|
+
| `Ctrl+Shift+G` | Tidy windows |
|
|
210
207
|
| `Tab` | Switch panes |
|
|
211
208
|
| `j` / `k` | Navigate lists |
|
|
212
209
|
| `h` / `l` | Switch tabs |
|
|
@@ -289,6 +286,7 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
|
|
|
289
286
|
| `AUCT` | Treasury auction results: high rate, bid-to-cover, indirect share, and size |
|
|
290
287
|
| `VIX` | VIX 30-day/3-month implied-volatility curve |
|
|
291
288
|
| `CRD` | Credit spreads |
|
|
289
|
+
| `CDS [ticker]` | Single-name corporate CDS activity: most-active issuers, or one issuer's trades |
|
|
292
290
|
| `ERN` | Earnings calendar |
|
|
293
291
|
| `IPO` | Upcoming and recent IPOs |
|
|
294
292
|
| `HALT` | US trading halts with reason and resumption times |
|
|
@@ -318,9 +316,10 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
|
|
|
318
316
|
| `AW` / `AP <ticker>` | Add a ticker to the active watchlist or portfolio |
|
|
319
317
|
| `RW` / `RP <ticker>` | Remove a ticker from the active watchlist or portfolio |
|
|
320
318
|
| `PS` | Open focused pane settings |
|
|
321
|
-
| `LAY
|
|
319
|
+
| `LAY` | Open the layout browser to switch, publish, or add layouts |
|
|
320
|
+
| `LMA <query>` | Layout and pane arrangement actions |
|
|
322
321
|
| `WIN move\|resize` | Move or resize the focused window |
|
|
323
|
-
| `GL` |
|
|
322
|
+
| `GL` | Tidy all windows |
|
|
324
323
|
| `SB` | Toggle the status bar |
|
|
325
324
|
| `VF` | Toggle quote value flashing |
|
|
326
325
|
| `TH <theme>` | Change color theme |
|
|
@@ -333,6 +332,8 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
|
|
|
333
332
|
| `LANG <locale>` | Change interface language (`auto`, `en`, `es`, `zh-CN`, `zh-TW`, `ja`, or `ko`) |
|
|
334
333
|
| `PL <plugin>` | Manage plugins |
|
|
335
334
|
|
|
335
|
+
Published layouts preserve portable pane setup and state, including searches, chart viewport, and drawings. Credentials, accounts, portfolios, and pane fields marked private stay local. Publishing copies a durable `term.gloom.sh/l/...` link for social sharing.
|
|
336
|
+
|
|
336
337
|
## Gloom Cloud sign-in
|
|
337
338
|
|
|
338
339
|
Sign in with email and password, or pick `Log In with QR Code` from the command bar and scan the code with the Gloomberb mobile companion app to sign the terminal in without typing. The onboarding wizard offers the same QR option as the recommended path, with email and password as the alternative.
|
package/README.zh-CN.md
CHANGED
|
@@ -136,10 +136,9 @@ gloomberb
|
|
|
136
136
|
| `gloomberb portfolio [action]` | 管理手动投资组合 |
|
|
137
137
|
| `gloomberb watchlist [action]` | 管理自选列表 |
|
|
138
138
|
| `gloomberb notes\|alerts [action]` | 管理本地笔记与提醒 |
|
|
139
|
-
| `gloomberb broker\|ibkr [action]` |
|
|
140
|
-
| `gloomberb ai providers\|ask
|
|
139
|
+
| `gloomberb broker\|ibkr [action]` | 查看券商配置 |
|
|
140
|
+
| `gloomberb ai providers\|ask` | 使用已配置的 AI 服务商 |
|
|
141
141
|
| `gloomberb rss fetch <url>` | 抓取 RSS 订阅源 |
|
|
142
|
-
| `gloomberb buildout\|congress\|substack\|x-feed\|tweets` | 在已有会话可用时访问云端与社交数据源 |
|
|
143
142
|
| `gloomberb provider status` | 查看已启用的数据源 |
|
|
144
143
|
| `gloomberb config\|cache\|plugin\|layout\|pane\|debug\|doctor\|version\|changelog` | 查看和管理本地应用状态 |
|
|
145
144
|
| `gloomberb fn [...]` | 运行基于面板的报告命令 |
|
|
@@ -150,8 +149,6 @@ gloomberb
|
|
|
150
149
|
| `gloomberb remove <name>` | 移除已安装插件 |
|
|
151
150
|
| `gloomberb update [name]` | 更新插件 |
|
|
152
151
|
|
|
153
|
-
需要已登录云端会话的命令可能返回 `auth_required`;登录、账户管理与聊天相关操作目前仍需在应用界面中完成。
|
|
154
|
-
|
|
155
152
|
## 插件
|
|
156
153
|
|
|
157
154
|
从投资组合列表到券商集成,一切皆为插件。插件可以添加面板、页签、列、命令栏命令、CLI 命令、状态栏组件与数据源。
|
|
@@ -260,7 +257,8 @@ gloomberb
|
|
|
260
257
|
| `AW` / `AP <ticker>` | 将股票添加到活动自选列表或投资组合 |
|
|
261
258
|
| `RW` / `RP <ticker>` | 从活动自选列表或投资组合中移除股票 |
|
|
262
259
|
| `PS` | 打开聚焦面板的设置 |
|
|
263
|
-
| `LAY
|
|
260
|
+
| `LAY` | 打开布局浏览器以切换、发布或添加布局 |
|
|
261
|
+
| `LMA <query>` | 布局与面板排列操作 |
|
|
264
262
|
| `WIN move\|resize` | 移动或缩放聚焦窗口 |
|
|
265
263
|
| `GL` | 所有可见面板网格对齐 |
|
|
266
264
|
| `SB` | 切换状态栏 |
|
|
@@ -270,6 +268,8 @@ gloomberb
|
|
|
270
268
|
| `LANG <locale>` | 切换界面语言(`auto`、`en`、`es`、`zh-CN`、`zh-TW`、`ja` 或 `ko`) |
|
|
271
269
|
| `PL <plugin>` | 管理插件 |
|
|
272
270
|
|
|
271
|
+
发布的布局会保留可移植的面板设置与状态,包括搜索条件、图表视窗和绘图;凭据、账户、投资组合以及标记为私密的字段始终保留在本地。发布后会自动复制一个长期有效的 `term.gloom.sh/l/...` 社交分享链接。
|
|
272
|
+
|
|
273
273
|
## 本地化界面
|
|
274
274
|
|
|
275
275
|
Gloomberb 内置 English、简体中文、繁體中文、日本語和한국어界面支持;英文仍是默认回退语言。
|
package/package.json
CHANGED
package/src/api-client/data.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { TickerFinancials } from "../types/financials";
|
|
|
2
2
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
3
3
|
import { normalizeTweetSearchResponse } from "./normalizers";
|
|
4
4
|
import {
|
|
5
|
+
cloudCdsPath,
|
|
5
6
|
cloudCongressHousePath,
|
|
6
7
|
cloudExchangeRatePath,
|
|
7
8
|
cloudSec13FPath,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
cloudStatementsPath,
|
|
18
19
|
cloudTickerTweetsPath,
|
|
19
20
|
cloudTweetSearchPath,
|
|
21
|
+
type CloudCdsParams,
|
|
20
22
|
type CloudCongressHouseParams,
|
|
21
23
|
type CloudFredSeriesParams,
|
|
22
24
|
type CloudHistoryParams,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
import type {
|
|
30
32
|
CloudAnalystResearchPayload,
|
|
31
33
|
CloudShortInterestPayload,
|
|
34
|
+
CloudCdsResponse,
|
|
32
35
|
CloudCompanyProfile,
|
|
33
36
|
CloudCongressHousePayload,
|
|
34
37
|
CloudCorporateActionsPayload,
|
|
@@ -209,6 +212,10 @@ export class CloudDataApi {
|
|
|
209
212
|
return this.request<CloudYieldPointPayload[]>("/cloud/econ/yield-curve");
|
|
210
213
|
}
|
|
211
214
|
|
|
215
|
+
async getCloudCds(params: CloudCdsParams = {}): Promise<CloudCdsResponse> {
|
|
216
|
+
return this.request<CloudCdsResponse>(cloudCdsPath(params));
|
|
217
|
+
}
|
|
218
|
+
|
|
212
219
|
async getCloudCongressHouse(params: CloudCongressHouseParams = {}): Promise<CloudCongressHousePayload> {
|
|
213
220
|
return this.request<CloudCongressHousePayload>(cloudCongressHousePath(params));
|
|
214
221
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { afterEach, describe, expect, jest, test } from "bun:test";
|
|
2
2
|
import type { AuthUser } from "./index";
|
|
3
3
|
import { apiClient, setCloudApiFetchTransport } from "./index";
|
|
4
|
+
import { publishableMarketplaceLayout } from "../layout-marketplace/payload";
|
|
5
|
+
import { createDefaultConfig } from "../types/config";
|
|
6
|
+
import type { PaneDef } from "../types/plugin";
|
|
4
7
|
|
|
5
8
|
const originalFetch = globalThis.fetch;
|
|
6
9
|
const originalWebSocket = globalThis.WebSocket;
|
|
@@ -108,6 +111,50 @@ afterEach(() => {
|
|
|
108
111
|
jest.useRealTimers();
|
|
109
112
|
});
|
|
110
113
|
|
|
114
|
+
describe("apiClient layout marketplace", () => {
|
|
115
|
+
test("lists and publishes validated layouts through authenticated transport", async () => {
|
|
116
|
+
const config = createDefaultConfig("/tmp/api-layout-marketplace-test");
|
|
117
|
+
const panes = new Map(config.layout.instances.map((instance) => [instance.paneId, {
|
|
118
|
+
id: instance.paneId,
|
|
119
|
+
name: instance.paneId,
|
|
120
|
+
component: () => null,
|
|
121
|
+
defaultPosition: "right" as const,
|
|
122
|
+
} satisfies PaneDef]));
|
|
123
|
+
const payload = publishableMarketplaceLayout(config.layout, {}, panes);
|
|
124
|
+
const entry = {
|
|
125
|
+
id: "0123456789abcdef0123456789abcdef",
|
|
126
|
+
name: "Research Desk",
|
|
127
|
+
...payload,
|
|
128
|
+
author: { username: "analyst", displayName: "Analyst" },
|
|
129
|
+
publishedAt: "2026-08-26T00:00:00.000Z",
|
|
130
|
+
};
|
|
131
|
+
const calls: Array<{ url: string; method: string; body?: unknown }> = [];
|
|
132
|
+
apiClient.setSessionToken("marketplace-session");
|
|
133
|
+
setCloudApiFetchTransport(async (url, init) => {
|
|
134
|
+
calls.push({
|
|
135
|
+
url,
|
|
136
|
+
method: init?.method ?? "GET",
|
|
137
|
+
...(typeof init?.body === "string" ? { body: JSON.parse(init.body) } : {}),
|
|
138
|
+
});
|
|
139
|
+
const path = new URL(url).pathname;
|
|
140
|
+
return createResponse(path === `/layouts/${entry.id}` || init?.method === "POST"
|
|
141
|
+
? entry
|
|
142
|
+
: { items: [entry] });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await expect(apiClient.listMarketplaceLayouts()).resolves.toEqual([entry]);
|
|
146
|
+
await expect(apiClient.getMarketplaceLayout(entry.id)).resolves.toEqual(entry);
|
|
147
|
+
await expect(apiClient.publishMarketplaceLayout(entry.name, payload)).resolves.toEqual(entry);
|
|
148
|
+
|
|
149
|
+
expect(calls.map((call) => [new URL(call.url).pathname, call.method])).toEqual([
|
|
150
|
+
["/layouts", "GET"],
|
|
151
|
+
[`/layouts/${entry.id}`, "GET"],
|
|
152
|
+
["/layouts", "POST"],
|
|
153
|
+
]);
|
|
154
|
+
expect(calls[2]?.body).toMatchObject({ name: "Research Desk", schemaVersion: 2, paneState: {} });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
111
158
|
describe("apiClient auth cookies", () => {
|
|
112
159
|
test("accepts a browser-managed api.gloom.sh cookie without exposing its value", async () => {
|
|
113
160
|
apiClient.setCookieSessionMode(true);
|
package/src/api-client/index.ts
CHANGED
|
@@ -3,9 +3,11 @@ import type { InstrumentSearchResult } from "../types/instrument";
|
|
|
3
3
|
import { CloudAuthApi } from "./auth";
|
|
4
4
|
import { CloudChatApi } from "./chat";
|
|
5
5
|
import { CloudDataApi } from "./data";
|
|
6
|
+
import { ApiRequestError } from "./errors";
|
|
6
7
|
import { CloudApiRequestTransport } from "./request";
|
|
7
8
|
import { CloudApiSocket } from "./socket";
|
|
8
9
|
import type {
|
|
10
|
+
CloudCdsParams,
|
|
9
11
|
CloudCongressHouseParams,
|
|
10
12
|
CloudFredSeriesParams,
|
|
11
13
|
CloudSecFilingParams,
|
|
@@ -44,6 +46,7 @@ import type {
|
|
|
44
46
|
CloudEconEventPayload,
|
|
45
47
|
CloudEquityDiagnosticMode,
|
|
46
48
|
CloudEquityDiagnosticResult,
|
|
49
|
+
CloudCdsResponse,
|
|
47
50
|
CloudFredSeriesPayload,
|
|
48
51
|
CloudYieldPointPayload,
|
|
49
52
|
CloudCongressHousePayload,
|
|
@@ -69,6 +72,13 @@ import type {
|
|
|
69
72
|
ScannerKind,
|
|
70
73
|
} from "./types";
|
|
71
74
|
import type { SyncSettings, SyncSnapshot } from "../sync/types";
|
|
75
|
+
import {
|
|
76
|
+
isMarketplaceLayoutId,
|
|
77
|
+
parseMarketplaceLayoutEntry,
|
|
78
|
+
parseMarketplaceLayoutList,
|
|
79
|
+
type LayoutMarketplaceEntry,
|
|
80
|
+
type LayoutMarketplacePayload,
|
|
81
|
+
} from "../layout-marketplace/payload";
|
|
72
82
|
|
|
73
83
|
export type * from "./types";
|
|
74
84
|
export { setCloudApiFetchTransport } from "./request";
|
|
@@ -282,6 +292,14 @@ class GloomApiClient {
|
|
|
282
292
|
return this.request<{ url: string }>("/stripe/checkout", { method: "POST", body: JSON.stringify({}) });
|
|
283
293
|
}
|
|
284
294
|
|
|
295
|
+
/** Stores a verified user's public terminal snapshot or pane handoff. */
|
|
296
|
+
async createTerminalShare(payload: unknown): Promise<{ id: string; expiresAt: string }> {
|
|
297
|
+
return this.request<{ id: string; expiresAt: string }>("/shares", {
|
|
298
|
+
method: "POST",
|
|
299
|
+
body: JSON.stringify(payload),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
285
303
|
/** Stripe billing portal for an account that already has a subscription. */
|
|
286
304
|
async createBillingPortal(): Promise<{ url: string }> {
|
|
287
305
|
return this.request<{ url: string }>("/stripe/portal", { method: "POST", body: JSON.stringify({}) });
|
|
@@ -321,6 +339,45 @@ class GloomApiClient {
|
|
|
321
339
|
});
|
|
322
340
|
}
|
|
323
341
|
|
|
342
|
+
async getMarketplaceLayout(
|
|
343
|
+
id: string,
|
|
344
|
+
options?: { signal?: AbortSignal },
|
|
345
|
+
): Promise<LayoutMarketplaceEntry | null> {
|
|
346
|
+
if (!isMarketplaceLayoutId(id)) return null;
|
|
347
|
+
try {
|
|
348
|
+
return parseMarketplaceLayoutEntry(await this.request<unknown>(`/layouts/${encodeURIComponent(id)}`, {
|
|
349
|
+
method: "GET",
|
|
350
|
+
signal: options?.signal,
|
|
351
|
+
}));
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error instanceof ApiRequestError && error.status === 404) return null;
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async listMarketplaceLayouts(options?: { signal?: AbortSignal }): Promise<LayoutMarketplaceEntry[]> {
|
|
359
|
+
const items = parseMarketplaceLayoutList(await this.request<unknown>("/layouts", {
|
|
360
|
+
method: "GET",
|
|
361
|
+
signal: options?.signal,
|
|
362
|
+
}));
|
|
363
|
+
if (!items) throw new Error("The layout marketplace returned invalid data.");
|
|
364
|
+
return items;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async publishMarketplaceLayout(
|
|
368
|
+
name: string,
|
|
369
|
+
payload: LayoutMarketplacePayload,
|
|
370
|
+
options?: { signal?: AbortSignal },
|
|
371
|
+
): Promise<LayoutMarketplaceEntry> {
|
|
372
|
+
const item = parseMarketplaceLayoutEntry(await this.request<unknown>("/layouts", {
|
|
373
|
+
method: "POST",
|
|
374
|
+
body: JSON.stringify({ name: name.trim(), ...payload }),
|
|
375
|
+
signal: options?.signal,
|
|
376
|
+
}));
|
|
377
|
+
if (!item) throw new Error("The layout marketplace returned invalid data.");
|
|
378
|
+
return item;
|
|
379
|
+
}
|
|
380
|
+
|
|
324
381
|
async updateSyncSettings(update: Partial<SyncSettings>): Promise<SyncSettings> {
|
|
325
382
|
const result = await this.request<{ settings: SyncSettings }>("/sync/settings", {
|
|
326
383
|
method: "PATCH",
|
|
@@ -582,6 +639,10 @@ class GloomApiClient {
|
|
|
582
639
|
return this.data.getCloudYieldCurve();
|
|
583
640
|
}
|
|
584
641
|
|
|
642
|
+
async getCloudCds(params: CloudCdsParams = {}): Promise<CloudCdsResponse> {
|
|
643
|
+
return this.data.getCloudCds(params);
|
|
644
|
+
}
|
|
645
|
+
|
|
585
646
|
async getCloudCongressHouse(params: CloudCongressHouseParams = {}): Promise<CloudCongressHousePayload> {
|
|
586
647
|
return this.data.getCloudCongressHouse(params);
|
|
587
648
|
}
|
package/src/api-client/paths.ts
CHANGED
|
@@ -16,6 +16,13 @@ export type CloudFredSeriesParams = {
|
|
|
16
16
|
sortOrder?: "asc" | "desc";
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
export type CloudCdsParams = {
|
|
20
|
+
/** Reference entity name, matched by the backend against DTCC issuer names. */
|
|
21
|
+
issuer?: string;
|
|
22
|
+
days?: number;
|
|
23
|
+
limit?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
19
26
|
export type CloudCongressHouseParams = {
|
|
20
27
|
year?: number;
|
|
21
28
|
limit?: number;
|
|
@@ -137,6 +144,15 @@ export function cloudFredSeriesPath(seriesId: string, params: CloudFredSeriesPar
|
|
|
137
144
|
return appendQuery(`/cloud/econ/series/${encodeURIComponent(seriesId)}`, search);
|
|
138
145
|
}
|
|
139
146
|
|
|
147
|
+
export function cloudCdsPath(params: CloudCdsParams = {}): string {
|
|
148
|
+
const search = new URLSearchParams();
|
|
149
|
+
const issuer = params.issuer?.trim();
|
|
150
|
+
if (issuer) search.set("issuer", issuer);
|
|
151
|
+
if (params.days != null) search.set("days", String(params.days));
|
|
152
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
153
|
+
return appendQuery("/cloud/credit/cds", search);
|
|
154
|
+
}
|
|
155
|
+
|
|
140
156
|
export type CloudSecFilingsParams = {
|
|
141
157
|
ticker: string;
|
|
142
158
|
limit?: number;
|
package/src/api-client/types.ts
CHANGED
|
@@ -322,6 +322,40 @@ export interface CloudFredSeriesPayload {
|
|
|
322
322
|
info: CloudFredSeriesInfoPayload | null;
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
+
/** One publicly disseminated single-name CDS transaction report. */
|
|
326
|
+
export interface CloudCdsTradePayload {
|
|
327
|
+
disseminationId: string;
|
|
328
|
+
originalDisseminationId: string | null;
|
|
329
|
+
actionType: string;
|
|
330
|
+
eventTimestamp: string;
|
|
331
|
+
executionTimestamp: string | null;
|
|
332
|
+
effectiveDate: string | null;
|
|
333
|
+
expirationDate: string | null;
|
|
334
|
+
maturityDate: string | null;
|
|
335
|
+
issuerName: string | null;
|
|
336
|
+
underlierId: string | null;
|
|
337
|
+
underlierIdSource: string | null;
|
|
338
|
+
upi: string | null;
|
|
339
|
+
upiFisn: string | null;
|
|
340
|
+
upiUnderlierName: string | null;
|
|
341
|
+
notionalAmount: number | null;
|
|
342
|
+
/** Reported notional is a regulatory cap, so the real trade was at least this size. */
|
|
343
|
+
notionalCapped: boolean;
|
|
344
|
+
notionalCurrency: string | null;
|
|
345
|
+
fixedRate: number | null;
|
|
346
|
+
reportedSpread: number | null;
|
|
347
|
+
/** Unit of `reportedSpread`, e.g. basis points or percent. */
|
|
348
|
+
spreadNotation: string | null;
|
|
349
|
+
upfrontAmount: number | null;
|
|
350
|
+
upfrontCurrency: string | null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface CloudCdsResponse {
|
|
354
|
+
source: string;
|
|
355
|
+
asOf: string | null;
|
|
356
|
+
trades: CloudCdsTradePayload[];
|
|
357
|
+
}
|
|
358
|
+
|
|
325
359
|
export interface CloudShortInterestPointPayload {
|
|
326
360
|
settlementDate: string;
|
|
327
361
|
sharesShort: number;
|
|
@@ -720,6 +754,7 @@ export interface CloudEquityDiagnosticPending {
|
|
|
720
754
|
|
|
721
755
|
export interface CloudEquityDiagnosticResponse {
|
|
722
756
|
schemaVersion: 1;
|
|
757
|
+
access: "preview" | "full";
|
|
723
758
|
symbol: string;
|
|
724
759
|
exchange: string;
|
|
725
760
|
companyName?: string;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
12
12
|
import {
|
|
13
13
|
getFocusedCollectionId,
|
|
14
|
+
resolveTickerForPane,
|
|
14
15
|
syncConfigActiveLayoutState,
|
|
15
16
|
type AppAction,
|
|
16
17
|
type AppState,
|
|
@@ -75,7 +76,9 @@ export function useAppPaneRuntime({
|
|
|
75
76
|
|
|
76
77
|
const persistLayout = useCallback((layout: LayoutConfig, options?: { pushHistory?: boolean; focusedPaneId?: string | null }) => {
|
|
77
78
|
const currentState = stateRef.current;
|
|
78
|
-
const normalizedLayout = normalizePaneLayout(layout
|
|
79
|
+
const normalizedLayout = normalizePaneLayout(layout, {
|
|
80
|
+
resolveOrphanSymbol: (instanceId) => resolveTickerForPane(currentState, instanceId),
|
|
81
|
+
});
|
|
79
82
|
if (options?.pushHistory !== false) {
|
|
80
83
|
dispatch({ type: "PUSH_LAYOUT_HISTORY" });
|
|
81
84
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createInitialState } from "../../state/app/context";
|
|
3
|
+
import { createTestDataProvider } from "../../test-support/data-provider";
|
|
4
|
+
import { createDefaultConfig, type PaneInstanceConfig } from "../../types/config";
|
|
5
|
+
import type { PaneDef } from "../../types/plugin";
|
|
6
|
+
import { bindAppPanePluginRegistry } from "./plugin-bindings";
|
|
7
|
+
|
|
8
|
+
const paneDef: PaneDef = {
|
|
9
|
+
id: "prediction-markets",
|
|
10
|
+
name: "Prediction Markets",
|
|
11
|
+
component: () => null,
|
|
12
|
+
defaultPosition: "left",
|
|
13
|
+
defaultMode: "floating",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function bindPortablePaneRuntime(disabledPlugins: string[] = []) {
|
|
17
|
+
const config = createDefaultConfig("/tmp/gloomberb-portable-pane-test");
|
|
18
|
+
config.disabledPlugins = disabledPlugins;
|
|
19
|
+
const state = createInitialState(config);
|
|
20
|
+
const actions: any[] = [];
|
|
21
|
+
const built: PaneInstanceConfig[] = [];
|
|
22
|
+
const placed: Array<{ instance: PaneInstanceConfig; options: unknown }> = [];
|
|
23
|
+
const pluginRegistry = {
|
|
24
|
+
panes: new Map([[paneDef.id, paneDef]]),
|
|
25
|
+
getPanePluginId: () => "prediction-markets",
|
|
26
|
+
getTermSizeFn: () => ({ width: 120, height: 40 }),
|
|
27
|
+
} as any;
|
|
28
|
+
|
|
29
|
+
bindAppPanePluginRegistry({
|
|
30
|
+
activatePane() {},
|
|
31
|
+
buildPaneInstance: (paneId, options) => {
|
|
32
|
+
const instance = { paneId, ...options } as PaneInstanceConfig;
|
|
33
|
+
built.push(instance);
|
|
34
|
+
return instance;
|
|
35
|
+
},
|
|
36
|
+
createPaneFromTemplate: async () => {},
|
|
37
|
+
dataProvider: createTestDataProvider(),
|
|
38
|
+
detachedPaneId: null,
|
|
39
|
+
dispatch: (action) => actions.push(action),
|
|
40
|
+
focusVisiblePane() {},
|
|
41
|
+
isDetachedWindow: false,
|
|
42
|
+
openPaneSettings: async () => {},
|
|
43
|
+
openPinnedTicker: async () => {},
|
|
44
|
+
persistLayout() {},
|
|
45
|
+
placePaneInstance: (instance, _def, options) => placed.push({ instance, options }),
|
|
46
|
+
placePinnedTickerTarget() {},
|
|
47
|
+
pluginRegistry,
|
|
48
|
+
publishTickerOpenTarget() {},
|
|
49
|
+
resolveOpenTickerTarget: async () => null,
|
|
50
|
+
resolvePaneTarget: () => null,
|
|
51
|
+
selectTickerInPane() {},
|
|
52
|
+
showPane() {},
|
|
53
|
+
state,
|
|
54
|
+
stateRef: { current: state },
|
|
55
|
+
switchTickerResearchTab() {},
|
|
56
|
+
tickerRepository: {} as any,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return { actions, built, placed, pluginRegistry };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const portablePane = {
|
|
63
|
+
schemaVersion: 2 as const,
|
|
64
|
+
sourceConfigVersion: 13,
|
|
65
|
+
layout: {
|
|
66
|
+
dockRoot: null,
|
|
67
|
+
instances: [{
|
|
68
|
+
instanceId: "p1",
|
|
69
|
+
paneId: "prediction-markets",
|
|
70
|
+
title: "Fed Markets",
|
|
71
|
+
binding: { kind: "none" as const },
|
|
72
|
+
params: { query: "fed" },
|
|
73
|
+
settings: { dense: true },
|
|
74
|
+
}],
|
|
75
|
+
floating: [{ instanceId: "p1", x: 0, y: 0, width: 100, height: 30 }],
|
|
76
|
+
detached: [],
|
|
77
|
+
},
|
|
78
|
+
paneState: {
|
|
79
|
+
p1: { pluginState: { "prediction-markets": { searchQuery: "fed" } } },
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
describe("portable pane runtime", () => {
|
|
84
|
+
test("creates a fresh floating pane and restores its complete state", async () => {
|
|
85
|
+
const runtime = bindPortablePaneRuntime();
|
|
86
|
+
|
|
87
|
+
await runtime.pluginRegistry.openPortablePaneShareAsyncFn(portablePane);
|
|
88
|
+
|
|
89
|
+
expect(runtime.built).toHaveLength(1);
|
|
90
|
+
expect(runtime.built[0]).toMatchObject({
|
|
91
|
+
paneId: "prediction-markets",
|
|
92
|
+
title: "Fed Markets",
|
|
93
|
+
binding: { kind: "none" },
|
|
94
|
+
params: { query: "fed" },
|
|
95
|
+
settings: { dense: true },
|
|
96
|
+
});
|
|
97
|
+
expect(runtime.built[0]!.instanceId).toMatch(/^prediction-markets:shared-/);
|
|
98
|
+
expect(runtime.placed).toEqual([{
|
|
99
|
+
instance: runtime.built[0],
|
|
100
|
+
options: { placement: "floating" },
|
|
101
|
+
}]);
|
|
102
|
+
expect(runtime.actions).toContainEqual({
|
|
103
|
+
type: "REPLACE_PANE_STATE",
|
|
104
|
+
paneId: runtime.built[0]!.instanceId,
|
|
105
|
+
state: portablePane.paneState.p1,
|
|
106
|
+
});
|
|
107
|
+
expect(runtime.actions.some((action) => action.type === "INSTALL_LAYOUT_COPY")).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("rejects panes owned by a disabled plugin", async () => {
|
|
111
|
+
const runtime = bindPortablePaneRuntime(["prediction-markets"]);
|
|
112
|
+
await expect(runtime.pluginRegistry.openPortablePaneShareAsyncFn(portablePane)).rejects.toThrow(
|
|
113
|
+
"unavailable",
|
|
114
|
+
);
|
|
115
|
+
expect(runtime.built).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "../../components/command-bar/workflow/ops";
|
|
6
6
|
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
7
7
|
import { setLayoutManagerDispatch } from "../../plugins/builtin/layout-manager";
|
|
8
|
+
import { materializeMarketplaceLayout } from "../../layout-marketplace/payload";
|
|
8
9
|
import {
|
|
9
10
|
isPaneInLayout,
|
|
10
11
|
removePane,
|
|
@@ -112,7 +113,7 @@ export function bindAppPanePluginRegistry({
|
|
|
112
113
|
launch: { kind: "plugin-command", commandId },
|
|
113
114
|
});
|
|
114
115
|
};
|
|
115
|
-
pluginRegistry.getLayoutFn = () =>
|
|
116
|
+
pluginRegistry.getLayoutFn = () => stateRef.current.config.layout;
|
|
116
117
|
pluginRegistry.updateLayoutFn = (layout) => {
|
|
117
118
|
if (isDetachedWindow) return;
|
|
118
119
|
persistLayout(layout);
|
|
@@ -134,6 +135,25 @@ export function bindAppPanePluginRegistry({
|
|
|
134
135
|
placePaneInstance,
|
|
135
136
|
});
|
|
136
137
|
};
|
|
138
|
+
pluginRegistry.openPortablePaneShareAsyncFn = async (payload) => {
|
|
139
|
+
if (isDetachedWindow) throw new Error("Open shared panes in the main window.");
|
|
140
|
+
const materialized = materializeMarketplaceLayout(payload);
|
|
141
|
+
const sharedPane = materialized.layout.instances[0];
|
|
142
|
+
if (!sharedPane) throw new Error("This shared pane is invalid.");
|
|
143
|
+
const paneDef = pluginRegistry.panes.get(sharedPane.paneId);
|
|
144
|
+
const ownerId = pluginRegistry.getPanePluginId(sharedPane.paneId);
|
|
145
|
+
if (!paneDef || (ownerId && stateRef.current.config.disabledPlugins.includes(ownerId))) {
|
|
146
|
+
throw new Error("This shared pane is unavailable in this version of Gloomberb.");
|
|
147
|
+
}
|
|
148
|
+
const instance = buildPaneInstance(sharedPane.paneId, sharedPane);
|
|
149
|
+
if (!instance) throw new Error("This shared pane could not be created.");
|
|
150
|
+
dispatch({
|
|
151
|
+
type: "REPLACE_PANE_STATE",
|
|
152
|
+
paneId: instance.instanceId,
|
|
153
|
+
state: materialized.paneState[instance.instanceId] ?? {},
|
|
154
|
+
});
|
|
155
|
+
placePaneInstance(instance, paneDef, { placement: "floating" });
|
|
156
|
+
};
|
|
137
157
|
pluginRegistry.createPaneFromTemplateFn = (templateId, options) => {
|
|
138
158
|
if (isDetachedWindow) return;
|
|
139
159
|
void createPaneFromTemplate(templateId, options);
|
|
@@ -151,8 +171,9 @@ export function bindAppPanePluginRegistry({
|
|
|
151
171
|
pluginRegistry.hidePaneFn = (paneId) => {
|
|
152
172
|
if (isDetachedWindow) return;
|
|
153
173
|
const instanceId = resolvePaneTarget(paneId);
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
const layout = stateRef.current.config.layout;
|
|
175
|
+
if (!instanceId || !isPaneInLayout(layout, instanceId)) return;
|
|
176
|
+
persistLayout(removePane(layout, instanceId));
|
|
156
177
|
};
|
|
157
178
|
pluginRegistry.focusPaneFn = (paneId) => {
|
|
158
179
|
if (isDetachedWindow) {
|
|
@@ -162,7 +183,7 @@ export function bindAppPanePluginRegistry({
|
|
|
162
183
|
return;
|
|
163
184
|
}
|
|
164
185
|
const instanceId = resolvePaneTarget(paneId);
|
|
165
|
-
if (!instanceId || !isPaneInLayout(
|
|
186
|
+
if (!instanceId || !isPaneInLayout(stateRef.current.config.layout, instanceId)) {
|
|
166
187
|
showPane(paneId);
|
|
167
188
|
return;
|
|
168
189
|
}
|