gloomberb 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +49 -15
- package/src/components/chart/chart-types.ts +2 -2
- package/src/components/chart/chart.test.ts +27 -8
- package/src/components/chart/comparison-stock-chart.tsx +84 -121
- package/src/components/chart/native/chart-rasterizer.ts +43 -17
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +1 -1
- package/src/components/chart/stock-chart.tsx +59 -41
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +528 -383
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +1 -0
- package/src/components/command-bar/view-model.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +47 -7
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +11 -51
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator.ts +8 -1
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +258 -44
- package/src/plugins/builtin/chat.test.tsx +257 -6
- package/src/plugins/builtin/chat.tsx +225 -60
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/news-wire/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +11 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +117 -18
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +282 -2
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +252 -41
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +0 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +442 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +67 -11
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/state/app-bootstrap.ts +3 -3
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/ui/host.tsx +8 -2
- package/src/ui/index.tsx +1 -0
- package/src/utils/api-client.test.ts +128 -0
- package/src/utils/api-client.ts +112 -7
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
|
@@ -3,7 +3,7 @@ import { memo, useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
|
3
3
|
import { useShortcut } from "../../react/input";
|
|
4
4
|
import { TextAttributes, type ScrollBoxRenderable, type TextareaRenderable } from "../../ui";
|
|
5
5
|
import type { GloomPlugin, PaneProps } from "../../types/plugin";
|
|
6
|
-
import { useAppDispatch, useAppSelector, useAppStateRef, usePaneInstance, usePaneInstanceId } from "../../state/app-context";
|
|
6
|
+
import { syncConfigActiveLayoutState, useAppDispatch, useAppSelector, useAppStateRef, usePaneInstance, usePaneInstanceId } from "../../state/app-context";
|
|
7
7
|
import { useInlineTickers, type InlineTickerCatalogEntry } from "../../state/use-inline-tickers";
|
|
8
8
|
import { ExternalLinkText, getMessageComposerBlockHeight, MessageComposer } from "../../components/ui";
|
|
9
9
|
import { TickerBadgeText } from "../../components/ticker-badge-text";
|
|
@@ -12,6 +12,7 @@ import { blendHex, colors, hoverBg } from "../../theme/colors";
|
|
|
12
12
|
import { apiClient, type ChatChannel, type ChatMessage } from "../../utils/api-client";
|
|
13
13
|
import { formatTimeAgo } from "../../utils/format";
|
|
14
14
|
import { tokenizeInlineContent } from "../../utils/inline-content-tokenizer";
|
|
15
|
+
import { isPlainKey } from "../../utils/keyboard";
|
|
15
16
|
import { getSharedRegistry } from "../../plugins/registry";
|
|
16
17
|
import { usePluginAppActions } from "../../plugins/plugin-runtime";
|
|
17
18
|
import { setPaneSetting } from "../../pane-settings";
|
|
@@ -34,6 +35,8 @@ interface ChatContentProps {
|
|
|
34
35
|
| "attachChannelView"
|
|
35
36
|
| "getSnapshot"
|
|
36
37
|
| "refreshChannels"
|
|
38
|
+
| "refreshChatState"
|
|
39
|
+
| "refreshPresence"
|
|
37
40
|
| "loadOlderMessages"
|
|
38
41
|
| "loadOlderChannelMessages"
|
|
39
42
|
| "refreshMessages"
|
|
@@ -43,6 +46,7 @@ interface ChatContentProps {
|
|
|
43
46
|
| "sendToChannel"
|
|
44
47
|
| "setDraft"
|
|
45
48
|
| "setChannelDraft"
|
|
49
|
+
| "setChannelNotificationsEnabled"
|
|
46
50
|
| "setReplyToId"
|
|
47
51
|
| "setChannelReplyToId"
|
|
48
52
|
| "subscribe"
|
|
@@ -61,10 +65,17 @@ const MESSAGE_SELECTION_BOTTOM_INSET = 1;
|
|
|
61
65
|
const DESKTOP_MESSAGE_RIGHT_PADDING = 2;
|
|
62
66
|
const CHANNEL_SIDEBAR_MIN_WIDTH = 18;
|
|
63
67
|
const CHANNEL_SIDEBAR_MAX_WIDTH = 24;
|
|
68
|
+
const DESKTOP_CHANNEL_SIDEBAR_MIN_WIDTH = 14;
|
|
69
|
+
const DESKTOP_CHANNEL_SIDEBAR_MAX_WIDTH = 19;
|
|
70
|
+
const DESKTOP_CHANNEL_SIDEBAR_WIDTH_RATIO = 0.192;
|
|
64
71
|
const CHANNEL_SIDEBAR_BREAKPOINT = 72;
|
|
72
|
+
const DESKTOP_NOTIFICATION_ICON_WIDTH = 3;
|
|
73
|
+
const DESKTOP_ONLINE_COUNT_PADDING_X = 1;
|
|
74
|
+
const DESKTOP_CHAT_INPUT_TOP_MARGIN_PX = 6;
|
|
65
75
|
const DEFAULT_CHAT_CHANNEL_ID = "everyone";
|
|
66
76
|
const LAST_VISITED_CHAT_CHANNEL_KEY = "lastChatChannelId";
|
|
67
77
|
const CHAT_CHANNEL_MOUSE_HANDLED = "__gloomberbChatChannelHandled";
|
|
78
|
+
const SCROLL_BOTTOM_THRESHOLD_PX = 2;
|
|
68
79
|
|
|
69
80
|
function isGroupedWithPrevious(messages: ChatMessage[], index: number) {
|
|
70
81
|
if (index === 0) return false;
|
|
@@ -170,6 +181,15 @@ function scrollToBottom(scrollBox: ScrollBoxRenderable | null, preferPixels = fa
|
|
|
170
181
|
);
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
function isScrolledToBottom(scrollBox: ScrollBoxRenderable | null, preferPixels = false) {
|
|
185
|
+
if (!scrollBox) return true;
|
|
186
|
+
const exactPixels = preferPixels && hasPixelScrollMetrics(scrollBox);
|
|
187
|
+
const scrollTop = getScrollTop(scrollBox, exactPixels);
|
|
188
|
+
const maxScrollTop = Math.max(0, getScrollHeight(scrollBox, exactPixels) - getViewportHeight(scrollBox, exactPixels));
|
|
189
|
+
const threshold = exactPixels ? SCROLL_BOTTOM_THRESHOLD_PX : 0;
|
|
190
|
+
return maxScrollTop - scrollTop <= threshold;
|
|
191
|
+
}
|
|
192
|
+
|
|
173
193
|
function runAfterLayout(callback: () => void) {
|
|
174
194
|
if (typeof requestAnimationFrame === "function") {
|
|
175
195
|
requestAnimationFrame(callback);
|
|
@@ -311,6 +331,73 @@ function CloudStatusIcon() {
|
|
|
311
331
|
);
|
|
312
332
|
}
|
|
313
333
|
|
|
334
|
+
function ChannelNotificationIcon({
|
|
335
|
+
enabled,
|
|
336
|
+
onMouseDown,
|
|
337
|
+
}: {
|
|
338
|
+
enabled: boolean;
|
|
339
|
+
onMouseDown?: (event: any) => void;
|
|
340
|
+
}) {
|
|
341
|
+
const { nativePaneChrome } = useUiCapabilities();
|
|
342
|
+
const iconColor = enabled ? colors.positive : colors.textMuted;
|
|
343
|
+
|
|
344
|
+
if (!nativePaneChrome) {
|
|
345
|
+
return (
|
|
346
|
+
<Text fg={iconColor} selectable={false} onMouseDown={onMouseDown}>
|
|
347
|
+
{enabled ? "◖)" : "◖·"}
|
|
348
|
+
</Text>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<Span
|
|
354
|
+
fg={iconColor}
|
|
355
|
+
onMouseDown={onMouseDown}
|
|
356
|
+
style={{
|
|
357
|
+
color: iconColor,
|
|
358
|
+
display: "inline-flex",
|
|
359
|
+
alignItems: "center",
|
|
360
|
+
justifyContent: "center",
|
|
361
|
+
width: 16,
|
|
362
|
+
height: 16,
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" aria-hidden="true">
|
|
366
|
+
<path
|
|
367
|
+
d="M4.5 9.5v5h3.2l4.8 4v-13l-4.8 4H4.5Z"
|
|
368
|
+
stroke="currentColor"
|
|
369
|
+
strokeWidth="1.8"
|
|
370
|
+
strokeLinecap="round"
|
|
371
|
+
strokeLinejoin="round"
|
|
372
|
+
/>
|
|
373
|
+
{enabled ? (
|
|
374
|
+
<>
|
|
375
|
+
<path
|
|
376
|
+
d="M16 8.5a5 5 0 0 1 0 7"
|
|
377
|
+
stroke="currentColor"
|
|
378
|
+
strokeWidth="1.8"
|
|
379
|
+
strokeLinecap="round"
|
|
380
|
+
/>
|
|
381
|
+
<path
|
|
382
|
+
d="M18.8 5.8a9 9 0 0 1 0 12.4"
|
|
383
|
+
stroke="currentColor"
|
|
384
|
+
strokeWidth="1.8"
|
|
385
|
+
strokeLinecap="round"
|
|
386
|
+
/>
|
|
387
|
+
</>
|
|
388
|
+
) : (
|
|
389
|
+
<path
|
|
390
|
+
d="M19 5 5 19"
|
|
391
|
+
stroke="currentColor"
|
|
392
|
+
strokeWidth="1.8"
|
|
393
|
+
strokeLinecap="round"
|
|
394
|
+
/>
|
|
395
|
+
)}
|
|
396
|
+
</svg>
|
|
397
|
+
</Span>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
314
401
|
function ChatActionChip({
|
|
315
402
|
label,
|
|
316
403
|
width,
|
|
@@ -752,35 +839,46 @@ const DesktopChatMessage = memo(function DesktopChatMessage({
|
|
|
752
839
|
|
|
753
840
|
function ChannelSidebar({
|
|
754
841
|
channels,
|
|
842
|
+
channelStates,
|
|
755
843
|
activeChannelId,
|
|
844
|
+
onlineCount,
|
|
756
845
|
width,
|
|
757
846
|
height,
|
|
758
847
|
focused,
|
|
759
848
|
keyboardFocused,
|
|
760
849
|
loading,
|
|
850
|
+
canManageNotifications,
|
|
761
851
|
onSelect,
|
|
762
852
|
onFocusRequest,
|
|
853
|
+
onToggleNotifications,
|
|
763
854
|
}: {
|
|
764
855
|
channels: ChatChannel[];
|
|
856
|
+
channelStates: ReturnType<ChatController["getSnapshot"]>["channelStates"];
|
|
765
857
|
activeChannelId: string;
|
|
858
|
+
onlineCount: number;
|
|
766
859
|
width: number;
|
|
767
860
|
height: number;
|
|
768
861
|
focused: boolean;
|
|
769
862
|
keyboardFocused: boolean;
|
|
770
863
|
loading: boolean;
|
|
864
|
+
canManageNotifications: boolean;
|
|
771
865
|
onSelect?: (channelId: string) => void;
|
|
772
866
|
onFocusRequest?: () => void;
|
|
867
|
+
onToggleNotifications?: (channelId: string, enabled: boolean) => void;
|
|
773
868
|
}) {
|
|
774
869
|
const { nativePaneChrome } = useUiCapabilities();
|
|
775
870
|
const borderWidth = nativePaneChrome ? 0 : width > 1 ? 1 : 0;
|
|
776
871
|
const listWidth = Math.max(width - borderWidth, 1);
|
|
777
|
-
const
|
|
872
|
+
const notificationWidth = canManageNotifications ? (nativePaneChrome ? DESKTOP_NOTIFICATION_ICON_WIDTH : 2) : 0;
|
|
873
|
+
const onlineCountPaddingX = nativePaneChrome ? DESKTOP_ONLINE_COUNT_PADDING_X : 0;
|
|
874
|
+
const labelWidth = Math.max(listWidth - 3 - notificationWidth, 1);
|
|
778
875
|
const dividerColor = focused ? colors.borderFocused : colors.border;
|
|
779
876
|
const sidebarBg = keyboardFocused ? blendHex(colors.panel, colors.borderFocused, 0.18) : colors.panel;
|
|
780
877
|
const activeBg = keyboardFocused
|
|
781
878
|
? blendHex(colors.selected, colors.borderFocused, 0.32)
|
|
782
879
|
: blendHex(colors.panel, colors.selected, 0.35);
|
|
783
880
|
const [hoveredChannelId, setHoveredChannelId] = useState<string | null>(null);
|
|
881
|
+
const channelStateById = useMemo(() => new Map(channelStates.map((state) => [state.channelId, state])), [channelStates]);
|
|
784
882
|
const sidebarLayoutHeight = nativePaneChrome ? "100%" : height;
|
|
785
883
|
const nativeFillStyle = nativePaneChrome ? { minHeight: 0 } : undefined;
|
|
786
884
|
const sidebarBorder = borderWidth > 0
|
|
@@ -810,6 +908,9 @@ function ChannelSidebar({
|
|
|
810
908
|
>
|
|
811
909
|
{channels.map((channel) => {
|
|
812
910
|
const active = channel.id === activeChannelId;
|
|
911
|
+
const channelState = channelStateById.get(channel.id);
|
|
912
|
+
const notificationsEnabled = channelState?.notificationsEnabled === true;
|
|
913
|
+
const unread = (channelState?.unreadCount ?? 0) > 0;
|
|
813
914
|
const hovered = hoveredChannelId === channel.id && !active;
|
|
814
915
|
const fg = active ? colors.selectedText : keyboardFocused ? colors.text : colors.textDim;
|
|
815
916
|
const bg = active ? activeBg : hovered ? hoverBg() : sidebarBg;
|
|
@@ -824,6 +925,14 @@ function ChannelSidebar({
|
|
|
824
925
|
onFocusRequest?.();
|
|
825
926
|
onSelect?.(channel.id);
|
|
826
927
|
};
|
|
928
|
+
const toggleNotifications = (event: any) => {
|
|
929
|
+
event?.preventDefault?.();
|
|
930
|
+
event?.stopPropagation?.();
|
|
931
|
+
if (event) {
|
|
932
|
+
event[CHAT_CHANNEL_MOUSE_HANDLED] = true;
|
|
933
|
+
}
|
|
934
|
+
onToggleNotifications?.(channel.id, !notificationsEnabled);
|
|
935
|
+
};
|
|
827
936
|
return (
|
|
828
937
|
<Box
|
|
829
938
|
key={channel.id}
|
|
@@ -837,9 +946,21 @@ function ChannelSidebar({
|
|
|
837
946
|
style={{ cursor: "pointer" }}
|
|
838
947
|
>
|
|
839
948
|
<Text fg={fg} selectable={false} onMouseDown={selectChannel}> </Text>
|
|
840
|
-
<Text fg={fg} selectable={false} onMouseDown={selectChannel}>{active ? "#" : " "}</Text>
|
|
841
|
-
<Text fg={fg} selectable={false} onMouseDown={selectChannel}>{truncateChannelLabel(label, labelWidth)}</Text>
|
|
949
|
+
<Text fg={fg} attributes={unread ? TextAttributes.BOLD : 0} selectable={false} onMouseDown={selectChannel}>{active ? "#" : " "}</Text>
|
|
950
|
+
<Text fg={fg} attributes={unread ? TextAttributes.BOLD : 0} selectable={false} onMouseDown={selectChannel}>{truncateChannelLabel(label, labelWidth)}</Text>
|
|
842
951
|
<Box flexGrow={1} onMouseDown={selectChannel} />
|
|
952
|
+
{canManageNotifications && (
|
|
953
|
+
<Box
|
|
954
|
+
width={notificationWidth}
|
|
955
|
+
height={1}
|
|
956
|
+
alignItems="center"
|
|
957
|
+
justifyContent="center"
|
|
958
|
+
onMouseDown={toggleNotifications}
|
|
959
|
+
style={{ cursor: "pointer" }}
|
|
960
|
+
>
|
|
961
|
+
<ChannelNotificationIcon enabled={notificationsEnabled} onMouseDown={toggleNotifications} />
|
|
962
|
+
</Box>
|
|
963
|
+
)}
|
|
843
964
|
</Box>
|
|
844
965
|
);
|
|
845
966
|
})}
|
|
@@ -849,6 +970,12 @@ function ChannelSidebar({
|
|
|
849
970
|
<Text fg={colors.textDim}> syncing</Text>
|
|
850
971
|
</Box>
|
|
851
972
|
)}
|
|
973
|
+
<Box height={1} width={listWidth} flexDirection="row" paddingX={onlineCountPaddingX}>
|
|
974
|
+
<Text fg={colors.positive}>●</Text>
|
|
975
|
+
<Text fg={colors.textDim}>
|
|
976
|
+
{` ${truncateChannelLabel(`${onlineCount} online`, Math.max(listWidth - 2 - onlineCountPaddingX * 2, 1))}`}
|
|
977
|
+
</Text>
|
|
978
|
+
</Box>
|
|
852
979
|
</Box>
|
|
853
980
|
{sidebarBorder}
|
|
854
981
|
{nativePaneChrome && (
|
|
@@ -888,16 +1015,20 @@ export function ChatContent({
|
|
|
888
1015
|
const [channels, setChannels] = useState<ChatChannel[]>(initialSnapshot.channels);
|
|
889
1016
|
const [channelsLoading, setChannelsLoading] = useState(initialSnapshot.channelsLoading);
|
|
890
1017
|
const showChannelSidebar = channels.length > 1 && width >= CHANNEL_SIDEBAR_BREAKPOINT && height >= 8;
|
|
1018
|
+
const sidebarMinWidth = nativePaneChrome ? DESKTOP_CHANNEL_SIDEBAR_MIN_WIDTH : CHANNEL_SIDEBAR_MIN_WIDTH;
|
|
1019
|
+
const sidebarMaxWidth = nativePaneChrome ? DESKTOP_CHANNEL_SIDEBAR_MAX_WIDTH : CHANNEL_SIDEBAR_MAX_WIDTH;
|
|
1020
|
+
const sidebarWidthRatio = nativePaneChrome ? DESKTOP_CHANNEL_SIDEBAR_WIDTH_RATIO : 0.24;
|
|
891
1021
|
const channelSidebarWidth = showChannelSidebar
|
|
892
|
-
? Math.min(
|
|
1022
|
+
? Math.min(sidebarMaxWidth, Math.max(sidebarMinWidth, Math.floor(width * sidebarWidthRatio)))
|
|
893
1023
|
: 0;
|
|
894
|
-
const compactChannelHeaderHeight = nativePaneChrome && !showChannelSidebar && channels.length > 1 ? 1 : 0;
|
|
895
1024
|
const chatWidth = Math.max(width - channelSidebarWidth, 1);
|
|
896
1025
|
const contentWidth = Math.max(chatWidth - 2, 1);
|
|
897
1026
|
const composerPrefixWidth = nativePaneChrome ? 0 : 3;
|
|
898
1027
|
const composerTextWidth = Math.max(contentWidth - composerPrefixWidth, 1);
|
|
899
1028
|
const inputValueRef = useRef(initialSnapshot.draft);
|
|
900
1029
|
const [messages, setMessages] = useState<ChatMessage[]>(initialSnapshot.messages);
|
|
1030
|
+
const [channelStates, setChannelStates] = useState(initialSnapshot.channelStates);
|
|
1031
|
+
const [onlineCount, setOnlineCount] = useState(initialSnapshot.onlineCount);
|
|
901
1032
|
const [hasSavedSession, setHasSavedSession] = useState(initialSnapshot.hasSavedSession);
|
|
902
1033
|
const [user, setUser] = useState<{ id: string; username: string; emailVerified: boolean } | null>(initialSnapshot.user);
|
|
903
1034
|
const [loading, setLoading] = useState(initialSnapshot.loading);
|
|
@@ -937,6 +1068,7 @@ export function ChatContent({
|
|
|
937
1068
|
: 0;
|
|
938
1069
|
const selectionActive = selectedIdx >= 0 && selectedIdx < messages.length;
|
|
939
1070
|
const stickyTranscript = followMessages && !selectionActive;
|
|
1071
|
+
const latestMessageId = messages[messages.length - 1]?.id ?? null;
|
|
940
1072
|
const updateComposerRows = useCallback((draft: string) => {
|
|
941
1073
|
const nextRows = estimateComposerHeight(draft, composerTextWidth);
|
|
942
1074
|
setComposerRows((current) => (current === nextRows ? current : nextRows));
|
|
@@ -952,6 +1084,7 @@ export function ChatContent({
|
|
|
952
1084
|
|
|
953
1085
|
useEffect(() => {
|
|
954
1086
|
void controller.refreshChannels().catch(() => {});
|
|
1087
|
+
void controller.refreshPresence().catch(() => {});
|
|
955
1088
|
void controller.refreshSession().catch(() => {});
|
|
956
1089
|
}, [controller]);
|
|
957
1090
|
|
|
@@ -962,6 +1095,8 @@ export function ChatContent({
|
|
|
962
1095
|
const snapshot = controller.getSnapshot(channelId);
|
|
963
1096
|
setMessages(snapshot.messages);
|
|
964
1097
|
setChannels(snapshot.channels);
|
|
1098
|
+
setChannelStates(snapshot.channelStates);
|
|
1099
|
+
setOnlineCount(snapshot.onlineCount);
|
|
965
1100
|
setChannelsLoading(snapshot.channelsLoading);
|
|
966
1101
|
setHasSavedSession(snapshot.hasSavedSession);
|
|
967
1102
|
setUser(snapshot.user);
|
|
@@ -987,6 +1122,8 @@ export function ChatContent({
|
|
|
987
1122
|
function handleSnapshot(snapshot: ReturnType<ChatController["getSnapshot"]>) {
|
|
988
1123
|
setMessages(snapshot.messages);
|
|
989
1124
|
setChannels(snapshot.channels);
|
|
1125
|
+
setChannelStates(snapshot.channelStates);
|
|
1126
|
+
setOnlineCount(snapshot.onlineCount);
|
|
990
1127
|
setChannelsLoading(snapshot.channelsLoading);
|
|
991
1128
|
setHasSavedSession(snapshot.hasSavedSession);
|
|
992
1129
|
setUser(snapshot.user);
|
|
@@ -1132,7 +1269,7 @@ export function ChatContent({
|
|
|
1132
1269
|
oldestMessageId: messages[0]?.id ?? null,
|
|
1133
1270
|
scrollHeight: getScrollHeight(scrollBox, exactPixels),
|
|
1134
1271
|
scrollTop: getScrollTop(scrollBox, exactPixels),
|
|
1135
|
-
selectedMessageId: selectedIdx >= 0 ? messages[selectedIdx]?.id ?? null : null,
|
|
1272
|
+
selectedMessageId: selectedIdx >= 0 ? messages[selectedIdx]?.id ?? null : messages[0]?.id ?? null,
|
|
1136
1273
|
};
|
|
1137
1274
|
setFollowMessages(false);
|
|
1138
1275
|
const request = useDefaultControllerChannel
|
|
@@ -1149,6 +1286,23 @@ export function ChatContent({
|
|
|
1149
1286
|
requestOlderMessages();
|
|
1150
1287
|
}, [requestOlderMessages]);
|
|
1151
1288
|
|
|
1289
|
+
const handleTranscriptScrollActivity = useCallback((event?: { scroll?: { direction?: "up" | "down" | "left" | "right" } }) => {
|
|
1290
|
+
const direction = event?.scroll?.direction;
|
|
1291
|
+
runAfterLayout(() => {
|
|
1292
|
+
requestOlderMessagesIfNeeded();
|
|
1293
|
+
|
|
1294
|
+
const scrollBox = scrollRef.current;
|
|
1295
|
+
if (!scrollBox) return;
|
|
1296
|
+
const atBottom = isScrolledToBottom(scrollBox, nativePaneChrome);
|
|
1297
|
+
if (direction === "up" && !atBottom) {
|
|
1298
|
+
setFollowMessages(false);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
setFollowMessages(atBottom);
|
|
1303
|
+
});
|
|
1304
|
+
}, [nativePaneChrome, requestOlderMessagesIfNeeded]);
|
|
1305
|
+
|
|
1152
1306
|
const scrollToLoadedMessage = useCallback((messageId: string) => {
|
|
1153
1307
|
const targetIndex = messages.findIndex((message) => message.id === messageId);
|
|
1154
1308
|
if (targetIndex < 0) return false;
|
|
@@ -1307,20 +1461,20 @@ export function ChatContent({
|
|
|
1307
1461
|
const isEnterKey = event.name === "return" || event.name === "enter";
|
|
1308
1462
|
|
|
1309
1463
|
if (sidebarFocusedRef.current && showChannelSidebar) {
|
|
1310
|
-
if (event
|
|
1464
|
+
if (isPlainKey(event, "left")) {
|
|
1311
1465
|
event.preventDefault?.();
|
|
1312
1466
|
event.stopPropagation?.();
|
|
1313
1467
|
return;
|
|
1314
1468
|
}
|
|
1315
1469
|
|
|
1316
|
-
if (event
|
|
1470
|
+
if (isPlainKey(event, "right")) {
|
|
1317
1471
|
event.preventDefault?.();
|
|
1318
1472
|
event.stopPropagation?.();
|
|
1319
1473
|
focusChatContent();
|
|
1320
1474
|
return;
|
|
1321
1475
|
}
|
|
1322
1476
|
|
|
1323
|
-
if (event
|
|
1477
|
+
if (isPlainKey(event, "up", "down")) {
|
|
1324
1478
|
event.preventDefault?.();
|
|
1325
1479
|
event.stopPropagation?.();
|
|
1326
1480
|
moveSidebarChannelSelection(event.name);
|
|
@@ -1329,7 +1483,7 @@ export function ChatContent({
|
|
|
1329
1483
|
}
|
|
1330
1484
|
|
|
1331
1485
|
if (
|
|
1332
|
-
event
|
|
1486
|
+
isPlainKey(event, "left") &&
|
|
1333
1487
|
showChannelSidebar &&
|
|
1334
1488
|
(!inputFocused || inputValueRef.current.length === 0) &&
|
|
1335
1489
|
focusChannelSidebar()
|
|
@@ -1351,7 +1505,7 @@ export function ChatContent({
|
|
|
1351
1505
|
return;
|
|
1352
1506
|
}
|
|
1353
1507
|
|
|
1354
|
-
if ((event
|
|
1508
|
+
if (isPlainKey(event, "up", "down") && shouldLeaveComposerForSelection(event.name)) {
|
|
1355
1509
|
const moved = moveMessageSelection(event.name);
|
|
1356
1510
|
if (moved) {
|
|
1357
1511
|
event.preventDefault?.();
|
|
@@ -1364,12 +1518,12 @@ export function ChatContent({
|
|
|
1364
1518
|
return;
|
|
1365
1519
|
}
|
|
1366
1520
|
|
|
1367
|
-
if ((event
|
|
1521
|
+
if (isPlainKey(event, "]") && cycleChannel(1)) {
|
|
1368
1522
|
event.preventDefault?.();
|
|
1369
1523
|
event.stopPropagation?.();
|
|
1370
1524
|
return;
|
|
1371
1525
|
}
|
|
1372
|
-
if ((event
|
|
1526
|
+
if (isPlainKey(event, "[") && cycleChannel(-1)) {
|
|
1373
1527
|
event.preventDefault?.();
|
|
1374
1528
|
event.stopPropagation?.();
|
|
1375
1529
|
return;
|
|
@@ -1382,7 +1536,7 @@ export function ChatContent({
|
|
|
1382
1536
|
return;
|
|
1383
1537
|
}
|
|
1384
1538
|
|
|
1385
|
-
if ((isEnterKey || event
|
|
1539
|
+
if ((isEnterKey || isPlainKey(event, "i")) && canSend) {
|
|
1386
1540
|
event.preventDefault?.();
|
|
1387
1541
|
event.stopPropagation?.();
|
|
1388
1542
|
queueMicrotask(() => focusComposer());
|
|
@@ -1399,7 +1553,7 @@ export function ChatContent({
|
|
|
1399
1553
|
return;
|
|
1400
1554
|
}
|
|
1401
1555
|
|
|
1402
|
-
if (event
|
|
1556
|
+
if (isPlainKey(event, "j", "down")) {
|
|
1403
1557
|
event.preventDefault?.();
|
|
1404
1558
|
event.stopPropagation?.();
|
|
1405
1559
|
if (selectedIdx === messages.length - 1) {
|
|
@@ -1409,7 +1563,7 @@ export function ChatContent({
|
|
|
1409
1563
|
moveMessageSelection("down");
|
|
1410
1564
|
return;
|
|
1411
1565
|
}
|
|
1412
|
-
if (event
|
|
1566
|
+
if (isPlainKey(event, "k", "up")) {
|
|
1413
1567
|
event.preventDefault?.();
|
|
1414
1568
|
event.stopPropagation?.();
|
|
1415
1569
|
if (selectedIdx === 0 && hasOlderMessages && !loadingOlderMessages) {
|
|
@@ -1420,14 +1574,14 @@ export function ChatContent({
|
|
|
1420
1574
|
return;
|
|
1421
1575
|
}
|
|
1422
1576
|
|
|
1423
|
-
if (canSend && event
|
|
1577
|
+
if (canSend && isPlainKey(event, "r") && selectedIdx >= 0 && selectedIdx < messages.length) {
|
|
1424
1578
|
event.preventDefault?.();
|
|
1425
1579
|
event.stopPropagation?.();
|
|
1426
1580
|
beginReplyTo(selectedIdx, { deferFocus: true });
|
|
1427
1581
|
return;
|
|
1428
1582
|
}
|
|
1429
1583
|
|
|
1430
|
-
if (event
|
|
1584
|
+
if (isPlainKey(event, "g")) {
|
|
1431
1585
|
event.preventDefault?.();
|
|
1432
1586
|
event.stopPropagation?.();
|
|
1433
1587
|
setSelectedIdx(0);
|
|
@@ -1453,7 +1607,7 @@ export function ChatContent({
|
|
|
1453
1607
|
const inputAreaHeight = canSend ? composerBlockHeight + inputMetaHeight : 2;
|
|
1454
1608
|
const topSeparatorHeight = nativePaneChrome ? 0 : 1;
|
|
1455
1609
|
const footerSeparatorHeight = !nativePaneChrome && !canSend ? 1 : 0;
|
|
1456
|
-
const messageAreaHeight = Math.max(1, height -
|
|
1610
|
+
const messageAreaHeight = Math.max(1, height - topSeparatorHeight - footerSeparatorHeight - inputAreaHeight);
|
|
1457
1611
|
const composerWidth = nativePaneChrome ? chatWidth : contentWidth;
|
|
1458
1612
|
|
|
1459
1613
|
useEffect(() => {
|
|
@@ -1464,36 +1618,38 @@ export function ChatContent({
|
|
|
1464
1618
|
useEffect(() => {
|
|
1465
1619
|
if (!stickyTranscript) return;
|
|
1466
1620
|
queueMicrotask(() => scrollToBottom(scrollRef.current, nativePaneChrome));
|
|
1467
|
-
}, [contentWidth, height,
|
|
1621
|
+
}, [channelId, contentWidth, height, latestMessageId, messageAreaHeight, nativePaneChrome, stickyTranscript]);
|
|
1468
1622
|
|
|
1469
1623
|
useEffect(() => {
|
|
1470
1624
|
const anchor = prependAnchorRef.current;
|
|
1471
1625
|
if (!anchor || loadingOlderMessages) return;
|
|
1472
1626
|
|
|
1627
|
+
const currentAnchor = anchor;
|
|
1628
|
+
const scrollBox = scrollRef.current;
|
|
1629
|
+
if (!scrollBox) return;
|
|
1630
|
+
if (pendingJumpMessageIdRef.current) {
|
|
1631
|
+
prependAnchorRef.current = null;
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const previousOldestIndex = currentAnchor.oldestMessageId
|
|
1636
|
+
? messages.findIndex((message) => message.id === currentAnchor.oldestMessageId)
|
|
1637
|
+
: -1;
|
|
1638
|
+
const exactPixels = nativePaneChrome === true && hasPixelScrollMetrics(scrollBox);
|
|
1639
|
+
const addedRows = !exactPixels && previousOldestIndex > 0
|
|
1640
|
+
? getMessageTopOffset(messages, previousOldestIndex, contentWidth)
|
|
1641
|
+
: Math.max(0, getScrollHeight(scrollBox, exactPixels) - currentAnchor.scrollHeight);
|
|
1642
|
+
scrollToPosition(scrollBox, currentAnchor.scrollTop + addedRows, exactPixels);
|
|
1643
|
+
if (currentAnchor.selectedMessageId) {
|
|
1644
|
+
const selectedMessageIndex = messages.findIndex((message) => message.id === currentAnchor.selectedMessageId);
|
|
1645
|
+
if (selectedMessageIndex >= 0) {
|
|
1646
|
+
setSelectedIdx(selectedMessageIndex);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1473
1649
|
queueMicrotask(() => {
|
|
1474
|
-
|
|
1475
|
-
const scrollBox = scrollRef.current;
|
|
1476
|
-
if (!currentAnchor || !scrollBox) return;
|
|
1477
|
-
if (pendingJumpMessageIdRef.current) {
|
|
1650
|
+
if (prependAnchorRef.current === currentAnchor) {
|
|
1478
1651
|
prependAnchorRef.current = null;
|
|
1479
|
-
return;
|
|
1480
1652
|
}
|
|
1481
|
-
|
|
1482
|
-
const previousOldestIndex = currentAnchor.oldestMessageId
|
|
1483
|
-
? messages.findIndex((message) => message.id === currentAnchor.oldestMessageId)
|
|
1484
|
-
: -1;
|
|
1485
|
-
const exactPixels = nativePaneChrome === true && hasPixelScrollMetrics(scrollBox);
|
|
1486
|
-
const addedRows = !exactPixels && previousOldestIndex > 0
|
|
1487
|
-
? getMessageTopOffset(messages, previousOldestIndex, contentWidth)
|
|
1488
|
-
: Math.max(0, getScrollHeight(scrollBox, exactPixels) - currentAnchor.scrollHeight);
|
|
1489
|
-
scrollToPosition(scrollBox, currentAnchor.scrollTop + addedRows, exactPixels);
|
|
1490
|
-
if (currentAnchor.selectedMessageId) {
|
|
1491
|
-
const selectedMessageIndex = messages.findIndex((message) => message.id === currentAnchor.selectedMessageId);
|
|
1492
|
-
if (selectedMessageIndex >= 0) {
|
|
1493
|
-
setSelectedIdx(selectedMessageIndex);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
prependAnchorRef.current = null;
|
|
1497
1653
|
});
|
|
1498
1654
|
}, [contentWidth, loadingOlderMessages, messages, nativePaneChrome]);
|
|
1499
1655
|
|
|
@@ -1506,6 +1662,7 @@ export function ChatContent({
|
|
|
1506
1662
|
const sb = scrollRef.current;
|
|
1507
1663
|
if (!sb) return;
|
|
1508
1664
|
if (!selectionActive) return;
|
|
1665
|
+
if (prependAnchorRef.current) return;
|
|
1509
1666
|
if (nativePaneChrome) {
|
|
1510
1667
|
const selectedMessageId = messages[selectedIdx]?.id ?? "";
|
|
1511
1668
|
runAfterLayout(() => scrollElementIntoScrollBoxView(scrollRef.current, messageElementsRef.current.get(selectedMessageId)));
|
|
@@ -1531,8 +1688,6 @@ export function ChatContent({
|
|
|
1531
1688
|
)
|
|
1532
1689
|
: "";
|
|
1533
1690
|
const inputPlaceholder = replyTo ? `Reply to @${replyTo.user.username}...` : "Type a message...";
|
|
1534
|
-
const activeChannel = channels.find((channel) => channel.id === channelId);
|
|
1535
|
-
const activeChannelLabel = formatChannelLabel(activeChannel, channelId);
|
|
1536
1691
|
const chatContentBg = focused && showChannelSidebar && !sidebarFocused
|
|
1537
1692
|
? blendHex(colors.bg, colors.borderFocused, 0.08)
|
|
1538
1693
|
: undefined;
|
|
@@ -1550,14 +1705,20 @@ export function ChatContent({
|
|
|
1550
1705
|
{showChannelSidebar && (
|
|
1551
1706
|
<ChannelSidebar
|
|
1552
1707
|
channels={channels}
|
|
1708
|
+
channelStates={channelStates}
|
|
1553
1709
|
activeChannelId={channelId}
|
|
1710
|
+
onlineCount={onlineCount}
|
|
1554
1711
|
width={channelSidebarWidth}
|
|
1555
1712
|
height={height}
|
|
1556
1713
|
focused={focused}
|
|
1557
1714
|
keyboardFocused={sidebarFocused}
|
|
1558
1715
|
loading={channelsLoading}
|
|
1716
|
+
canManageNotifications={!!user?.emailVerified}
|
|
1559
1717
|
onSelect={changeChannel}
|
|
1560
1718
|
onFocusRequest={() => setSidebarFocused(true)}
|
|
1719
|
+
onToggleNotifications={(nextChannelId, enabled) => {
|
|
1720
|
+
controller.setChannelNotificationsEnabled(nextChannelId, enabled);
|
|
1721
|
+
}}
|
|
1561
1722
|
/>
|
|
1562
1723
|
)}
|
|
1563
1724
|
|
|
@@ -1570,15 +1731,6 @@ export function ChatContent({
|
|
|
1570
1731
|
onMouseDown={() => setSidebarFocused(false)}
|
|
1571
1732
|
style={nativeFillStyle}
|
|
1572
1733
|
>
|
|
1573
|
-
{compactChannelHeaderHeight > 0 && (
|
|
1574
|
-
<Box height={1} width={contentWidth} flexDirection="row">
|
|
1575
|
-
<Text fg={colors.textDim}>#</Text>
|
|
1576
|
-
<Text fg={colors.positive} attributes={TextAttributes.BOLD}>
|
|
1577
|
-
{truncateChannelLabel(activeChannelLabel, Math.max(contentWidth - 1, 1))}
|
|
1578
|
-
</Text>
|
|
1579
|
-
</Box>
|
|
1580
|
-
)}
|
|
1581
|
-
|
|
1582
1734
|
{!nativePaneChrome && (
|
|
1583
1735
|
<Box height={1} width={contentWidth}>
|
|
1584
1736
|
<Text fg={colors.border}>{"-".repeat(contentWidth)}</Text>
|
|
@@ -1593,7 +1745,7 @@ export function ChatContent({
|
|
|
1593
1745
|
focusable={false}
|
|
1594
1746
|
stickyScroll={stickyTranscript}
|
|
1595
1747
|
stickyStart="bottom"
|
|
1596
|
-
onMouseScroll={
|
|
1748
|
+
onMouseScroll={handleTranscriptScrollActivity}
|
|
1597
1749
|
style={nativePaneChrome ? { minHeight: 0 } : undefined}
|
|
1598
1750
|
>
|
|
1599
1751
|
{loadingOlderMessages && (
|
|
@@ -1655,6 +1807,17 @@ export function ChatContent({
|
|
|
1655
1807
|
|
|
1656
1808
|
{canSend ? (
|
|
1657
1809
|
<>
|
|
1810
|
+
{nativePaneChrome && (
|
|
1811
|
+
<Box
|
|
1812
|
+
width={composerWidth}
|
|
1813
|
+
style={{
|
|
1814
|
+
flex: `0 0 ${DESKTOP_CHAT_INPUT_TOP_MARGIN_PX}px`,
|
|
1815
|
+
height: DESKTOP_CHAT_INPUT_TOP_MARGIN_PX,
|
|
1816
|
+
minHeight: DESKTOP_CHAT_INPUT_TOP_MARGIN_PX,
|
|
1817
|
+
}}
|
|
1818
|
+
/>
|
|
1819
|
+
)}
|
|
1820
|
+
|
|
1658
1821
|
{replyTo && (
|
|
1659
1822
|
<Box height={1} width={contentWidth} flexDirection="row">
|
|
1660
1823
|
<Text fg={colors.textMuted}> replying to </Text>
|
|
@@ -1735,9 +1898,6 @@ function ChatPane({ focused, width, height, close }: PaneProps) {
|
|
|
1735
1898
|
const persistChannelId = useCallback((nextChannelId: string) => {
|
|
1736
1899
|
const currentState = stateRef.current;
|
|
1737
1900
|
const layout = setPaneSetting(currentState.config.layout, paneId, "channelId", nextChannelId);
|
|
1738
|
-
const layouts = currentState.config.layouts.map((savedLayout, index) => (
|
|
1739
|
-
index === currentState.config.activeLayoutIndex ? { ...savedLayout, layout } : savedLayout
|
|
1740
|
-
));
|
|
1741
1901
|
const pluginConfig = {
|
|
1742
1902
|
...currentState.config.pluginConfig,
|
|
1743
1903
|
"gloomberb-cloud": {
|
|
@@ -1748,11 +1908,16 @@ function ChatPane({ focused, width, height, close }: PaneProps) {
|
|
|
1748
1908
|
const nextConfig = {
|
|
1749
1909
|
...currentState.config,
|
|
1750
1910
|
layout,
|
|
1751
|
-
layouts,
|
|
1752
1911
|
pluginConfig,
|
|
1753
1912
|
};
|
|
1754
|
-
|
|
1755
|
-
|
|
1913
|
+
const syncedConfig = syncConfigActiveLayoutState(
|
|
1914
|
+
nextConfig,
|
|
1915
|
+
currentState.paneState,
|
|
1916
|
+
currentState.focusedPaneId,
|
|
1917
|
+
currentState.activePanel,
|
|
1918
|
+
);
|
|
1919
|
+
dispatch({ type: "SET_CONFIG", config: syncedConfig });
|
|
1920
|
+
scheduleConfigSave(syncedConfig);
|
|
1756
1921
|
}, [dispatch, paneId, stateRef]);
|
|
1757
1922
|
const [channelId, setLocalChannelId] = useState(initialChannelIdRef.current);
|
|
1758
1923
|
const pendingChannelIdRef = useRef<string | null>(null);
|