@agent-native/core 0.149.1 → 0.149.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +122 -9
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +69 -60
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -3
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -27
- package/corpus/templates/slides/app/pages/Index.tsx +240 -89
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +129 -10
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +2 -2
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
|
@@ -13,32 +13,23 @@ import { PresenceBar } from "@agent-native/toolkit/collab-ui";
|
|
|
13
13
|
import {
|
|
14
14
|
IconArrowLeft,
|
|
15
15
|
IconPlayerPlay,
|
|
16
|
-
IconLayout,
|
|
17
16
|
IconLayoutSidebar,
|
|
18
17
|
IconPhoto,
|
|
19
18
|
IconHistory,
|
|
20
19
|
IconFolderOpen,
|
|
21
|
-
IconSettings,
|
|
22
|
-
IconSchema,
|
|
23
|
-
IconPencil,
|
|
24
|
-
IconTransform,
|
|
25
20
|
IconMessage,
|
|
26
|
-
IconBolt,
|
|
27
|
-
IconAdjustments,
|
|
28
|
-
IconPencilPlus,
|
|
29
|
-
IconPin,
|
|
30
|
-
IconTool,
|
|
31
21
|
IconDownload,
|
|
32
22
|
IconSun,
|
|
33
23
|
IconMoon,
|
|
34
24
|
IconDotsVertical,
|
|
35
25
|
IconLoader2,
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
IconBolt,
|
|
27
|
+
IconAdjustments,
|
|
28
|
+
IconPencilPlus,
|
|
29
|
+
IconPin,
|
|
38
30
|
} from "@tabler/icons-react";
|
|
39
31
|
import { useTheme } from "next-themes";
|
|
40
32
|
import { useState, useRef, useEffect } from "react";
|
|
41
|
-
import { createPortal } from "react-dom";
|
|
42
33
|
import { Link } from "react-router";
|
|
43
34
|
import { toast } from "sonner";
|
|
44
35
|
|
|
@@ -46,6 +37,7 @@ import {
|
|
|
46
37
|
DropdownMenu,
|
|
47
38
|
DropdownMenuContent,
|
|
48
39
|
DropdownMenuGroup,
|
|
40
|
+
DropdownMenuLabel,
|
|
49
41
|
DropdownMenuItem,
|
|
50
42
|
DropdownMenuTrigger,
|
|
51
43
|
DropdownMenuSeparator,
|
|
@@ -56,23 +48,12 @@ import {
|
|
|
56
48
|
TooltipTrigger,
|
|
57
49
|
} from "@/components/ui/tooltip";
|
|
58
50
|
import { SaveStatusIndicator } from "@/components/visual-editor";
|
|
59
|
-
import type { Deck, Slide
|
|
60
|
-
import {
|
|
61
|
-
defaultSlideContent,
|
|
62
|
-
useDecks,
|
|
63
|
-
useSaveState,
|
|
64
|
-
} from "@/context/DeckContext";
|
|
65
|
-
import {
|
|
66
|
-
ASPECT_RATIO_VALUES,
|
|
67
|
-
type AspectRatio,
|
|
68
|
-
DEFAULT_ASPECT_RATIO,
|
|
69
|
-
} from "@/lib/aspect-ratios";
|
|
51
|
+
import type { Deck, Slide } from "@/context/DeckContext";
|
|
52
|
+
import { useSaveState } from "@/context/DeckContext";
|
|
70
53
|
import { getDeckShareLinkOrder } from "@/lib/deck-share-links";
|
|
71
54
|
import type { GoogleSlidesExportResult } from "@/lib/export-google-slides-client";
|
|
72
55
|
import { parseUploadResponse } from "@/lib/upload-response";
|
|
73
|
-
import { shortcutLabel } from "@/lib/utils";
|
|
74
56
|
|
|
75
|
-
import { commitActiveEditThenRun } from "./commit-active-edit";
|
|
76
57
|
import { EditorActionCluster } from "./EditorActionCluster";
|
|
77
58
|
import { ExportMenu } from "./ExportMenu";
|
|
78
59
|
interface EditorToolbarProps {
|
|
@@ -90,16 +71,9 @@ interface EditorToolbarProps {
|
|
|
90
71
|
onToggleSidebar: () => void;
|
|
91
72
|
onGenerateImage: () => void;
|
|
92
73
|
onOpenAssetLibrary: () => void;
|
|
93
|
-
imageGenButtonRef: React.RefObject<HTMLButtonElement | null>;
|
|
94
|
-
assetsButtonRef: React.RefObject<HTMLButtonElement | null>;
|
|
95
|
-
historyOpen: boolean;
|
|
96
74
|
onShowHistory: () => void;
|
|
97
75
|
historyButtonRef: React.RefObject<HTMLButtonElement | null>;
|
|
98
76
|
currentSlide?: Slide;
|
|
99
|
-
onUpdateSlide?: (
|
|
100
|
-
updates: Partial<Omit<Slide, "id">>,
|
|
101
|
-
slideIdOverride?: string,
|
|
102
|
-
) => void;
|
|
103
77
|
/** Active users on the current slide (from collab awareness) */
|
|
104
78
|
activeUsers?: CollabUser[];
|
|
105
79
|
/** Whether the agent has a durable presence entry on this slide */
|
|
@@ -142,10 +116,6 @@ interface EditorToolbarProps {
|
|
|
142
116
|
onExportPptx?: () => Promise<void> | void;
|
|
143
117
|
/** Create the deck in the user's Google Drive as native Google Slides */
|
|
144
118
|
onExportGoogleSlides?: () => Promise<GoogleSlidesExportResult>;
|
|
145
|
-
/** Active deck aspect ratio (defaults to 16:9 when omitted) */
|
|
146
|
-
aspectRatio?: AspectRatio;
|
|
147
|
-
/** Change the deck's aspect ratio */
|
|
148
|
-
onSetAspectRatio?: (ratio: AspectRatio) => void;
|
|
149
119
|
/** Insert a blank slide after the current one */
|
|
150
120
|
onAddEmptySlide?: () => void;
|
|
151
121
|
/** Duplicate the current slide */
|
|
@@ -158,92 +128,9 @@ interface EditorToolbarProps {
|
|
|
158
128
|
onAddSlideGeneratingChange?: (generating: boolean) => void;
|
|
159
129
|
}
|
|
160
130
|
|
|
161
|
-
const slideLayoutOptions: { value: SlideLayout; labelKey: string }[] = [
|
|
162
|
-
{ value: "title", labelKey: "editorToolbar.layoutTitle" },
|
|
163
|
-
{ value: "section", labelKey: "editorToolbar.layoutSection" },
|
|
164
|
-
{ value: "content", labelKey: "editorToolbar.layoutContent" },
|
|
165
|
-
{ value: "two-column", labelKey: "editorToolbar.layoutTwoColumn" },
|
|
166
|
-
{ value: "image", labelKey: "editorToolbar.layoutImage" },
|
|
167
|
-
{ value: "statement", labelKey: "editorToolbar.layoutStatement" },
|
|
168
|
-
{ value: "full-image", labelKey: "editorToolbar.layoutFullImage" },
|
|
169
|
-
{ value: "blank", labelKey: "editorToolbar.layoutBlank" },
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
const backgroundOptions = [
|
|
173
|
-
"bg-[#000000]",
|
|
174
|
-
"bg-[#0a0a0a]",
|
|
175
|
-
"bg-[#0f0f11]",
|
|
176
|
-
"bg-[#111114]",
|
|
177
|
-
"bg-[#141418]",
|
|
178
|
-
"bg-gradient-to-br from-[#000000] to-[#0a0a14]",
|
|
179
|
-
"bg-gradient-to-br from-[#0a0a0a] to-[#0f1a14]",
|
|
180
|
-
"bg-[#ffffff]",
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
const HEX_COLOR_PATTERN = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
184
131
|
const TOOLBAR_ICON_BUTTON_CLASS =
|
|
185
132
|
"inline-flex size-8 flex-shrink-0 items-center justify-center rounded-md transition-colors";
|
|
186
133
|
|
|
187
|
-
/** Native <input type="color"> only accepts 3/6-digit hex — fall back to
|
|
188
|
-
* black for gradients or other raw CSS values so the picker still opens. */
|
|
189
|
-
function toColorInputValue(background: string | undefined): string {
|
|
190
|
-
return background && HEX_COLOR_PATTERN.test(background)
|
|
191
|
-
? background
|
|
192
|
-
: "#000000";
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Popover anchored to a button ref */
|
|
196
|
-
function ToolbarPopover({
|
|
197
|
-
open,
|
|
198
|
-
anchorRef,
|
|
199
|
-
onClose,
|
|
200
|
-
children,
|
|
201
|
-
width = 160,
|
|
202
|
-
align = "right",
|
|
203
|
-
}: {
|
|
204
|
-
open: boolean;
|
|
205
|
-
anchorRef: React.RefObject<HTMLButtonElement | null>;
|
|
206
|
-
onClose: () => void;
|
|
207
|
-
children: React.ReactNode;
|
|
208
|
-
width?: number;
|
|
209
|
-
align?: "left" | "right";
|
|
210
|
-
}) {
|
|
211
|
-
const menuRef = useRef<HTMLDivElement>(null);
|
|
212
|
-
|
|
213
|
-
useEffect(() => {
|
|
214
|
-
if (!open) return;
|
|
215
|
-
const handleClick = (e: MouseEvent) => {
|
|
216
|
-
if (
|
|
217
|
-
menuRef.current &&
|
|
218
|
-
!menuRef.current.contains(e.target as Node) &&
|
|
219
|
-
anchorRef.current &&
|
|
220
|
-
!anchorRef.current.contains(e.target as Node)
|
|
221
|
-
) {
|
|
222
|
-
onClose();
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
document.addEventListener("mousedown", handleClick);
|
|
226
|
-
return () => document.removeEventListener("mousedown", handleClick);
|
|
227
|
-
}, [open, onClose, anchorRef]);
|
|
228
|
-
|
|
229
|
-
if (!open || !anchorRef.current) return null;
|
|
230
|
-
const rect = anchorRef.current.getBoundingClientRect();
|
|
231
|
-
const vw = window.innerWidth;
|
|
232
|
-
let left = align === "right" ? rect.right - width : rect.left;
|
|
233
|
-
left = Math.max(8, Math.min(left, vw - width - 8));
|
|
234
|
-
|
|
235
|
-
return createPortal(
|
|
236
|
-
<div
|
|
237
|
-
ref={menuRef}
|
|
238
|
-
className="fixed rounded-lg border border-border bg-popover shadow-xl z-[200] max-h-[80vh] overflow-y-auto animate-in fade-in-0 zoom-in-95 duration-150 origin-top-left"
|
|
239
|
-
style={{ top: rect.bottom + 4, left, width: Math.min(width, vw - 16) }}
|
|
240
|
-
>
|
|
241
|
-
{children}
|
|
242
|
-
</div>,
|
|
243
|
-
document.body,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
134
|
export default function EditorToolbar({
|
|
248
135
|
deck,
|
|
249
136
|
deckId,
|
|
@@ -255,12 +142,9 @@ export default function EditorToolbar({
|
|
|
255
142
|
onToggleSidebar,
|
|
256
143
|
onGenerateImage,
|
|
257
144
|
onOpenAssetLibrary,
|
|
258
|
-
imageGenButtonRef,
|
|
259
|
-
assetsButtonRef,
|
|
260
145
|
onShowHistory,
|
|
261
146
|
historyButtonRef,
|
|
262
147
|
currentSlide,
|
|
263
|
-
onUpdateSlide,
|
|
264
148
|
activeUsers,
|
|
265
149
|
agentPresent,
|
|
266
150
|
agentActive,
|
|
@@ -282,8 +166,6 @@ export default function EditorToolbar({
|
|
|
282
166
|
onExportPdf,
|
|
283
167
|
onExportPptx,
|
|
284
168
|
onExportGoogleSlides,
|
|
285
|
-
aspectRatio,
|
|
286
|
-
onSetAspectRatio,
|
|
287
169
|
onAddEmptySlide,
|
|
288
170
|
onDuplicateCurrentSlide,
|
|
289
171
|
currentSlideId,
|
|
@@ -336,43 +218,16 @@ export default function EditorToolbar({
|
|
|
336
218
|
};
|
|
337
219
|
}, []);
|
|
338
220
|
|
|
339
|
-
const activeAspectRatio: AspectRatio = aspectRatio ?? DEFAULT_ASPECT_RATIO;
|
|
340
|
-
const isCustomBackground =
|
|
341
|
-
!!currentSlide?.background &&
|
|
342
|
-
!backgroundOptions.includes(currentSlide.background);
|
|
343
|
-
const [layoutOpen, setLayoutOpen] = useState(false);
|
|
344
|
-
const layoutRef = useRef<HTMLButtonElement>(null);
|
|
345
|
-
|
|
346
|
-
const handleLayoutSelect = (layout: SlideLayout) => {
|
|
347
|
-
if (!currentSlide || !onUpdateSlide) return;
|
|
348
|
-
if (currentSlide.layout !== layout) {
|
|
349
|
-
onUpdateSlide({ layout, content: defaultSlideContent[layout] });
|
|
350
|
-
}
|
|
351
|
-
setLayoutOpen(false);
|
|
352
|
-
};
|
|
353
|
-
const [toolsOpen, setToolsOpen] = useState(false);
|
|
354
221
|
// The contextual toolbar hosts the action cluster whenever it is on screen.
|
|
355
222
|
// That row rides on SlideEditor, which only mounts for a real slide, so an
|
|
356
223
|
// empty deck must keep this fallback or it has no way to add one.
|
|
357
224
|
const contextToolbarVisible = canEdit && Boolean(currentSlide);
|
|
358
|
-
const { undo, redo, canUndo, canRedo, getDeck } = useDecks();
|
|
359
225
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
360
226
|
const [importing, setImporting] = useState(false);
|
|
361
227
|
const { setTheme, resolvedTheme } = useTheme();
|
|
362
228
|
const [themeMounted, setThemeMounted] = useState(false);
|
|
363
229
|
useEffect(() => setThemeMounted(true), []);
|
|
364
230
|
const isDark = themeMounted ? resolvedTheme === "dark" : false;
|
|
365
|
-
// The secondary tools share an "active when something is on" indicator so
|
|
366
|
-
// the dot on the consolidated button reflects any of them.
|
|
367
|
-
const anyToolActive = Boolean(
|
|
368
|
-
animationsOpen || tweaksOpen || drawMode || pinMode,
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
const closeAll = () => {
|
|
372
|
-
setLayoutOpen(false);
|
|
373
|
-
setToolsOpen(false);
|
|
374
|
-
};
|
|
375
|
-
|
|
376
231
|
const handleImportFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
377
232
|
const file = e.target.files?.[0];
|
|
378
233
|
if (!file) return;
|
|
@@ -450,7 +305,7 @@ export default function EditorToolbar({
|
|
|
450
305
|
};
|
|
451
306
|
|
|
452
307
|
return (
|
|
453
|
-
<div className="flex h-11 shrink-0 items-center gap-1 overflow-x-auto whitespace-nowrap border-b border-border/70 bg-background/95 px-2 shadow-[0_1px_0_hsl(var(--border)/0.35)]
|
|
308
|
+
<div className="flex h-11 shrink-0 items-center gap-1 overflow-x-auto whitespace-nowrap border-b border-border/70 bg-background/95 px-2 shadow-[0_1px_0_hsl(var(--border)/0.35)] sm:px-3">
|
|
454
309
|
{/* Back button */}
|
|
455
310
|
<Tooltip>
|
|
456
311
|
<TooltipTrigger asChild>
|
|
@@ -525,407 +380,6 @@ export default function EditorToolbar({
|
|
|
525
380
|
</span>
|
|
526
381
|
)}
|
|
527
382
|
|
|
528
|
-
{/* Slide settings cog menu */}
|
|
529
|
-
{canEdit && currentSlide && onUpdateSlide && (
|
|
530
|
-
<>
|
|
531
|
-
<Tooltip>
|
|
532
|
-
<TooltipTrigger asChild>
|
|
533
|
-
<button
|
|
534
|
-
ref={layoutRef}
|
|
535
|
-
onClick={() => {
|
|
536
|
-
closeAll();
|
|
537
|
-
setLayoutOpen(!layoutOpen);
|
|
538
|
-
}}
|
|
539
|
-
className={`${TOOLBAR_ICON_BUTTON_CLASS} ${
|
|
540
|
-
layoutOpen
|
|
541
|
-
? "text-foreground/90 bg-accent"
|
|
542
|
-
: "text-muted-foreground hover:text-foreground/70 hover:bg-accent"
|
|
543
|
-
}`}
|
|
544
|
-
aria-label={t("editorToolbar.slideSettings")}
|
|
545
|
-
>
|
|
546
|
-
<IconSettings className="size-4" />
|
|
547
|
-
</button>
|
|
548
|
-
</TooltipTrigger>
|
|
549
|
-
<TooltipContent>{t("editorToolbar.slideSettings")}</TooltipContent>
|
|
550
|
-
</Tooltip>
|
|
551
|
-
<ToolbarPopover
|
|
552
|
-
open={layoutOpen}
|
|
553
|
-
anchorRef={layoutRef}
|
|
554
|
-
onClose={() => setLayoutOpen(false)}
|
|
555
|
-
width={220}
|
|
556
|
-
>
|
|
557
|
-
<div className="py-1.5">
|
|
558
|
-
{/* Layout section */}
|
|
559
|
-
<div className="px-3 py-1.5 text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
|
560
|
-
{t("editorToolbar.layout")}
|
|
561
|
-
</div>
|
|
562
|
-
{slideLayoutOptions.map((opt) => (
|
|
563
|
-
<button
|
|
564
|
-
key={opt.value}
|
|
565
|
-
onClick={() => handleLayoutSelect(opt.value)}
|
|
566
|
-
className={`flex items-center gap-2 w-full px-3 py-1.5 text-xs transition-colors ${
|
|
567
|
-
currentSlide.layout === opt.value
|
|
568
|
-
? "text-[#609FF8] bg-accent/50"
|
|
569
|
-
: "text-muted-foreground hover:text-foreground hover:bg-accent/50"
|
|
570
|
-
}`}
|
|
571
|
-
>
|
|
572
|
-
<IconLayout className="w-3 h-3" />
|
|
573
|
-
{t(opt.labelKey)}
|
|
574
|
-
</button>
|
|
575
|
-
))}
|
|
576
|
-
|
|
577
|
-
{/* Background section */}
|
|
578
|
-
<div className="mx-2 my-1.5 border-t border-border" />
|
|
579
|
-
<div className="px-3 py-1.5 text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
|
580
|
-
{t("editorToolbar.background")}
|
|
581
|
-
</div>
|
|
582
|
-
<div className="px-3 pb-2">
|
|
583
|
-
<div className="grid grid-cols-4 gap-2">
|
|
584
|
-
{backgroundOptions.map((bg, i) => (
|
|
585
|
-
<button
|
|
586
|
-
key={i}
|
|
587
|
-
onClick={() => {
|
|
588
|
-
onUpdateSlide!({ background: bg });
|
|
589
|
-
}}
|
|
590
|
-
className={`w-10 h-7 rounded-md border transition-all ${bg} ${
|
|
591
|
-
currentSlide.background === bg
|
|
592
|
-
? "border-[#609FF8] ring-1 ring-[#609FF8]/30"
|
|
593
|
-
: "border-border hover:border-foreground/20"
|
|
594
|
-
}`}
|
|
595
|
-
/>
|
|
596
|
-
))}
|
|
597
|
-
<label
|
|
598
|
-
className={`relative w-10 h-7 rounded-md border cursor-pointer overflow-hidden transition-all ${
|
|
599
|
-
isCustomBackground
|
|
600
|
-
? "border-[#609FF8] ring-1 ring-[#609FF8]/30"
|
|
601
|
-
: "border-border hover:border-foreground/20"
|
|
602
|
-
}`}
|
|
603
|
-
style={{
|
|
604
|
-
background: isCustomBackground
|
|
605
|
-
? currentSlide.background
|
|
606
|
-
: "conic-gradient(from 180deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)",
|
|
607
|
-
}}
|
|
608
|
-
title={t("editorToolbar.customColor")}
|
|
609
|
-
>
|
|
610
|
-
<input
|
|
611
|
-
type="color"
|
|
612
|
-
value={toColorInputValue(currentSlide.background)}
|
|
613
|
-
onChange={(e) => {
|
|
614
|
-
onUpdateSlide!({ background: e.target.value });
|
|
615
|
-
}}
|
|
616
|
-
className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
|
|
617
|
-
aria-label={t("editorToolbar.customColor")}
|
|
618
|
-
/>
|
|
619
|
-
</label>
|
|
620
|
-
</div>
|
|
621
|
-
</div>
|
|
622
|
-
|
|
623
|
-
{/* Image & Assets section */}
|
|
624
|
-
<div className="mx-2 my-1.5 border-t border-border" />
|
|
625
|
-
<div className="px-3 py-1.5 text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
|
626
|
-
{t("editorToolbar.media")}
|
|
627
|
-
</div>
|
|
628
|
-
<button
|
|
629
|
-
ref={imageGenButtonRef}
|
|
630
|
-
onClick={() => {
|
|
631
|
-
onGenerateImage();
|
|
632
|
-
setLayoutOpen(false);
|
|
633
|
-
}}
|
|
634
|
-
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
635
|
-
>
|
|
636
|
-
<IconPhoto className="w-3 h-3" />
|
|
637
|
-
{t("editorToolbar.generateImage")}
|
|
638
|
-
</button>
|
|
639
|
-
<button
|
|
640
|
-
ref={assetsButtonRef}
|
|
641
|
-
onClick={() => {
|
|
642
|
-
onOpenAssetLibrary();
|
|
643
|
-
setLayoutOpen(false);
|
|
644
|
-
}}
|
|
645
|
-
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
646
|
-
>
|
|
647
|
-
<IconFolderOpen className="w-3 h-3" />
|
|
648
|
-
{t("editorToolbar.assetLibrary")}
|
|
649
|
-
</button>
|
|
650
|
-
|
|
651
|
-
{/* Diagrams section */}
|
|
652
|
-
<div className="mx-2 my-1.5 border-t border-border" />
|
|
653
|
-
<div className="px-3 py-1.5 text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
|
654
|
-
{t("editorToolbar.diagrams")}
|
|
655
|
-
</div>
|
|
656
|
-
<button
|
|
657
|
-
onClick={async () => {
|
|
658
|
-
if (!onUpdateSlide || !currentSlide) return;
|
|
659
|
-
// Pin the slide this insert targets before the async
|
|
660
|
-
// import/parse below, so navigating to another slide in the
|
|
661
|
-
// meantime can't redirect where the diagram lands.
|
|
662
|
-
const targetSlideId = currentSlide.id;
|
|
663
|
-
const defaultMermaidDefinition = `graph TD
|
|
664
|
-
A[Start] --> B{Decision}
|
|
665
|
-
B -->|Yes| C[Action A]
|
|
666
|
-
B -->|No| D[Action B]
|
|
667
|
-
C --> E[End]
|
|
668
|
-
D --> E`;
|
|
669
|
-
try {
|
|
670
|
-
const { convertMermaidToExcalidraw } =
|
|
671
|
-
await import("./MermaidToExcalidrawPanel");
|
|
672
|
-
const data = await convertMermaidToExcalidraw(
|
|
673
|
-
defaultMermaidDefinition,
|
|
674
|
-
);
|
|
675
|
-
// The target slide may have been deleted while the import/
|
|
676
|
-
// parse above was in flight — patch-deck rejects the whole
|
|
677
|
-
// batch for a missing slide id, so re-check with the live
|
|
678
|
-
// deck (not the stale `currentSlide` this closure captured)
|
|
679
|
-
// and drop the result instead of enqueueing a dead op.
|
|
680
|
-
const stillExists = getDeck(deckId)?.slides.some(
|
|
681
|
-
(s) => s.id === targetSlideId,
|
|
682
|
-
);
|
|
683
|
-
if (!stillExists) return;
|
|
684
|
-
onUpdateSlide({ excalidrawData: data }, targetSlideId);
|
|
685
|
-
setLayoutOpen(false);
|
|
686
|
-
} catch (err) {
|
|
687
|
-
console.error("Insert Mermaid diagram failed:", err);
|
|
688
|
-
toast.error(t("editorToolbar.insertMermaidFailed"), {
|
|
689
|
-
description:
|
|
690
|
-
err instanceof Error ? err.message : undefined,
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}}
|
|
694
|
-
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
695
|
-
>
|
|
696
|
-
<IconSchema className="w-3 h-3" />
|
|
697
|
-
{t("editorToolbar.insertMermaidDiagram")}
|
|
698
|
-
</button>
|
|
699
|
-
<button
|
|
700
|
-
onClick={() => {
|
|
701
|
-
if (!onUpdateSlide) return;
|
|
702
|
-
onUpdateSlide({
|
|
703
|
-
excalidrawData: JSON.stringify({
|
|
704
|
-
elements: [],
|
|
705
|
-
appState: { viewBackgroundColor: "transparent" },
|
|
706
|
-
files: {},
|
|
707
|
-
}),
|
|
708
|
-
});
|
|
709
|
-
setLayoutOpen(false);
|
|
710
|
-
}}
|
|
711
|
-
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
712
|
-
>
|
|
713
|
-
<IconPencil className="w-3 h-3" />
|
|
714
|
-
{t("editorToolbar.excalidrawCanvas")}
|
|
715
|
-
</button>
|
|
716
|
-
{typeof currentSlide?.content === "string" &&
|
|
717
|
-
currentSlide.content.includes('class="mermaid"') && (
|
|
718
|
-
<button
|
|
719
|
-
onClick={async () => {
|
|
720
|
-
if (!onUpdateSlide || !currentSlide) return;
|
|
721
|
-
// Same pinning as the insert handler above.
|
|
722
|
-
const targetSlideId = currentSlide.id;
|
|
723
|
-
try {
|
|
724
|
-
const match = currentSlide.content.match(
|
|
725
|
-
/<div\s+class="mermaid"[^>]*>([\s\S]*?)<\/div>/i,
|
|
726
|
-
);
|
|
727
|
-
if (!match) return;
|
|
728
|
-
const { convertMermaidToExcalidraw } =
|
|
729
|
-
await import("./MermaidToExcalidrawPanel");
|
|
730
|
-
const data = await convertMermaidToExcalidraw(
|
|
731
|
-
match[1].trim(),
|
|
732
|
-
);
|
|
733
|
-
// Same stale-target guard as the insert handler above.
|
|
734
|
-
const stillExists = getDeck(deckId)?.slides.some(
|
|
735
|
-
(s) => s.id === targetSlideId,
|
|
736
|
-
);
|
|
737
|
-
if (!stillExists) return;
|
|
738
|
-
onUpdateSlide({ excalidrawData: data }, targetSlideId);
|
|
739
|
-
setLayoutOpen(false);
|
|
740
|
-
} catch (err) {
|
|
741
|
-
console.error("Mermaid to Excalidraw failed:", err);
|
|
742
|
-
toast.error(t("editorToolbar.convertMermaidFailed"), {
|
|
743
|
-
description:
|
|
744
|
-
err instanceof Error ? err.message : undefined,
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
}}
|
|
748
|
-
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-[hsl(var(--accent-cyan))] hover:bg-accent/50 transition-colors"
|
|
749
|
-
>
|
|
750
|
-
<IconTransform className="w-3 h-3" />
|
|
751
|
-
{t("editorToolbar.convertMermaidToExcalidraw")}
|
|
752
|
-
</button>
|
|
753
|
-
)}
|
|
754
|
-
{currentSlide?.excalidrawData && (
|
|
755
|
-
<button
|
|
756
|
-
onClick={() => {
|
|
757
|
-
if (!onUpdateSlide) return;
|
|
758
|
-
onUpdateSlide({ excalidrawData: undefined });
|
|
759
|
-
setLayoutOpen(false);
|
|
760
|
-
}}
|
|
761
|
-
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-muted-foreground hover:text-muted-foreground hover:bg-accent/50 transition-colors"
|
|
762
|
-
>
|
|
763
|
-
<IconPencil className="w-3 h-3" />
|
|
764
|
-
{t("editorToolbar.removeExcalidrawCanvas")}
|
|
765
|
-
</button>
|
|
766
|
-
)}
|
|
767
|
-
|
|
768
|
-
{/* Transitions section */}
|
|
769
|
-
<div className="mx-2 my-1.5 border-t border-border" />
|
|
770
|
-
<div className="px-3 py-1.5 text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
|
771
|
-
{t("editorToolbar.transition")}
|
|
772
|
-
</div>
|
|
773
|
-
<div className="px-3 pb-2.5 grid grid-cols-4 gap-1">
|
|
774
|
-
{(["instant", "fade", "slide", "zoom"] as const).map(
|
|
775
|
-
(transition) => {
|
|
776
|
-
const active =
|
|
777
|
-
transition === "instant"
|
|
778
|
-
? !currentSlide.transition ||
|
|
779
|
-
currentSlide.transition === "instant" ||
|
|
780
|
-
currentSlide.transition === "none"
|
|
781
|
-
: currentSlide.transition === transition;
|
|
782
|
-
return (
|
|
783
|
-
<button
|
|
784
|
-
key={transition}
|
|
785
|
-
onClick={() => onUpdateSlide!({ transition })}
|
|
786
|
-
className={`px-1.5 py-1 rounded text-[10px] font-medium capitalize border ${
|
|
787
|
-
active
|
|
788
|
-
? "bg-[#609FF8]/20 text-[#609FF8] border-[#609FF8]/30"
|
|
789
|
-
: "text-muted-foreground hover:text-foreground/70 hover:bg-accent/50 border-transparent"
|
|
790
|
-
}`}
|
|
791
|
-
>
|
|
792
|
-
{t(`editorToolbar.transition_${transition}`)}
|
|
793
|
-
</button>
|
|
794
|
-
);
|
|
795
|
-
},
|
|
796
|
-
)}
|
|
797
|
-
</div>
|
|
798
|
-
|
|
799
|
-
{/* Aspect Ratio section (deck-level) */}
|
|
800
|
-
{onSetAspectRatio && (
|
|
801
|
-
<>
|
|
802
|
-
<div className="mx-2 my-1.5 border-t border-border" />
|
|
803
|
-
<div className="px-3 py-1.5 text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
|
|
804
|
-
{t("editorToolbar.aspectRatio")}
|
|
805
|
-
</div>
|
|
806
|
-
<div className="px-3 pb-2.5 grid grid-cols-4 gap-1">
|
|
807
|
-
{ASPECT_RATIO_VALUES.map((r) => {
|
|
808
|
-
const active = activeAspectRatio === r;
|
|
809
|
-
return (
|
|
810
|
-
<Tooltip key={r}>
|
|
811
|
-
<TooltipTrigger asChild>
|
|
812
|
-
<button
|
|
813
|
-
onClick={() => onSetAspectRatio(r)}
|
|
814
|
-
className={`px-1.5 py-1 rounded text-[10px] font-medium border ${
|
|
815
|
-
active
|
|
816
|
-
? "bg-[#609FF8]/20 text-[#609FF8] border-[#609FF8]/30"
|
|
817
|
-
: "text-muted-foreground hover:text-foreground hover:bg-accent/50 border-transparent"
|
|
818
|
-
}`}
|
|
819
|
-
>
|
|
820
|
-
{r}
|
|
821
|
-
</button>
|
|
822
|
-
</TooltipTrigger>
|
|
823
|
-
<TooltipContent>
|
|
824
|
-
{t("editorToolbar.setAspectRatio", { ratio: r })}
|
|
825
|
-
</TooltipContent>
|
|
826
|
-
</Tooltip>
|
|
827
|
-
);
|
|
828
|
-
})}
|
|
829
|
-
</div>
|
|
830
|
-
</>
|
|
831
|
-
)}
|
|
832
|
-
</div>
|
|
833
|
-
</ToolbarPopover>
|
|
834
|
-
</>
|
|
835
|
-
)}
|
|
836
|
-
|
|
837
|
-
{/* Slide tools palette — animations, tweaks, draw, comment-pin all live
|
|
838
|
-
* inside one popover so the toolbar doesn't drown in icons. Hidden in
|
|
839
|
-
* view-only mode since none of these affordances apply. */}
|
|
840
|
-
{canEdit &&
|
|
841
|
-
(onToggleAnimations ||
|
|
842
|
-
onToggleTweaks ||
|
|
843
|
-
onToggleDrawMode ||
|
|
844
|
-
onTogglePinMode) && (
|
|
845
|
-
<DropdownMenu
|
|
846
|
-
open={toolsOpen}
|
|
847
|
-
onOpenChange={(open) => {
|
|
848
|
-
if (open) setLayoutOpen(false);
|
|
849
|
-
setToolsOpen(open);
|
|
850
|
-
}}
|
|
851
|
-
>
|
|
852
|
-
<Tooltip>
|
|
853
|
-
<TooltipTrigger asChild>
|
|
854
|
-
<DropdownMenuTrigger asChild>
|
|
855
|
-
<button
|
|
856
|
-
className={`${TOOLBAR_ICON_BUTTON_CLASS} relative cursor-pointer ${
|
|
857
|
-
anyToolActive || toolsOpen
|
|
858
|
-
? "bg-accent text-foreground"
|
|
859
|
-
: "text-muted-foreground hover:text-foreground/70 hover:bg-accent"
|
|
860
|
-
}`}
|
|
861
|
-
aria-label={t("editorToolbar.slideTools")}
|
|
862
|
-
>
|
|
863
|
-
<IconTool className="size-4" />
|
|
864
|
-
{anyToolActive && !toolsOpen && (
|
|
865
|
-
<span className="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-[#609FF8]" />
|
|
866
|
-
)}
|
|
867
|
-
</button>
|
|
868
|
-
</DropdownMenuTrigger>
|
|
869
|
-
</TooltipTrigger>
|
|
870
|
-
<TooltipContent>{t("editorToolbar.slideTools")}</TooltipContent>
|
|
871
|
-
</Tooltip>
|
|
872
|
-
<DropdownMenuContent align="end" className="w-52">
|
|
873
|
-
<DropdownMenuGroup>
|
|
874
|
-
{currentSlide && onToggleAnimations && (
|
|
875
|
-
<DropdownMenuItem
|
|
876
|
-
onSelect={onToggleAnimations}
|
|
877
|
-
className={
|
|
878
|
-
animationsOpen
|
|
879
|
-
? "bg-accent text-accent-foreground"
|
|
880
|
-
: undefined
|
|
881
|
-
}
|
|
882
|
-
>
|
|
883
|
-
<IconBolt className="size-4" />
|
|
884
|
-
{t("editorToolbar.elementAnimations")}
|
|
885
|
-
</DropdownMenuItem>
|
|
886
|
-
)}
|
|
887
|
-
{onToggleTweaks && (
|
|
888
|
-
<DropdownMenuItem
|
|
889
|
-
onSelect={onToggleTweaks}
|
|
890
|
-
className={
|
|
891
|
-
tweaksOpen
|
|
892
|
-
? "bg-accent text-accent-foreground"
|
|
893
|
-
: undefined
|
|
894
|
-
}
|
|
895
|
-
>
|
|
896
|
-
<IconAdjustments className="size-4" />
|
|
897
|
-
{t("editorToolbar.tweaks")}
|
|
898
|
-
</DropdownMenuItem>
|
|
899
|
-
)}
|
|
900
|
-
{onToggleDrawMode && (
|
|
901
|
-
<DropdownMenuItem
|
|
902
|
-
onSelect={onToggleDrawMode}
|
|
903
|
-
data-toolbar-draw-button
|
|
904
|
-
className={
|
|
905
|
-
drawMode ? "bg-accent text-accent-foreground" : undefined
|
|
906
|
-
}
|
|
907
|
-
>
|
|
908
|
-
<IconPencilPlus className="size-4" />
|
|
909
|
-
{t("editorToolbar.drawOnSlide")}
|
|
910
|
-
</DropdownMenuItem>
|
|
911
|
-
)}
|
|
912
|
-
{onTogglePinMode && (
|
|
913
|
-
<DropdownMenuItem
|
|
914
|
-
onSelect={onTogglePinMode}
|
|
915
|
-
data-toolbar-pin-button
|
|
916
|
-
className={
|
|
917
|
-
pinMode ? "bg-accent text-accent-foreground" : undefined
|
|
918
|
-
}
|
|
919
|
-
>
|
|
920
|
-
<IconPin className="size-4" />
|
|
921
|
-
{t("editorToolbar.pinComments")}
|
|
922
|
-
</DropdownMenuItem>
|
|
923
|
-
)}
|
|
924
|
-
</DropdownMenuGroup>
|
|
925
|
-
</DropdownMenuContent>
|
|
926
|
-
</DropdownMenu>
|
|
927
|
-
)}
|
|
928
|
-
|
|
929
383
|
{/* Save status — subtle "Saving…" / "Saved" / offline pill. Renders
|
|
930
384
|
nothing when idle. Only meaningful for editors. */}
|
|
931
385
|
{canEdit && (
|
|
@@ -941,47 +395,11 @@ export default function EditorToolbar({
|
|
|
941
395
|
activeUsers={activeUsers ?? []}
|
|
942
396
|
agentPresent={agentPresent}
|
|
943
397
|
agentActive={agentActive}
|
|
398
|
+
showAgentEditingDot={false}
|
|
944
399
|
currentUserEmail={currentUserEmail}
|
|
945
400
|
className="flex-shrink-0 mr-0.5"
|
|
946
401
|
/>
|
|
947
402
|
|
|
948
|
-
{/* Comments toggle */}
|
|
949
|
-
{onToggleComments && (
|
|
950
|
-
<Tooltip>
|
|
951
|
-
<TooltipTrigger asChild>
|
|
952
|
-
<button
|
|
953
|
-
onClick={onToggleComments}
|
|
954
|
-
className={`${TOOLBAR_ICON_BUTTON_CLASS} relative ${
|
|
955
|
-
commentsOpen
|
|
956
|
-
? "text-foreground bg-accent"
|
|
957
|
-
: "text-muted-foreground hover:text-foreground/70 hover:bg-accent"
|
|
958
|
-
}`}
|
|
959
|
-
aria-label={t("editorToolbar.comments")}
|
|
960
|
-
>
|
|
961
|
-
<IconMessage className="size-4" />
|
|
962
|
-
{unresolvedCommentCount > 0 && (
|
|
963
|
-
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-[#609FF8] text-[8px] font-bold text-black flex items-center justify-center leading-none">
|
|
964
|
-
{unresolvedCommentCount > 9 ? "9+" : unresolvedCommentCount}
|
|
965
|
-
</span>
|
|
966
|
-
)}
|
|
967
|
-
</button>
|
|
968
|
-
</TooltipTrigger>
|
|
969
|
-
<TooltipContent>{t("editorToolbar.comments")}</TooltipContent>
|
|
970
|
-
</Tooltip>
|
|
971
|
-
)}
|
|
972
|
-
|
|
973
|
-
{/* Export / Share menu (export, duplicate, share) */}
|
|
974
|
-
<div className="flex-shrink-0">
|
|
975
|
-
<ExportMenu
|
|
976
|
-
deckId={deckId}
|
|
977
|
-
deckTitle={deckTitle}
|
|
978
|
-
onDuplicate={onDuplicateDeck ?? (() => {})}
|
|
979
|
-
onExportPdf={onExportPdf ?? (() => {})}
|
|
980
|
-
onExportPptx={onExportPptx ?? (() => {})}
|
|
981
|
-
onExportGoogleSlides={onExportGoogleSlides}
|
|
982
|
-
/>
|
|
983
|
-
</div>
|
|
984
|
-
|
|
985
403
|
{/* Framework share (ownership, per-user/org grants, visibility) */}
|
|
986
404
|
<div className="flex-shrink-0">
|
|
987
405
|
<ShareButton
|
|
@@ -1018,7 +436,7 @@ export default function EditorToolbar({
|
|
|
1018
436
|
</div>
|
|
1019
437
|
{/* Present button — matches Share trigger height (h-9) */}
|
|
1020
438
|
<Link
|
|
1021
|
-
to={`/deck/${deckId}/present`}
|
|
439
|
+
to={`/deck/${deckId}/present?slide=${currentSlideIndex + 1}`}
|
|
1022
440
|
className="inline-flex h-9 flex-shrink-0 items-center justify-center gap-1.5 rounded-md border border-border bg-primary px-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
|
1023
441
|
>
|
|
1024
442
|
<IconPlayerPlay className="w-3.5 h-3.5" />
|
|
@@ -1034,7 +452,7 @@ export default function EditorToolbar({
|
|
|
1034
452
|
className="hidden"
|
|
1035
453
|
/>
|
|
1036
454
|
|
|
1037
|
-
{/*
|
|
455
|
+
{/* Consolidated editor menu */}
|
|
1038
456
|
<DropdownMenu>
|
|
1039
457
|
<Tooltip>
|
|
1040
458
|
<TooltipTrigger asChild>
|
|
@@ -1050,53 +468,152 @@ export default function EditorToolbar({
|
|
|
1050
468
|
</TooltipTrigger>
|
|
1051
469
|
<TooltipContent>{t("editorToolbar.more")}</TooltipContent>
|
|
1052
470
|
</Tooltip>
|
|
1053
|
-
<DropdownMenuContent
|
|
471
|
+
<DropdownMenuContent
|
|
472
|
+
align="end"
|
|
473
|
+
className="max-h-[min(80vh,32rem)] w-64 overflow-y-auto"
|
|
474
|
+
>
|
|
1054
475
|
{canEdit && (
|
|
1055
476
|
<>
|
|
477
|
+
<DropdownMenuLabel>{t("editorToolbar.media")}</DropdownMenuLabel>
|
|
478
|
+
<DropdownMenuGroup>
|
|
479
|
+
<DropdownMenuItem onSelect={onGenerateImage}>
|
|
480
|
+
<IconPhoto className="size-4" />
|
|
481
|
+
{t("editorToolbar.generateImage")}
|
|
482
|
+
</DropdownMenuItem>
|
|
483
|
+
<DropdownMenuItem onSelect={onOpenAssetLibrary}>
|
|
484
|
+
<IconFolderOpen className="size-4" />
|
|
485
|
+
{t("editorToolbar.assetLibrary")}
|
|
486
|
+
</DropdownMenuItem>
|
|
487
|
+
</DropdownMenuGroup>
|
|
488
|
+
</>
|
|
489
|
+
)}
|
|
490
|
+
|
|
491
|
+
{canEdit &&
|
|
492
|
+
(onToggleAnimations ||
|
|
493
|
+
onToggleTweaks ||
|
|
494
|
+
onToggleDrawMode ||
|
|
495
|
+
onTogglePinMode) && (
|
|
496
|
+
<>
|
|
497
|
+
<DropdownMenuSeparator />
|
|
498
|
+
<DropdownMenuLabel>
|
|
499
|
+
{t("editorToolbar.slideTools")}
|
|
500
|
+
</DropdownMenuLabel>
|
|
501
|
+
<DropdownMenuGroup>
|
|
502
|
+
{currentSlide && onToggleAnimations && (
|
|
503
|
+
<DropdownMenuItem
|
|
504
|
+
onSelect={onToggleAnimations}
|
|
505
|
+
className={
|
|
506
|
+
animationsOpen
|
|
507
|
+
? "bg-accent text-accent-foreground"
|
|
508
|
+
: undefined
|
|
509
|
+
}
|
|
510
|
+
>
|
|
511
|
+
<IconBolt className="size-4" />
|
|
512
|
+
{t("editorToolbar.elementAnimations")}
|
|
513
|
+
</DropdownMenuItem>
|
|
514
|
+
)}
|
|
515
|
+
{onToggleTweaks && (
|
|
516
|
+
<DropdownMenuItem
|
|
517
|
+
onSelect={onToggleTweaks}
|
|
518
|
+
className={
|
|
519
|
+
tweaksOpen
|
|
520
|
+
? "bg-accent text-accent-foreground"
|
|
521
|
+
: undefined
|
|
522
|
+
}
|
|
523
|
+
>
|
|
524
|
+
<IconAdjustments className="size-4" />
|
|
525
|
+
{t("editorToolbar.tweaks")}
|
|
526
|
+
</DropdownMenuItem>
|
|
527
|
+
)}
|
|
528
|
+
{onToggleDrawMode && (
|
|
529
|
+
<DropdownMenuItem
|
|
530
|
+
onSelect={onToggleDrawMode}
|
|
531
|
+
data-toolbar-draw-button
|
|
532
|
+
className={
|
|
533
|
+
drawMode
|
|
534
|
+
? "bg-accent text-accent-foreground"
|
|
535
|
+
: undefined
|
|
536
|
+
}
|
|
537
|
+
>
|
|
538
|
+
<IconPencilPlus className="size-4" />
|
|
539
|
+
{t("editorToolbar.drawOnSlide")}
|
|
540
|
+
</DropdownMenuItem>
|
|
541
|
+
)}
|
|
542
|
+
{onTogglePinMode && (
|
|
543
|
+
<DropdownMenuItem
|
|
544
|
+
onSelect={onTogglePinMode}
|
|
545
|
+
data-toolbar-pin-button
|
|
546
|
+
className={
|
|
547
|
+
pinMode ? "bg-accent text-accent-foreground" : undefined
|
|
548
|
+
}
|
|
549
|
+
>
|
|
550
|
+
<IconPin className="size-4" />
|
|
551
|
+
{t("editorToolbar.pinComments")}
|
|
552
|
+
</DropdownMenuItem>
|
|
553
|
+
)}
|
|
554
|
+
</DropdownMenuGroup>
|
|
555
|
+
</>
|
|
556
|
+
)}
|
|
557
|
+
|
|
558
|
+
{onToggleComments && (
|
|
559
|
+
<>
|
|
560
|
+
<DropdownMenuSeparator />
|
|
561
|
+
<DropdownMenuLabel>
|
|
562
|
+
{t("editorToolbar.comments")}
|
|
563
|
+
</DropdownMenuLabel>
|
|
1056
564
|
<DropdownMenuItem
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
{t("editorToolbar.undoWithShortcut", {
|
|
1062
|
-
shortcut: shortcutLabel("Cmd+Z"),
|
|
1063
|
-
})}
|
|
1064
|
-
</DropdownMenuItem>
|
|
1065
|
-
<DropdownMenuItem
|
|
1066
|
-
disabled={!canRedo}
|
|
1067
|
-
onSelect={() => commitActiveEditThenRun(redo)}
|
|
565
|
+
onSelect={onToggleComments}
|
|
566
|
+
className={
|
|
567
|
+
commentsOpen ? "bg-accent text-accent-foreground" : undefined
|
|
568
|
+
}
|
|
1068
569
|
>
|
|
1069
|
-
<
|
|
1070
|
-
{t("editorToolbar.
|
|
1071
|
-
|
|
1072
|
-
|
|
570
|
+
<IconMessage className="size-4" />
|
|
571
|
+
{t("editorToolbar.comments")}
|
|
572
|
+
{unresolvedCommentCount > 0 && (
|
|
573
|
+
<span className="ml-auto rounded-full bg-primary px-1.5 text-[10px] font-semibold text-primary-foreground">
|
|
574
|
+
{unresolvedCommentCount > 9 ? "9+" : unresolvedCommentCount}
|
|
575
|
+
</span>
|
|
576
|
+
)}
|
|
1073
577
|
</DropdownMenuItem>
|
|
1074
|
-
<DropdownMenuSeparator />
|
|
1075
578
|
</>
|
|
1076
579
|
)}
|
|
580
|
+
|
|
581
|
+
<DropdownMenuSeparator />
|
|
582
|
+
<ExportMenu
|
|
583
|
+
inline
|
|
584
|
+
deckId={deckId}
|
|
585
|
+
deckTitle={deckTitle}
|
|
586
|
+
onDuplicate={onDuplicateDeck ?? (() => {})}
|
|
587
|
+
onExportPdf={onExportPdf ?? (() => {})}
|
|
588
|
+
onExportPptx={onExportPptx ?? (() => {})}
|
|
589
|
+
onExportGoogleSlides={onExportGoogleSlides}
|
|
590
|
+
/>
|
|
591
|
+
<DropdownMenuSeparator />
|
|
1077
592
|
<DropdownMenuItem
|
|
1078
593
|
disabled={importing}
|
|
1079
594
|
onSelect={() => fileInputRef.current?.click()}
|
|
1080
595
|
>
|
|
1081
596
|
{importing ? (
|
|
1082
|
-
<IconLoader2 className="
|
|
597
|
+
<IconLoader2 className="size-4 animate-spin" />
|
|
1083
598
|
) : (
|
|
1084
|
-
<IconDownload className="
|
|
599
|
+
<IconDownload className="size-4" />
|
|
1085
600
|
)}
|
|
1086
601
|
{importing
|
|
1087
602
|
? t("editorToolbar.importing")
|
|
1088
603
|
: t("editorToolbar.importFile")}
|
|
1089
604
|
</DropdownMenuItem>
|
|
1090
|
-
<DropdownMenuItem
|
|
1091
|
-
<IconHistory className="
|
|
605
|
+
<DropdownMenuItem onSelect={onShowHistory}>
|
|
606
|
+
<IconHistory className="size-4" />
|
|
1092
607
|
{t("editorToolbar.savedVersions")}
|
|
1093
608
|
</DropdownMenuItem>
|
|
1094
609
|
<DropdownMenuSeparator />
|
|
1095
|
-
<DropdownMenuItem
|
|
610
|
+
<DropdownMenuItem
|
|
611
|
+
onSelect={() => setTheme(isDark ? "light" : "dark")}
|
|
612
|
+
>
|
|
1096
613
|
{isDark ? (
|
|
1097
|
-
<IconSun className="
|
|
614
|
+
<IconSun className="size-4" />
|
|
1098
615
|
) : (
|
|
1099
|
-
<IconMoon className="
|
|
616
|
+
<IconMoon className="size-4" />
|
|
1100
617
|
)}
|
|
1101
618
|
{isDark
|
|
1102
619
|
? t("editorToolbar.lightTheme")
|
|
@@ -1104,7 +621,6 @@ export default function EditorToolbar({
|
|
|
1104
621
|
</DropdownMenuItem>
|
|
1105
622
|
</DropdownMenuContent>
|
|
1106
623
|
</DropdownMenu>
|
|
1107
|
-
|
|
1108
624
|
<RunsTray pollMs={0} />
|
|
1109
625
|
<AgentToggleButton />
|
|
1110
626
|
</div>
|