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.
Files changed (233) hide show
  1. package/README.md +10 -9
  2. package/README.zh-CN.md +6 -6
  3. package/package.json +1 -1
  4. package/src/api-client/data.ts +7 -0
  5. package/src/api-client/index.test.ts +47 -0
  6. package/src/api-client/index.ts +61 -0
  7. package/src/api-client/paths.ts +16 -0
  8. package/src/api-client/types.ts +35 -0
  9. package/src/app/pane-runtime/index.tsx +4 -1
  10. package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
  11. package/src/app/pane-runtime/plugin-bindings.ts +25 -4
  12. package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
  13. package/src/app/runtime/desktop-deeplink.test.ts +16 -0
  14. package/src/app/runtime/desktop-deeplink.ts +66 -2
  15. package/src/app/runtime/desktop-menu.ts +3 -0
  16. package/src/app/runtime/plugin-bindings.ts +30 -28
  17. package/src/app.tsx +9 -2
  18. package/src/cli/commands/automation.test.ts +9 -0
  19. package/src/cli/commands/automation.ts +22 -73
  20. package/src/cli/index.ts +0 -2
  21. package/src/components/chart/composite/composite-chart.test.tsx +45 -2
  22. package/src/components/chart/composite/composite-chart.tsx +12 -9
  23. package/src/components/chart/composite/scene.ts +0 -21
  24. package/src/components/chart/composite/tools.ts +49 -0
  25. package/src/components/command-bar/commands/direct/index.ts +4 -0
  26. package/src/components/command-bar/commands/registry.ts +8 -1
  27. package/src/components/command-bar/layout-items/current-layout.ts +1 -1
  28. package/src/components/command-bar/pane-templates/workflow.ts +4 -0
  29. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
  30. package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
  31. package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
  32. package/src/components/command-bar/surface/index.test.tsx +6 -6
  33. package/src/components/command-bar/surface/pane.test.tsx +66 -2
  34. package/src/components/command-bar/view-model.test.ts +2 -1
  35. package/src/components/command-bar/workflow/ops.test.ts +54 -0
  36. package/src/components/command-bar/workflow/ops.ts +1 -1
  37. package/src/components/layout/detached-pane-shell.tsx +74 -10
  38. package/src/components/layout/pane/title.ts +31 -3
  39. package/src/components/layout/shell/active-drag.ts +4 -11
  40. package/src/components/layout/shell/drag/index.ts +4 -20
  41. package/src/components/layout/shell/drag/runtime.ts +3 -4
  42. package/src/components/layout/shell/index.test.tsx +17 -3
  43. package/src/components/layout/shell/index.tsx +86 -30
  44. package/src/components/layout/shell/menu.test.ts +37 -1
  45. package/src/components/layout/shell/menu.ts +14 -0
  46. package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
  47. package/src/components/layout/shell/pane/actions.ts +0 -5
  48. package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
  49. package/src/components/layout/shell/shortcuts.ts +6 -3
  50. package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
  51. package/src/components/layout/shell/ticker-link-menu.ts +78 -0
  52. package/src/components/layout/status-bar.test.tsx +62 -61
  53. package/src/components/layout/status-bar.tsx +53 -75
  54. package/src/core/state/app/layout-reducer.ts +36 -0
  55. package/src/core/state/app/layout.ts +10 -5
  56. package/src/core/state/app/state.ts +0 -13
  57. package/src/core/state/app/types.ts +6 -4
  58. package/src/data/config/chart-settings.ts +1 -1
  59. package/src/data/config/layout.ts +2 -0
  60. package/src/data/config/store/index.test.ts +27 -6
  61. package/src/data/config/store/normalize.ts +3 -5
  62. package/src/data/plugin-state-store.ts +11 -0
  63. package/src/data/sqlite/cache.test.ts +10 -0
  64. package/src/i18n/es.ts +3 -4
  65. package/src/i18n/ja.ts +3 -4
  66. package/src/i18n/ko.ts +3 -4
  67. package/src/i18n/zh-cn.ts +3 -4
  68. package/src/i18n/zh-tw.ts +3 -4
  69. package/src/layout-marketplace/api.test.ts +54 -0
  70. package/src/layout-marketplace/api.ts +65 -0
  71. package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
  72. package/src/layout-marketplace/gallery-desktop.tsx +375 -0
  73. package/src/layout-marketplace/gallery-terminal.tsx +376 -0
  74. package/src/layout-marketplace/gallery.test.tsx +84 -0
  75. package/src/layout-marketplace/gallery.tsx +323 -0
  76. package/src/layout-marketplace/geometry.test.ts +47 -0
  77. package/src/layout-marketplace/geometry.ts +80 -0
  78. package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
  79. package/src/layout-marketplace/mini-workspace.tsx +428 -0
  80. package/src/layout-marketplace/model.test.ts +118 -0
  81. package/src/layout-marketplace/model.ts +171 -0
  82. package/src/layout-marketplace/name-dialog.tsx +60 -0
  83. package/src/layout-marketplace/pane-imagery.ts +85 -0
  84. package/src/layout-marketplace/pane.tsx +20 -0
  85. package/src/layout-marketplace/payload.test.ts +246 -0
  86. package/src/layout-marketplace/payload.ts +624 -0
  87. package/src/layout-marketplace/use-marketplace.ts +63 -0
  88. package/src/market-data/hooks.tsx +0 -41
  89. package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
  90. package/src/plugins/broker-sync/normalize.ts +9 -2
  91. package/src/plugins/builtin/ai/index.tsx +18 -2
  92. package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
  93. package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
  94. package/src/plugins/builtin/alerts/alert-engine.test.ts +16 -1
  95. package/src/plugins/builtin/alerts/alert-engine.ts +4 -0
  96. package/src/plugins/builtin/alerts/index.tsx +4 -0
  97. package/src/plugins/builtin/alerts/pane.tsx +2 -8
  98. package/src/plugins/builtin/analytics/index.tsx +3 -0
  99. package/src/plugins/builtin/broker-manager/index.tsx +3 -0
  100. package/src/plugins/builtin/cds/client.test.ts +83 -0
  101. package/src/plugins/builtin/cds/client.ts +67 -0
  102. package/src/plugins/builtin/cds/index.tsx +41 -0
  103. package/src/plugins/builtin/cds/model.test.ts +232 -0
  104. package/src/plugins/builtin/cds/model.ts +401 -0
  105. package/src/plugins/builtin/cds/pane.test.tsx +132 -0
  106. package/src/plugins/builtin/cds/pane.tsx +358 -0
  107. package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
  108. package/src/plugins/builtin/chart-composer/index.tsx +88 -2
  109. package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
  110. package/src/plugins/builtin/chart-composer/pane.tsx +81 -14
  111. package/src/plugins/builtin/chat/content/index.tsx +4 -1
  112. package/src/plugins/builtin/chat/controller/index.ts +8 -1
  113. package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
  114. package/src/plugins/builtin/chat/controller/session-runtime.ts +14 -2
  115. package/src/plugins/builtin/chat/controller/utils.ts +4 -6
  116. package/src/plugins/builtin/chat/controller.test.ts +64 -14
  117. package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
  118. package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
  119. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
  120. package/src/plugins/builtin/chat/profile-popover.ts +53 -22
  121. package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
  122. package/src/plugins/builtin/chat/sidebar.tsx +63 -0
  123. package/src/plugins/builtin/chat/test-harness.tsx +29 -1
  124. package/src/plugins/builtin/cloud/auth-commands.ts +0 -4
  125. package/src/plugins/builtin/cloud/auth-model.ts +4 -1
  126. package/src/plugins/builtin/cloud/plugin.tsx +11 -1
  127. package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
  128. package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
  129. package/src/plugins/builtin/cloud-tweets/registration.ts +44 -5
  130. package/src/plugins/builtin/composite-plugins.ts +3 -1
  131. package/src/plugins/builtin/connections/index.ts +3 -0
  132. package/src/plugins/builtin/econ/calendar-model.ts +0 -5
  133. package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
  134. package/src/plugins/builtin/help/index.tsx +2 -2
  135. package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
  136. package/src/plugins/builtin/layout-manager/index.tsx +17 -4
  137. package/src/plugins/builtin/news/index.tsx +1 -0
  138. package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
  139. package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
  140. package/src/plugins/builtin/news/wire/index.tsx +2 -2
  141. package/src/plugins/builtin/options/index.tsx +1 -0
  142. package/src/plugins/builtin/options/view.test.tsx +25 -3
  143. package/src/plugins/builtin/options/view.tsx +1 -0
  144. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +2 -0
  145. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  146. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  147. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
  148. package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
  149. package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
  150. package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
  151. package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
  152. package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
  153. package/src/plugins/builtin/portfolio-list/summary/index.tsx +21 -16
  154. package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
  155. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +33 -7
  156. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +109 -12
  157. package/src/plugins/builtin/research/index.tsx +4 -0
  158. package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
  159. package/src/plugins/builtin/shared/public-pane.ts +9 -0
  160. package/src/plugins/builtin/shared/ticker-source.ts +32 -0
  161. package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
  162. package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
  163. package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
  164. package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
  165. package/src/plugins/builtin/world-indices/index.tsx +2 -0
  166. package/src/plugins/catalog-browser.test.ts +10 -0
  167. package/src/plugins/catalog-browser.ts +3 -1
  168. package/src/plugins/gridlock-notification.ts +2 -1
  169. package/src/plugins/pane-manager/gridlock.ts +87 -3
  170. package/src/plugins/pane-manager.test.ts +30 -0
  171. package/src/plugins/pane-manager.ts +6 -1
  172. package/src/plugins/prediction-markets/chart.tsx +2 -2
  173. package/src/plugins/prediction-markets/controller/index.ts +5 -1
  174. package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
  175. package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
  176. package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
  177. package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
  178. package/src/plugins/prediction-markets/detail.test.tsx +29 -2
  179. package/src/plugins/prediction-markets/index.tsx +8 -4
  180. package/src/plugins/prediction-markets/navigation.ts +0 -1
  181. package/src/plugins/prediction-markets/pane.test.tsx +77 -0
  182. package/src/plugins/prediction-markets/pane.tsx +4 -0
  183. package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
  184. package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
  185. package/src/plugins/registry/index.ts +2 -0
  186. package/src/plugins/ticker-navigation.test.ts +24 -0
  187. package/src/plugins/ticker-navigation.ts +40 -0
  188. package/src/remote/schema.ts +1 -1
  189. package/src/renderers/browser/cloud-transport.test.ts +36 -0
  190. package/src/renderers/browser/cloud-transport.ts +19 -0
  191. package/src/renderers/browser/config-host.ts +0 -4
  192. package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
  193. package/src/renderers/browser/deeplink-bridge.ts +22 -0
  194. package/src/renderers/browser/main.tsx +3 -0
  195. package/src/renderers/cloudflare/worker.test.ts +8 -5
  196. package/src/renderers/cloudflare/worker.ts +6 -5
  197. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  198. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  199. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
  200. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  201. package/src/renderers/electrobun/view/host/native.ts +0 -5
  202. package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
  203. package/src/renderers/electrobun/view/styles.css +107 -7
  204. package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
  205. package/src/renderers/electrobun/view/toast-host.tsx +69 -43
  206. package/src/renderers/electrobun/view/ui-host.tsx +4 -1
  207. package/src/renderers/share/layout-view.test.tsx +58 -0
  208. package/src/renderers/share/layout-view.tsx +133 -0
  209. package/src/renderers/share/main.tsx +54 -6
  210. package/src/renderers/share/styles.css +35 -0
  211. package/src/renderers/share/view.test.tsx +78 -0
  212. package/src/renderers/share/view.tsx +71 -0
  213. package/src/shares/api.test.ts +52 -3
  214. package/src/shares/api.ts +39 -15
  215. package/src/shares/location.test.ts +19 -0
  216. package/src/shares/location.ts +11 -0
  217. package/src/shares/pane.test.ts +110 -0
  218. package/src/shares/pane.ts +54 -0
  219. package/src/shares/payload.ts +74 -1
  220. package/src/state/app/bootstrap.ts +6 -7
  221. package/src/state/app/context/index.test.ts +41 -1
  222. package/src/state/app/context/index.test.tsx +34 -16
  223. package/src/state/selectors-ui.ts +0 -8
  224. package/src/sync/controller.test.ts +57 -1
  225. package/src/sync/controller.ts +22 -11
  226. package/src/sync/react.ts +13 -0
  227. package/src/theme/font-scale.ts +0 -11
  228. package/src/types/config.ts +35 -5
  229. package/src/types/desktop-menu.ts +1 -0
  230. package/src/types/plugin.ts +36 -0
  231. package/src/ui/context-menu.tsx +1 -1
  232. package/src/ui/toast.tsx +2 -0
  233. package/src/utils/back-navigation.ts +0 -4
