tfrere HF Staff Cursor commited on
Commit
2dfb335
·
1 Parent(s): a2d6d01

feat(editor): Embed Studio button in the top bar

Browse files

Adds a chart button to the TopBar that opens the Embed Studio. Smart entry
point: when charts already exist it opens the studio on one of them (prefers
a real chart over the always-present banner) without mutating the document;
only when there is no chart does it fall back to inserting a fresh one,
mirroring the slash menu's "New Chart" flow. Both paths route through the
existing `open-embed-studio` event so session/key handling stays in one place.

Co-authored-by: Cursor <cursoragent@cursor.com>

frontend/src/App.tsx CHANGED
@@ -230,6 +230,54 @@ export default function App() {
230
  [],
231
  );
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  // When the user opens the chat with an active selection, broadcast it
234
  // via Yjs awareness so every collaborator (including us) sees a
235
  // persistent highlight "my agent is working on this range". The
@@ -1104,6 +1152,7 @@ export default function App() {
1104
  onOpenSettings={() => setSettingsOpen(true)}
1105
  onOpenPublish={openPublishDialog}
1106
  onOpenMobileToc={() => setTocSidebarOpen(true)}
 
1107
  />
1108
 
1109
  {!isEditorReady && (
 
230
  [],
231
  );
232
 
233
+ // Global entry point for the Embed Studio (TopBar button). Smart
234
+ // behaviour: if charts already exist, open the studio on one of them
235
+ // (browse/edit mode via the FilesSidebar) without touching the
236
+ // document. Only when there is no chart at all do we fall back to
237
+ // creating a fresh one - mirroring the slash menu's "New Chart" flow.
238
+ // Both paths route through the existing `open-embed-studio` listener
239
+ // so session/key handling stays in one place.
240
+ const handleOpenEmbedStudio = useCallback(() => {
241
+ const keys = embedStore ? embedStore.keys() : [];
242
+ if (keys.length > 0) {
243
+ // Prefer a real chart over the always-present banner so the
244
+ // studio lands on content the user most likely wants to tweak.
245
+ const preferred = keys.find((k) => k !== "banner.html") ?? keys[0];
246
+ window.dispatchEvent(
247
+ new CustomEvent("open-embed-studio", { detail: { src: preferred } }),
248
+ );
249
+ return;
250
+ }
251
+
252
+ const editor = editorRef.current;
253
+ if (!editor) return;
254
+ const id = `d3-chart-${Date.now().toString(36)}`;
255
+ const src = `${id}.html`;
256
+ (editor.chain().focus() as any).insertHtmlEmbed().run();
257
+ setTimeout(() => {
258
+ const { doc } = editor.state;
259
+ let targetPos = -1;
260
+ doc.descendants((node, pos) => {
261
+ if (node.type.name === "htmlEmbed" && !node.attrs.src) {
262
+ targetPos = pos;
263
+ return false;
264
+ }
265
+ });
266
+ if (targetPos >= 0) {
267
+ editor.view.dispatch(
268
+ editor.state.tr.setNodeMarkup(targetPos, undefined, {
269
+ ...editor.state.doc.nodeAt(targetPos)?.attrs,
270
+ src,
271
+ title: "New chart",
272
+ }),
273
+ );
274
+ }
275
+ window.dispatchEvent(
276
+ new CustomEvent("open-embed-studio", { detail: { src } }),
277
+ );
278
+ }, 50);
279
+ }, [embedStore]);
280
+
281
  // When the user opens the chat with an active selection, broadcast it
282
  // via Yjs awareness so every collaborator (including us) sees a
283
  // persistent highlight "my agent is working on this range". The
 
1152
  onOpenSettings={() => setSettingsOpen(true)}
1153
  onOpenPublish={openPublishDialog}
1154
  onOpenMobileToc={() => setTocSidebarOpen(true)}
1155
+ onOpenEmbedStudio={handleOpenEmbedStudio}
1156
  />
1157
 
1158
  {!isEditorReady && (
frontend/src/components/TopBar.tsx CHANGED
@@ -7,6 +7,7 @@ import {
7
  Settings,
8
  Upload,
9
  Eye,
 
10
  Sun,
11
  Moon,
12
  Menu,
@@ -50,6 +51,11 @@ interface TopBarProps {
50
  onOpenSettings: () => void;
51
  onOpenPublish: () => void;
52
  onOpenMobileToc: () => void;
 
 
 
 
 
53
  }
54
 
55
  /**
@@ -72,6 +78,7 @@ export function TopBar({
72
  onOpenSettings,
73
  onOpenPublish,
74
  onOpenMobileToc,
 
75
  }: TopBarProps) {
76
  const publishTooltip = !canEdit
77
  ? "Read-only access - you don't have write rights on this Space"
@@ -202,6 +209,15 @@ export function TopBar({
202
  <Settings size={18} />
203
  </button>
204
  </Tooltip>
 
 
 
 
 
 
 
 
 
205
  <Tooltip title="Preview article">
206
  <button
207
  className="icon-btn"
 
7
  Settings,
8
  Upload,
9
  Eye,
10
+ BarChart3,
11
  Sun,
12
  Moon,
13
  Menu,
 
51
  onOpenSettings: () => void;
52
  onOpenPublish: () => void;
53
  onOpenMobileToc: () => void;
54
+ /**
55
+ * Open the Embed Studio from the toolbar. Smart entry point: lands on
56
+ * an existing chart when there is one, otherwise creates a new chart.
57
+ */
58
+ onOpenEmbedStudio: () => void;
59
  }
60
 
61
  /**
 
78
  onOpenSettings,
79
  onOpenPublish,
80
  onOpenMobileToc,
81
+ onOpenEmbedStudio,
82
  }: TopBarProps) {
83
  const publishTooltip = !canEdit
84
  ? "Read-only access - you don't have write rights on this Space"
 
209
  <Settings size={18} />
210
  </button>
211
  </Tooltip>
212
+ <Tooltip title="Embed Studio (charts)">
213
+ <button
214
+ className="icon-btn"
215
+ onClick={onOpenEmbedStudio}
216
+ aria-label="Open Embed Studio"
217
+ >
218
+ <BarChart3 size={18} />
219
+ </button>
220
+ </Tooltip>
221
  <Tooltip title="Preview article">
222
  <button
223
  className="icon-btn"