@backmatter/studio 0.0.14 → 0.0.16
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/dist/chunks/{UiKitApp-fYeVEf4-.js → UiKitApp-_EDqWID3.js} +2 -2
- package/dist/chunks/{WorkspaceRoutes-Dt7JvEWT.js → WorkspaceRoutes-B2PrFgpz.js} +29019 -27476
- package/dist/chunks/{dashboard-BuY2aGkO.js → dashboard-BnlnMnnr.js} +55 -54
- package/dist/chunks/{index-NWDqEZYW.js → index-D_BHF_8q.js} +1 -1
- package/dist/dashboard.js +2 -2
- package/dist/index.js +51 -52
- package/dist/site-widget.css +4 -0
- package/dist/site-widget.js +9419 -9156
- package/dist/studio.css +186 -12
- package/package.json +2 -2
- package/src/WorkspaceRoutes.tsx +5 -2
- package/src/api.ts +64 -11
- package/src/chat/ChangeCard.tsx +9 -15
- package/src/chat/ChatMessage.tsx +172 -103
- package/src/chat/ChatPanel.tsx +54 -34
- package/src/chat/ChatProvider.tsx +120 -24
- package/src/chat/Composer.tsx +48 -7
- package/src/chat/DocListCard.tsx +2 -16
- package/src/chat/MentionPopover.tsx +8 -9
- package/src/chat/PointerChip.tsx +9 -5
- package/src/chat/ThreadHistory.tsx +3 -3
- package/src/chat/chat-context.ts +74 -10
- package/src/chat/chat-helpers.ts +15 -8
- package/src/chat/chat-state.ts +11 -0
- package/src/chrome/AppShell.tsx +16 -5
- package/src/chrome/CommandPalette.tsx +14 -10
- package/src/chrome/ContentExplorerSidebar.tsx +23 -3
- package/src/chrome/UserMenu.tsx +34 -3
- package/src/chrome/WorkspaceStatusBar.tsx +215 -50
- package/src/chrome/content-api-url.ts +0 -78
- package/src/chrome/content-tree-visibility.ts +13 -3
- package/src/chrome/explorer-icons.tsx +5 -0
- package/src/code-session-entry.ts +9 -0
- package/src/components/WorkspaceSwitcher.tsx +2 -2
- package/src/element-origin.ts +149 -0
- package/src/host-pointer-bootstrap.ts +11 -0
- package/src/host-pointer.ts +147 -0
- package/src/install-preview-pointer.ts +19 -3
- package/src/pages/CodeSessionFiles.tsx +221 -0
- package/src/pages/CodeSessionPage.tsx +1188 -0
- package/src/pages/DocEditorPage.tsx +33 -15
- package/src/pages/EmbedChatPage.tsx +3 -3
- package/src/pages/EmbedPublishPage.tsx +2 -3
- package/src/pages/PreviewSize.tsx +181 -0
- package/src/pages/SettingsGeneralPage.tsx +67 -2
- package/src/pages/WorkspaceHomePage.tsx +313 -0
- package/src/pages/WorkspaceLayout.tsx +10 -1
- package/src/pages/code-session-boot.ts +43 -0
- package/src/pages/code-session-context.tsx +51 -0
- package/src/pages/code-session-files.ts +42 -0
- package/src/pages/code-session-frame.ts +52 -0
- package/src/pages/code-session-routes.ts +42 -0
- package/src/pages/home-rows.ts +140 -0
- package/src/pages/preview-size.ts +33 -0
- package/src/pages/session-preview.ts +109 -0
- package/src/pages/type-docs.ts +3 -6
- package/src/pages/use-session-preview.ts +70 -0
- package/src/replica/store.ts +13 -10
- package/src/replica/types.ts +7 -2
- package/src/site-pointer.ts +24 -5
- package/src/site-widget.tsx +2 -3
- package/src/studio-config.ts +1 -1
- package/src/ui/ListRow.tsx +12 -3
- package/src/ui/Page.tsx +12 -1
- package/src/workspace-path.ts +35 -3
- package/src/chrome/ContentApiPopover.tsx +0 -215
- package/src/pages/WorkspaceHomeRedirect.tsx +0 -17
package/src/chat/ChatMessage.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// packages/studio/src/chat/ChatMessage.tsx
|
|
2
|
-
import { useState, type ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { useState, type ComponentPropsWithoutRef, type ReactNode } from "react";
|
|
3
3
|
import Markdown from "react-markdown";
|
|
4
4
|
import * as stylex from "@stylexjs/stylex";
|
|
5
5
|
import { getToolName, isToolUIPart, type UIMessage } from "ai";
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
changesInToolOutput,
|
|
23
23
|
type WriteToolOutput,
|
|
24
24
|
} from "./chat-helpers";
|
|
25
|
-
import { CheckIcon, EyeIcon, FileIcon, SearchIcon, UndoIcon } from "./icons";
|
|
25
|
+
import { CheckIcon, ChevronDownIcon, EyeIcon, FileIcon, SearchIcon, UndoIcon } from "./icons";
|
|
26
26
|
import { pointersFromMetadata } from "./chat-context";
|
|
27
27
|
import { PointerChip, pointerKey } from "./PointerChip";
|
|
28
28
|
import { Reveal } from "./Reveal";
|
|
@@ -34,7 +34,7 @@ type ToolPart = Extract<UIMessage["parts"][number], { toolCallId: string }>;
|
|
|
34
34
|
type ListDocsOutput = { docs: DocCardItem[]; total?: number };
|
|
35
35
|
type SearchDocsOutput = {
|
|
36
36
|
matches: Array<{
|
|
37
|
-
docRef: {
|
|
37
|
+
docRef: { type: string; slug: string };
|
|
38
38
|
source?: string;
|
|
39
39
|
excerpt?: string;
|
|
40
40
|
title?: string | null;
|
|
@@ -79,6 +79,51 @@ function ToolIcon({ name }: { name: string }) {
|
|
|
79
79
|
return <FileIcon size={12} />;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
type TraceRow = {
|
|
83
|
+
key: string;
|
|
84
|
+
name: string;
|
|
85
|
+
label: string;
|
|
86
|
+
failed: boolean;
|
|
87
|
+
error: string | null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function ToolTrace({ rows }: { rows: TraceRow[] }) {
|
|
91
|
+
const [open, setOpen] = useState(false);
|
|
92
|
+
if (rows.length === 0) return null;
|
|
93
|
+
const list = rows.map((row) => (
|
|
94
|
+
<div key={row.key} {...stylex.props(styles.toolRow, row.failed && styles.toolFailed)}>
|
|
95
|
+
<ToolIcon name={row.name} />
|
|
96
|
+
<span>{row.label}</span>
|
|
97
|
+
{row.error ? <span {...stylex.props(styles.toolError)}>{row.error}</span> : null}
|
|
98
|
+
</div>
|
|
99
|
+
));
|
|
100
|
+
|
|
101
|
+
const failed = rows.filter((row) => row.failed).length;
|
|
102
|
+
const summary = rows.length === 1 ? "1 step" : `${rows.length} steps`;
|
|
103
|
+
return (
|
|
104
|
+
<div {...stylex.props(styles.toolBlock)}>
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
aria-expanded={open}
|
|
108
|
+
aria-label={open ? `Hide ${summary}` : `Show ${summary}`}
|
|
109
|
+
onClick={() => setOpen((value) => !value)}
|
|
110
|
+
{...stylex.props(styles.traceToggle)}
|
|
111
|
+
>
|
|
112
|
+
<span {...stylex.props(styles.traceChevron, open && styles.traceChevronOpen)}>
|
|
113
|
+
<ChevronDownIcon />
|
|
114
|
+
</span>
|
|
115
|
+
<span>{summary}</span>
|
|
116
|
+
{failed > 0 ? (
|
|
117
|
+
<span {...stylex.props(styles.toolFailed)}>
|
|
118
|
+
{failed === 1 ? "1 failed" : `${failed} failed`}
|
|
119
|
+
</span>
|
|
120
|
+
) : null}
|
|
121
|
+
</button>
|
|
122
|
+
{open ? list : null}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
82
127
|
const md = {
|
|
83
128
|
p: (p: ComponentPropsWithoutRef<"p">) => <p {...p} {...stylex.props(mdStyles.p)} />,
|
|
84
129
|
ul: (p: ComponentPropsWithoutRef<"ul">) => <ul {...p} {...stylex.props(mdStyles.list)} />,
|
|
@@ -213,108 +258,107 @@ export function ChatMessage({
|
|
|
213
258
|
);
|
|
214
259
|
}
|
|
215
260
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
<Reveal key={`${i}-${j}`} active={streaming} origin="start">
|
|
236
|
-
<div {...stylex.props(styles.markdown)}>
|
|
237
|
-
<Markdown components={md}>{seg.text}</Markdown>
|
|
238
|
-
</div>
|
|
239
|
-
</Reveal>
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
if (part.type === "reasoning") return null;
|
|
244
|
-
if (isToolUIPart(part)) {
|
|
245
|
-
const tp = part as ToolPart;
|
|
246
|
-
if (tp.state !== "output-available" && tp.state !== "output-error") return null;
|
|
247
|
-
const name = getToolName(tp);
|
|
248
|
-
const label = toolLabel(
|
|
249
|
-
name,
|
|
250
|
-
tp.input,
|
|
251
|
-
tp.state === "output-available" ? tp.output : undefined,
|
|
252
|
-
tp.state,
|
|
253
|
-
);
|
|
254
|
-
const write = isWriteTool(name);
|
|
255
|
-
const commit = isCommitTool(name);
|
|
256
|
-
const out = tp.state === "output-available" ? (tp.output as WriteToolOutput) : null;
|
|
257
|
-
const failed =
|
|
258
|
-
tp.state === "output-error" ||
|
|
259
|
-
(out !== null && (write || commit) && !out.ok);
|
|
260
|
-
const written = write && out && out.ok ? changesInToolOutput(out) : [];
|
|
261
|
-
// Doc lists placed inline via [[docs]] are not repeated here. While
|
|
262
|
-
// streaming, the placeholder usually arrives a moment after the tool
|
|
263
|
-
// result, so hold the fallback back until the stream has ended;
|
|
264
|
-
// otherwise the card would jump from here to its inline position.
|
|
265
|
-
const docList =
|
|
266
|
-
tp.state === "output-available" && !inlined.has(i) && !streaming
|
|
267
|
-
? docListFromTool(name, tp.output)
|
|
268
|
-
: null;
|
|
269
|
-
const cards = written.map((raw) => {
|
|
270
|
-
const change = {
|
|
271
|
-
...(session.changes[raw.id] ?? raw),
|
|
272
|
-
summary: session.changes[raw.id]?.summary ?? raw.summary,
|
|
273
|
-
};
|
|
274
|
-
const viewStatus = presentedChangeStatus(change, stack);
|
|
275
|
-
return { change, viewStatus };
|
|
276
|
-
});
|
|
277
|
-
return (
|
|
278
|
-
<Reveal key={tp.toolCallId} active={streaming} origin="start">
|
|
279
|
-
<div {...stylex.props(styles.toolBlock)}>
|
|
280
|
-
<div {...stylex.props(styles.toolRow, failed && styles.toolFailed)}>
|
|
281
|
-
<ToolIcon name={name} />
|
|
282
|
-
<span>{label}</span>
|
|
283
|
-
{failed ? (
|
|
284
|
-
<span {...stylex.props(styles.toolError)}>
|
|
285
|
-
{tp.state === "output-error"
|
|
286
|
-
? tp.errorText
|
|
287
|
-
: (out as { message?: string } | null)?.message}
|
|
288
|
-
</span>
|
|
289
|
-
) : null}
|
|
290
|
-
</div>
|
|
291
|
-
{docList ? (
|
|
292
|
-
<DocListCard
|
|
293
|
-
items={docList.items}
|
|
294
|
-
total={docList.total}
|
|
295
|
-
workspaceId={workspaceId}
|
|
296
|
-
emptyLabel={name === "searchDocs" ? "No matches" : "No documents"}
|
|
297
|
-
/>
|
|
298
|
-
) : null}
|
|
299
|
-
{cards.map(({ change, viewStatus }) =>
|
|
300
|
-
viewStatus ? (
|
|
301
|
-
<ChangeCard
|
|
302
|
-
key={change.id}
|
|
303
|
-
change={{ ...change, status: viewStatus }}
|
|
304
|
-
workspaceId={workspaceId}
|
|
305
|
-
busy={busyId === change.id}
|
|
306
|
-
canAct={canAct && !streaming}
|
|
307
|
-
onKeep={() => void act(change.id, () => session.keep(change.id))}
|
|
308
|
-
onUndo={() => void act(change.id, () => session.undo(change.id))}
|
|
309
|
-
/>
|
|
310
|
-
) : null,
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
</Reveal>
|
|
261
|
+
const flow: ReactNode[] = [];
|
|
262
|
+
const traceRows: TraceRow[] = [];
|
|
263
|
+
let traceSlotted = false;
|
|
264
|
+
for (let i = 0; i < parts.length; i++) {
|
|
265
|
+
const part = parts[i];
|
|
266
|
+
if (part.type === "text") {
|
|
267
|
+
const text = streaming ? trimPartialPlaceholder(part.text) : part.text;
|
|
268
|
+
if (!text.trim()) continue;
|
|
269
|
+
const segments = splitDocsPlaceholders(text);
|
|
270
|
+
let n = 0;
|
|
271
|
+
for (let j = 0; j < segments.length; j++) {
|
|
272
|
+
const seg = segments[j];
|
|
273
|
+
if (seg.kind === "docs") {
|
|
274
|
+
const target = placeholderTarget.get(`${i}:${n++}`);
|
|
275
|
+
if (target === undefined) continue;
|
|
276
|
+
flow.push(
|
|
277
|
+
<Reveal key={`${i}-docs-${j}`} active={streaming} origin="start">
|
|
278
|
+
{renderDocCard(target, `${i}-docs-${j}`)}
|
|
279
|
+
</Reveal>,
|
|
314
280
|
);
|
|
281
|
+
continue;
|
|
315
282
|
}
|
|
316
|
-
|
|
317
|
-
|
|
283
|
+
flow.push(
|
|
284
|
+
<Reveal key={`${i}-${j}`} active={streaming} origin="start">
|
|
285
|
+
<div {...stylex.props(styles.markdown)}>
|
|
286
|
+
<Markdown components={md}>{seg.text}</Markdown>
|
|
287
|
+
</div>
|
|
288
|
+
</Reveal>,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (part.type === "reasoning" || !isToolUIPart(part)) continue;
|
|
294
|
+
const tp = part as ToolPart;
|
|
295
|
+
if (tp.state !== "output-available" && tp.state !== "output-error") continue;
|
|
296
|
+
const name = getToolName(tp);
|
|
297
|
+
const label = toolLabel(
|
|
298
|
+
name,
|
|
299
|
+
tp.input,
|
|
300
|
+
tp.state === "output-available" ? tp.output : undefined,
|
|
301
|
+
tp.state,
|
|
302
|
+
);
|
|
303
|
+
const write = isWriteTool(name);
|
|
304
|
+
const commit = isCommitTool(name);
|
|
305
|
+
const out = tp.state === "output-available" ? (tp.output as WriteToolOutput) : null;
|
|
306
|
+
const failed =
|
|
307
|
+
tp.state === "output-error" || (out !== null && (write || commit) && !out.ok);
|
|
308
|
+
const error = failed
|
|
309
|
+
? (tp.state === "output-error"
|
|
310
|
+
? tp.errorText
|
|
311
|
+
: (out as { message?: string } | null)?.message) ?? null
|
|
312
|
+
: null;
|
|
313
|
+
traceRows.push({ key: tp.toolCallId, name, label, failed, error });
|
|
314
|
+
if (!traceSlotted) {
|
|
315
|
+
traceSlotted = true;
|
|
316
|
+
flow.push(<ToolTrace key="trace" rows={traceRows} />);
|
|
317
|
+
}
|
|
318
|
+
const written = write && out && out.ok ? changesInToolOutput(out) : [];
|
|
319
|
+
// Doc lists placed inline via [[docs]] are not repeated here. While
|
|
320
|
+
// streaming, the placeholder usually arrives a moment after the tool
|
|
321
|
+
// result, so hold the fallback back until the stream has ended;
|
|
322
|
+
// otherwise the card would jump from here to its inline position.
|
|
323
|
+
const docList =
|
|
324
|
+
tp.state === "output-available" && !inlined.has(i) && !streaming
|
|
325
|
+
? docListFromTool(name, tp.output)
|
|
326
|
+
: null;
|
|
327
|
+
if (docList) {
|
|
328
|
+
flow.push(
|
|
329
|
+
<DocListCard
|
|
330
|
+
key={`${tp.toolCallId}-docs`}
|
|
331
|
+
items={docList.items}
|
|
332
|
+
total={docList.total}
|
|
333
|
+
workspaceId={workspaceId}
|
|
334
|
+
emptyLabel={name === "searchDocs" ? "No matches" : "No documents"}
|
|
335
|
+
/>,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
for (const raw of written) {
|
|
339
|
+
const change = {
|
|
340
|
+
...(session.changes[raw.id] ?? raw),
|
|
341
|
+
summary: session.changes[raw.id]?.summary ?? raw.summary,
|
|
342
|
+
};
|
|
343
|
+
const viewStatus = presentedChangeStatus(change, stack);
|
|
344
|
+
if (!viewStatus) continue;
|
|
345
|
+
flow.push(
|
|
346
|
+
<ChangeCard
|
|
347
|
+
key={change.id}
|
|
348
|
+
change={{ ...change, status: viewStatus }}
|
|
349
|
+
workspaceId={workspaceId}
|
|
350
|
+
busy={busyId === change.id}
|
|
351
|
+
canAct={canAct && !streaming}
|
|
352
|
+
onKeep={() => void act(change.id, () => session.keep(change.id))}
|
|
353
|
+
onUndo={() => void act(change.id, () => session.undo(change.id))}
|
|
354
|
+
/>,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<div {...stylex.props(styles.assistant)}>
|
|
361
|
+
{flow}
|
|
318
362
|
{applied.length > 1 && canAct && !streaming ? (
|
|
319
363
|
<div {...stylex.props(styles.bulk)}>
|
|
320
364
|
<span {...stylex.props(styles.bulkLabel)}>
|
|
@@ -389,6 +433,31 @@ const styles = stylex.create({
|
|
|
389
433
|
flexDirection: "column",
|
|
390
434
|
gap: space.sm,
|
|
391
435
|
},
|
|
436
|
+
traceToggle: {
|
|
437
|
+
display: "inline-flex",
|
|
438
|
+
alignItems: "center",
|
|
439
|
+
gap: space.sm,
|
|
440
|
+
padding: 0,
|
|
441
|
+
borderWidth: 0,
|
|
442
|
+
backgroundColor: "transparent",
|
|
443
|
+
font: "inherit",
|
|
444
|
+
fontSize: type.sm,
|
|
445
|
+
color: colors.textMuted,
|
|
446
|
+
cursor: "pointer",
|
|
447
|
+
textAlign: "left",
|
|
448
|
+
},
|
|
449
|
+
traceChevron: {
|
|
450
|
+
display: "inline-flex",
|
|
451
|
+
transform: "rotate(-90deg)",
|
|
452
|
+
transitionProperty: "transform",
|
|
453
|
+
transitionDuration: "120ms",
|
|
454
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
455
|
+
transitionDuration: "0ms",
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
traceChevronOpen: {
|
|
459
|
+
transform: "rotate(0deg)",
|
|
460
|
+
},
|
|
392
461
|
toolRow: {
|
|
393
462
|
display: "flex",
|
|
394
463
|
alignItems: "center",
|
package/src/chat/ChatPanel.tsx
CHANGED
|
@@ -32,6 +32,7 @@ export function ChatPanel({ fill = false, site = false }: { fill?: boolean; site
|
|
|
32
32
|
const session = useChatSession();
|
|
33
33
|
const visible = isPanelVisible(ui.panel.mode);
|
|
34
34
|
const fullscreen = ui.panel.mode === "fullscreen";
|
|
35
|
+
const docked = Boolean(ui.codeSessionId);
|
|
35
36
|
const threadId = session.thread?.id ?? null;
|
|
36
37
|
const knownIds = useRef<Set<string> | null>(null);
|
|
37
38
|
const seenThread = useRef<string | null | undefined>(undefined);
|
|
@@ -50,7 +51,9 @@ export function ChatPanel({ fill = false, site = false }: { fill?: boolean; site
|
|
|
50
51
|
resetKey: threadId,
|
|
51
52
|
pinKey: anchorId,
|
|
52
53
|
slotRef,
|
|
53
|
-
|
|
54
|
+
// The composer growing changes this viewport. That is not a new message,
|
|
55
|
+
// and following it scrolls the thread on every line the person types.
|
|
56
|
+
deps: [session.messages, session.status],
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
if (seenThread.current !== threadId) {
|
|
@@ -75,7 +78,7 @@ export function ChatPanel({ fill = false, site = false }: { fill?: boolean; site
|
|
|
75
78
|
|
|
76
79
|
const contextRows = Math.max(
|
|
77
80
|
1,
|
|
78
|
-
(ui.focus && ui.includeFocus ? 1 : 0) + ui.mentions.length,
|
|
81
|
+
(ui.focus && ui.includeFocus ? 1 : 0) + (ui.route ? 1 : 0) + ui.mentions.length,
|
|
79
82
|
);
|
|
80
83
|
const contextReserve = contextRows * CONTEXT_ROW_HEIGHT + CONTEXT_BAR_PADDING + 12;
|
|
81
84
|
|
|
@@ -153,7 +156,11 @@ export function ChatPanel({ fill = false, site = false }: { fill?: boolean; site
|
|
|
153
156
|
aria-label="AI chat"
|
|
154
157
|
data-backmatter-panel=""
|
|
155
158
|
{...stylex.props(
|
|
156
|
-
fill
|
|
159
|
+
fill
|
|
160
|
+
? ui.codeSessionId
|
|
161
|
+
? styles.panelColumnFill
|
|
162
|
+
: styles.panelFill
|
|
163
|
+
: styles.panel,
|
|
157
164
|
site && styles.panelSite,
|
|
158
165
|
!fill && fullscreen && styles.panelFull,
|
|
159
166
|
)}
|
|
@@ -193,37 +200,41 @@ export function ChatPanel({ fill = false, site = false }: { fill?: boolean; site
|
|
|
193
200
|
Back to chat
|
|
194
201
|
</button>
|
|
195
202
|
)}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
203
|
+
{docked ? null : (
|
|
204
|
+
<>
|
|
205
|
+
<span {...stylex.props(styles.divider)} />
|
|
206
|
+
<Tooltip label="Minimize (Esc)">
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
aria-label="Minimize"
|
|
210
|
+
onClick={() => ui.dispatch({ type: "minimize" })}
|
|
211
|
+
{...stylex.props(styles.iconBtn)}
|
|
212
|
+
>
|
|
213
|
+
<MinimizeIcon />
|
|
214
|
+
</button>
|
|
215
|
+
</Tooltip>
|
|
216
|
+
<Tooltip label={fullscreen ? "Exit fullscreen" : "Fullscreen"}>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
aria-label={fullscreen ? "Exit fullscreen" : "Fullscreen"}
|
|
220
|
+
onClick={() => ui.dispatch({ type: "toggleFullscreen" })}
|
|
221
|
+
{...stylex.props(styles.iconBtn)}
|
|
222
|
+
>
|
|
223
|
+
{fullscreen ? <CollapseIcon /> : <ExpandIcon />}
|
|
224
|
+
</button>
|
|
225
|
+
</Tooltip>
|
|
226
|
+
<Tooltip label="Close">
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
aria-label="Close"
|
|
230
|
+
onClick={() => ui.dispatch({ type: "close" })}
|
|
231
|
+
{...stylex.props(styles.iconBtn)}
|
|
232
|
+
>
|
|
233
|
+
<CloseIcon />
|
|
234
|
+
</button>
|
|
235
|
+
</Tooltip>
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
227
238
|
</div>
|
|
228
239
|
</header>
|
|
229
240
|
|
|
@@ -353,6 +364,15 @@ const styles = stylex.create({
|
|
|
353
364
|
overflow: "hidden",
|
|
354
365
|
backgroundColor: colors.bgPanel,
|
|
355
366
|
},
|
|
367
|
+
panelColumnFill: {
|
|
368
|
+
position: "relative",
|
|
369
|
+
flex: 1,
|
|
370
|
+
minHeight: 0,
|
|
371
|
+
display: "flex",
|
|
372
|
+
flexDirection: "column",
|
|
373
|
+
overflow: "hidden",
|
|
374
|
+
backgroundColor: colors.bgPanel,
|
|
375
|
+
},
|
|
356
376
|
header: {
|
|
357
377
|
display: "flex",
|
|
358
378
|
alignItems: "center",
|