@@ -0,0 +1,375 @@
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ getPaneSidebarWidth,
4
+ PaneSidebar,
5
+ PaneSidebarRow,
6
+ } from "../components/layout/pane/sidebar";
7
+ import { Button } from "../components/ui/button";
8
+ import { TextField } from "../components/ui/fields";
9
+ import { Spinner } from "../components/ui/loading";
10
+ import { t, tf } from "../i18n";
11
+ import { useThemeColors } from "../theme/theme-context";
12
+ import { Box, ScrollBox, Text, TextAttributes, useUiCapabilities } from "../ui";
13
+ import type { LayoutGalleryController } from "./gallery";
14
+ import { MiniWorkspace } from "./mini-workspace";
15
+ import {
16
+ describeArrangement,
17
+ formatPublishedAt,
18
+ summarizeLayoutPanes,
19
+ type GalleryEntry,
20
+ } from "./model";
21
+
22
+ const PREVIEW = { width: 640, height: 320 };
23
+ const ELLIPSIS = { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } as const;
24
+
25
+ function SidebarSection({ title, count }: { title: string; count: number }) {
26
+ const colors = useThemeColors();
27
+ return (
28
+ <Box height={1} flexDirection="row" alignItems="center" paddingX={1} flexShrink={0}>
29
+ <Text fg={colors.textMuted} attributes={TextAttributes.BOLD}>
30
+ {`${t(title).toUpperCase()} ${count}`}
31
+ </Text>
32
+ </Box>
33
+ );
34
+ }
35
+
36
+ function SidebarNote({ children }: { children: ReactNode }) {
37
+ const colors = useThemeColors();
38
+ return (
39
+ <Box flexDirection="row" paddingX={1} paddingY={1} flexShrink={0}>
40
+ <Text fg={colors.textDim} wrapText>{children}</Text>
41
+ </Box>
42
+ );
43
+ }
44
+
45
+ function SidebarActionRow({ label, onPress }: { label: string; onPress: () => void }) {
46
+ return (
47
+ <PaneSidebarRow active={false} ariaLabel={label} onSelect={onPress}>
48
+ {({ foregroundColor, listWidth, onMouseDown }) => (
49
+ <Box
50
+ width={listWidth}
51
+ height={1}
52
+ flexDirection="row"
53
+ role="button"
54
+ tabIndex={0}
55
+ aria-label={label}
56
+ data-gloom-role="layout-gallery-row"
57
+ data-gloom-interactive="true"
58
+ onMouseDown={onMouseDown}
59
+ onKeyDown={(event: { key?: string; preventDefault?: () => void; stopPropagation?: () => void }) => {
60
+ if (event.key !== "Enter" && event.key !== " ") return;
61
+ event.preventDefault?.();
62
+ event.stopPropagation?.();
63
+ onPress();
64
+ }}
65
+ style={{ cursor: "pointer" }}
66
+ >
67
+ <Text fg={foregroundColor}>{` ${label}`}</Text>
68
+ </Box>
69
+ )}
70
+ </PaneSidebarRow>
71
+ );
72
+ }
73
+
74
+ function EntryRow({
75
+ entry,
76
+ controller,
77
+ selected,
78
+ }: {
79
+ entry: GalleryEntry;
80
+ controller: LayoutGalleryController;
81
+ selected: boolean;
82
+ }) {
83
+ const colors = useThemeColors();
84
+ const panes = summarizeLayoutPanes(entry.layout, controller.panes);
85
+ const missing = panes.filter((pane) => pane.missing).length;
86
+ const select = () => controller.select(entry.id);
87
+ const activate = () => (entry.kind === "community" ? controller.install(entry) : controller.activate(entry));
88
+
89
+ return (
90
+ <PaneSidebarRow
91
+ active={selected}
92
+ ariaLabel={tf("{name}, {panes} panes", { name: entry.name, panes: String(panes.length) })}
93
+ onSelect={select}
94
+ >
95
+ {({ foregroundColor, listWidth, onMouseDown }) => (
96
+ <Box
97
+ width={listWidth}
98
+ height={1}
99
+ minWidth={0}
100
+ flexDirection="row"
101
+ alignItems="center"
102
+ role="button"
103
+ tabIndex={0}
104
+ aria-label={tf("{name}, {panes} panes", { name: entry.name, panes: String(panes.length) })}
105
+ aria-current={selected ? "true" : undefined}
106
+ data-gloom-role="layout-gallery-row"
107
+ data-gloom-interactive="true"
108
+ onMouseOver={select}
109
+ onFocus={select}
110
+ onMouseDown={onMouseDown}
111
+ onKeyDown={(event: { key?: string; preventDefault?: () => void; stopPropagation?: () => void }) => {
112
+ if (event.key !== "Enter" && event.key !== " ") return;
113
+ event.preventDefault?.();
114
+ event.stopPropagation?.();
115
+ activate();
116
+ }}
117
+ style={{ cursor: "pointer" }}
118
+ >
119
+ <Text fg={entry.active ? colors.borderFocused : foregroundColor} selectable={false}>
120
+ {entry.active ? " ● " : " "}
121
+ </Text>
122
+ <Text
123
+ fg={foregroundColor}
124
+ attributes={entry.active ? TextAttributes.BOLD : 0}
125
+ selectable={false}
126
+ style={{ ...ELLIPSIS, minWidth: 0, flexShrink: 1 }}
127
+ >
128
+ {entry.name}
129
+ </Text>
130
+ <Box flexGrow={1} minWidth={0} />
131
+ {missing > 0 && <Text fg={colors.warning}>!</Text>}
132
+ <Text> </Text>
133
+ </Box>
134
+ )}
135
+ </PaneSidebarRow>
136
+ );
137
+ }
138
+
139
+ function DiscoverStatus({ controller }: { controller: LayoutGalleryController }) {
140
+ const colors = useThemeColors();
141
+ const { discover } = controller;
142
+
143
+ if (!controller.signedIn) {
144
+ return (
145
+ <>
146
+ <SidebarNote>{t("A Gloom account is required to browse community layouts.")}</SidebarNote>
147
+ <SidebarActionRow label={t("Log in")} onPress={controller.requestSignIn} />
148
+ </>
149
+ );
150
+ }
151
+ if (discover.state.status === "loading" || discover.state.status === "idle") {
152
+ return (
153
+ <Box flexDirection="row" alignItems="center" paddingX={1} flexShrink={0}>
154
+ <Spinner />
155
+ <Text fg={colors.textDim}>{` ${t("Loading…")}`}</Text>
156
+ </Box>
157
+ );
158
+ }
159
+ if (discover.state.status === "error") {
160
+ return (
161
+ <>
162
+ <SidebarNote><Text fg={colors.negative} wrapText>{discover.state.error}</Text></SidebarNote>
163
+ <SidebarActionRow label={t("Retry")} onPress={discover.refresh} />
164
+ </>
165
+ );
166
+ }
167
+ if (controller.community.length === 0) {
168
+ return (
169
+ <SidebarNote>
170
+ {controller.query.trim()
171
+ ? t("No community layouts match this search.")
172
+ : t("No community layouts published yet.")}
173
+ </SidebarNote>
174
+ );
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function PreviewEmpty({ controller }: { controller: LayoutGalleryController }) {
180
+ const colors = useThemeColors();
181
+ const searching = controller.query.trim().length > 0;
182
+ return (
183
+ <Box
184
+ flexGrow={1}
185
+ alignItems="center"
186
+ justifyContent="center"
187
+ data-gloom-role="layout-gallery-preview-empty"
188
+ padding={2}
189
+ >
190
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
191
+ {searching ? t("No layouts match this search.") : t("No layout selected.")}
192
+ </Text>
193
+ </Box>
194
+ );
195
+ }
196
+
197
+ function PreviewPane({ controller, entry }: { controller: LayoutGalleryController; entry: GalleryEntry }) {
198
+ const colors = useThemeColors();
199
+ const community = entry.kind === "community";
200
+ const metadata = [
201
+ entry.author,
202
+ entry.publishedAt ? formatPublishedAt(entry.publishedAt) : null,
203
+ describeArrangement(entry.layout),
204
+ ].filter(Boolean).join(" · ");
205
+
206
+ return (
207
+ <Box flexDirection="column" flexGrow={1} minWidth={0} minHeight={0} data-gloom-role="layout-gallery-preview">
208
+ <Box
209
+ height={3}
210
+ flexDirection="column"
211
+ justifyContent="center"
212
+ paddingX={1}
213
+ flexShrink={0}
214
+ style={{ borderBottom: `1px solid ${colors.border}` }}
215
+ >
216
+ <Box height={1} flexDirection="row" alignItems="center" minWidth={0}>
217
+ <Text
218
+ fg={colors.textBright}
219
+ attributes={TextAttributes.BOLD}
220
+ style={{ ...ELLIPSIS, minWidth: 0, flexShrink: 1 }}
221
+ >
222
+ {entry.name}
223
+ </Text>
224
+ {entry.active && <Text fg={colors.borderFocused}>{` ${t("ACTIVE")}`}</Text>}
225
+ <Box flexGrow={1} minWidth={0} />
226
+ <Button label="New Layout" variant="secondary" onPress={controller.newLayout} />
227
+ <Box width={1} />
228
+ <Button
229
+ label={controller.publishing ? "Publishing…" : "Publish Current"}
230
+ variant="secondary"
231
+ disabled={controller.publishing}
232
+ onPress={controller.publishCurrent}
233
+ />
234
+ </Box>
235
+ <Text fg={colors.textMuted} style={ELLIPSIS}>{metadata}</Text>
236
+ </Box>
237
+
238
+ <Box flexGrow={2} minWidth={0} minHeight={8} overflow="hidden" padding={1}>
239
+ <MiniWorkspace
240
+ layout={entry.layout}
241
+ panes={controller.panes}
242
+ width={PREVIEW.width}
243
+ height={PREVIEW.height}
244
+ detail
245
+ />
246
+ </Box>
247
+
248
+ <Box
249
+ height={2}
250
+ flexDirection="row"
251
+ alignItems="center"
252
+ paddingX={1}
253
+ flexShrink={0}
254
+ style={{ borderTop: `1px solid ${colors.border}` }}
255
+ >
256
+ <Button
257
+ label={community ? "Add Layout" : "Use Layout"}
258
+ variant="primary"
259
+ onPress={() => (community ? controller.install(entry) : controller.activate(entry))}
260
+ />
261
+ {community && (
262
+ <>
263
+ <Box width={1} />
264
+ <Button label="Copy Link" variant="secondary" onPress={() => controller.copyLink(entry)} />
265
+ </>
266
+ )}
267
+ {entry.kind === "owned" && (
268
+ <>
269
+ <Box width={1} />
270
+ <Button label="Rename" variant="secondary" onPress={() => controller.renameLayout(entry)} />
271
+ <Box width={1} />
272
+ <Button label="Duplicate" variant="secondary" onPress={() => controller.duplicateLayout(entry)} />
273
+ <Box width={1} />
274
+ <Button
275
+ label="Delete"
276
+ variant="secondary"
277
+ disabled={!controller.canDelete}
278
+ onPress={() => controller.deleteLayout(entry)}
279
+ />
280
+ </>
281
+ )}
282
+ <Box flexGrow={1} minWidth={0} />
283
+ {community && (
284
+ <Text fg={colors.textMuted} style={{ ...ELLIPSIS, minWidth: 0 }}>
285
+ {t("Adds an editable copy")}
286
+ </Text>
287
+ )}
288
+ </Box>
289
+ </Box>
290
+ );
291
+ }
292
+
293
+ export function LayoutGalleryDesktop({
294
+ controller,
295
+ focused = true,
296
+ width = 118,
297
+ height = 34,
298
+ }: {
299
+ controller: LayoutGalleryController;
300
+ focused?: boolean;
301
+ width?: number;
302
+ height?: number;
303
+ }) {
304
+ const { nativePaneChrome } = useUiCapabilities();
305
+ const sidebarWidth = getPaneSidebarWidth(width, !!nativePaneChrome);
306
+ const selected = controller.entries.find((entry) => entry.id === controller.selectedId)
307
+ ?? controller.owned.find((entry) => entry.active)
308
+ ?? controller.entries[0]
309
+ ?? null;
310
+
311
+ return (
312
+ <Box
313
+ width={width}
314
+ height={height}
315
+ flexDirection="row"
316
+ overflow="hidden"
317
+ data-gloom-role="layout-gallery"
318
+ >
319
+ <PaneSidebar width={sidebarWidth} height={height} focused={focused}>
320
+ {({ listWidth }) => (
321
+ <>
322
+ <Box height={2} paddingX={1} justifyContent="center" flexShrink={0}>
323
+ <TextField
324
+ value={controller.query}
325
+ placeholder={t("Search layouts")}
326
+ onChange={controller.setQuery}
327
+ width={Math.max(1, listWidth - 2)}
328
+ />
329
+ </Box>
330
+ <ScrollBox
331
+ scrollY
332
+ flexGrow={1}
333
+ minHeight={0}
334
+ focusable={false}
335
+ data-gloom-role="layout-gallery-sidebar"
336
+ >
337
+ <SidebarSection title="Your Layouts" count={controller.owned.length} />
338
+ {controller.owned.length === 0 ? (
339
+ <SidebarNote>
340
+ {controller.query.trim()
341
+ ? t("No saved layouts match this search.")
342
+ : t("No saved layouts yet.")}
343
+ </SidebarNote>
344
+ ) : controller.owned.map((entry) => (
345
+ <EntryRow
346
+ key={entry.id}
347
+ entry={entry}
348
+ controller={controller}
349
+ selected={entry.id === selected?.id}
350
+ />
351
+ ))}
352
+
353
+ <SidebarSection title="Discover" count={controller.community.length} />
354
+ <DiscoverStatus controller={controller} />
355
+ {controller.community.map((entry) => (
356
+ <EntryRow
357
+ key={entry.id}
358
+ entry={entry}
359
+ controller={controller}
360
+ selected={entry.id === selected?.id}
361
+ />
362
+ ))}
363
+ </ScrollBox>
364
+ </>
365
+ )}
366
+ </PaneSidebar>
367
+
368
+ <Box flexDirection="column" flexGrow={1} minWidth={0} minHeight={0}>
369
+ {selected
370
+ ? <PreviewPane controller={controller} entry={selected} />
371
+ : <PreviewEmpty controller={controller} />}
372
+ </Box>
373
+ </Box>
374
+ );
375
+ }