@agent-native/core 0.137.0 → 0.137.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/app/components/AgentCompletionSound.tsx +105 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +44 -35
- package/corpus/templates/analytics/app/i18n-data.ts +38 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +53 -1
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +6 -0
- package/corpus/templates/analytics/changelog/2026-08-04-choose-whether-analytics-plays-a-bell-when-the-agent-finishe.md +6 -0
- package/corpus/templates/analytics/shared/analytics-user-prefs.ts +2 -0
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +258 -0
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +130 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +25 -337
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +73 -53
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +321 -0
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +32 -11
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +804 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +101 -45
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +7 -4
- package/corpus/templates/slides/app/components/editor/bullet-editing.ts +11 -3
- package/corpus/templates/slides/app/components/editor/commit-active-edit.ts +21 -0
- package/corpus/templates/slides/app/components/editor/list-editing.ts +219 -0
- package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +0 -3
- package/corpus/templates/slides/app/components/editor/slide-style.ts +199 -0
- package/corpus/templates/slides/app/global.css +14 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -1
- package/corpus/templates/slides/app/lib/recent-references.ts +82 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +51 -22
- package/corpus/templates/slides/app/pages/Index.tsx +236 -181
- package/corpus/templates/slides/changelog/2026-08-01-add-slide-moved-to-the-toolbar-and-the-slide-rail-is-now-mor.md +9 -0
- package/corpus/templates/slides/changelog/2026-08-01-add-slide-undo-redo-and-the-text-tool-now-lead-the-slide-too.md +9 -0
- package/corpus/templates/slides/changelog/2026-08-01-slide-styling-now-appears-in-a-contextual-toolbar-above-the-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-01-the-slide-style-side-panel-is-retired-all-styling-now-lives-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-03-the-slide-toolbar-is-denser-swatch-only-colors-dropdowns-for.md +10 -0
- package/corpus/templates/slides/changelog/2026-08-04-bullet-and-numbered-list-buttons-in-the-slide-toolbar-conver.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-04-italic-and-underline-are-now-one-click-away-in-the-slide-too.md +6 -0
- package/dist/client/DefaultSpinner.d.ts +0 -12
- package/dist/client/DefaultSpinner.js +22 -2
- package/dist/client/onboarding/use-onboarding.js +3 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
- package/package.json +2 -2
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +0 -763
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { DesignSystemData } from "@shared/api";
|
|
2
|
+
|
|
3
|
+
import type { InlineTextStyleKey } from "./rich-text-selection";
|
|
4
|
+
|
|
5
|
+
export interface SlideStyleSnapshot {
|
|
6
|
+
/** Omitted snapshots are existing object snapshots for backward compatibility. */
|
|
7
|
+
mode?: "object";
|
|
8
|
+
selector: string;
|
|
9
|
+
label: string;
|
|
10
|
+
tagName: string;
|
|
11
|
+
textPreview: string;
|
|
12
|
+
isText: boolean;
|
|
13
|
+
isImage: boolean;
|
|
14
|
+
isAbsolute: boolean;
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
rotation: number;
|
|
20
|
+
slideWidth: number;
|
|
21
|
+
slideHeight: number;
|
|
22
|
+
color: string;
|
|
23
|
+
backgroundColor: string;
|
|
24
|
+
fontSize: number;
|
|
25
|
+
fontWeight: string;
|
|
26
|
+
fontStyle: string;
|
|
27
|
+
textDecoration: string;
|
|
28
|
+
lineHeight: number;
|
|
29
|
+
textAlign: string;
|
|
30
|
+
opacity: number;
|
|
31
|
+
borderRadius: number;
|
|
32
|
+
borderWidth: number;
|
|
33
|
+
borderColor: string;
|
|
34
|
+
paddingX: number;
|
|
35
|
+
paddingY: number;
|
|
36
|
+
zIndex: number;
|
|
37
|
+
listKind: "bullet" | "ordered" | null;
|
|
38
|
+
textStyleScope?: "block" | "selection";
|
|
39
|
+
mixedTextStyles?: InlineTextStyleKey[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SlideStylePatch = Partial<{
|
|
43
|
+
color: string;
|
|
44
|
+
backgroundColor: string;
|
|
45
|
+
fontSize: string;
|
|
46
|
+
fontWeight: string;
|
|
47
|
+
fontStyle: string;
|
|
48
|
+
textDecoration: string;
|
|
49
|
+
lineHeight: string;
|
|
50
|
+
textAlign: string;
|
|
51
|
+
opacity: string;
|
|
52
|
+
borderRadius: string;
|
|
53
|
+
borderWidth: string;
|
|
54
|
+
borderColor: string;
|
|
55
|
+
paddingLeft: string;
|
|
56
|
+
paddingRight: string;
|
|
57
|
+
paddingTop: string;
|
|
58
|
+
paddingBottom: string;
|
|
59
|
+
left: string;
|
|
60
|
+
top: string;
|
|
61
|
+
width: string;
|
|
62
|
+
height: string;
|
|
63
|
+
transform: string;
|
|
64
|
+
zIndex: string;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export function tokenPalette(
|
|
68
|
+
designSystem: DesignSystemData | undefined,
|
|
69
|
+
t: (key: string) => string,
|
|
70
|
+
) {
|
|
71
|
+
const colors = designSystem?.colors;
|
|
72
|
+
const base = colors
|
|
73
|
+
? [
|
|
74
|
+
{
|
|
75
|
+
label: t("styleInspector.primary"),
|
|
76
|
+
value: colors.primary,
|
|
77
|
+
color: colors.primary,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: t("styleInspector.secondary"),
|
|
81
|
+
value: colors.secondary,
|
|
82
|
+
color: colors.secondary,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: t("styleInspector.accent"),
|
|
86
|
+
value: colors.accent,
|
|
87
|
+
color: colors.accent,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
label: t("styleInspector.surface"),
|
|
91
|
+
value: colors.surface,
|
|
92
|
+
color: colors.surface,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: t("styleInspector.background"),
|
|
96
|
+
value: colors.background,
|
|
97
|
+
color: colors.background,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: t("styleInspector.text"),
|
|
101
|
+
value: colors.text,
|
|
102
|
+
color: colors.text,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: t("styleInspector.muted"),
|
|
106
|
+
value: colors.textMuted,
|
|
107
|
+
color: colors.textMuted,
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
: [];
|
|
111
|
+
|
|
112
|
+
// Fixed swatches offered when a deck has no design system. These are
|
|
113
|
+
// document colors the user paints onto slide content, so they must stay
|
|
114
|
+
// literal — theming them would repaint finished decks on a theme switch.
|
|
115
|
+
const presets: Array<[key: string, hex: string]> = [
|
|
116
|
+
["white", "#ffffff"], // guard:allow-raw-color
|
|
117
|
+
["black", "#000000"], // guard:allow-raw-color
|
|
118
|
+
["slate", "#1f2937"], // guard:allow-raw-color
|
|
119
|
+
["blue", "#609ff8"], // guard:allow-raw-color
|
|
120
|
+
["cyan", "#22d3ee"], // guard:allow-raw-color
|
|
121
|
+
["emerald", "#34d399"], // guard:allow-raw-color
|
|
122
|
+
["amber", "#fbbf24"], // guard:allow-raw-color
|
|
123
|
+
["rose", "#fb7185"], // guard:allow-raw-color
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
return [
|
|
127
|
+
...base,
|
|
128
|
+
...presets.map(([key, hex]) => ({
|
|
129
|
+
label: t(`styleInspector.${key}`),
|
|
130
|
+
value: hex,
|
|
131
|
+
color: hex,
|
|
132
|
+
})),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// `slide.background` holds either a raw CSS value or a Tailwind arbitrary
|
|
137
|
+
// class (`bg-[...]`), which SlideRenderer applies as a class rather than
|
|
138
|
+
// an inline style. The picker only speaks CSS colors, so unwrap the arbitrary
|
|
139
|
+
// form and report anything else (named utilities, gradients) as unreadable
|
|
140
|
+
// rather than guessing a hex the slide is not actually using.
|
|
141
|
+
export function backgroundCssValue(
|
|
142
|
+
background: string | undefined,
|
|
143
|
+
): string | null {
|
|
144
|
+
// guard:allow-raw-color — mirrors SlideRenderer's own default slide fill
|
|
145
|
+
if (!background) return "#000000";
|
|
146
|
+
const arbitrary = background.match(/^bg-\[(.+)\]$/);
|
|
147
|
+
if (arbitrary) return arbitrary[1].replace(/_/g, " ");
|
|
148
|
+
return background.startsWith("bg-") ? null : background;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function formatValue(value: number) {
|
|
152
|
+
return Number.isInteger(value)
|
|
153
|
+
? String(value)
|
|
154
|
+
: String(Number(value.toFixed(2)));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function rotationTransform(rotation: number) {
|
|
158
|
+
return `rotate(${formatValue(rotation)}deg)`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function resolveHorizontalAlignment(snapshot: SlideStyleSnapshot) {
|
|
162
|
+
if (snapshot.x <= 0) return "left";
|
|
163
|
+
const centered = (snapshot.slideWidth - snapshot.width) / 2;
|
|
164
|
+
return Math.abs(snapshot.x - centered) < 1 ? "center" : "right";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function resolveVerticalAlignment(snapshot: SlideStyleSnapshot) {
|
|
168
|
+
if (snapshot.y <= 0) return "top";
|
|
169
|
+
const centered = (snapshot.slideHeight - snapshot.height) / 2;
|
|
170
|
+
return Math.abs(snapshot.y - centered) < 1 ? "middle" : "bottom";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function horizontalAlignPatch(
|
|
174
|
+
snapshot: SlideStyleSnapshot,
|
|
175
|
+
alignment: string,
|
|
176
|
+
): SlideStylePatch {
|
|
177
|
+
const available = Math.max(0, snapshot.slideWidth - snapshot.width);
|
|
178
|
+
const x =
|
|
179
|
+
alignment === "left"
|
|
180
|
+
? 0
|
|
181
|
+
: alignment === "center"
|
|
182
|
+
? available / 2
|
|
183
|
+
: available;
|
|
184
|
+
return { left: `${formatValue(x)}px` };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function verticalAlignPatch(
|
|
188
|
+
snapshot: SlideStyleSnapshot,
|
|
189
|
+
alignment: string,
|
|
190
|
+
): SlideStylePatch {
|
|
191
|
+
const available = Math.max(0, snapshot.slideHeight - snapshot.height);
|
|
192
|
+
const y =
|
|
193
|
+
alignment === "top"
|
|
194
|
+
? 0
|
|
195
|
+
: alignment === "middle"
|
|
196
|
+
? available / 2
|
|
197
|
+
: available;
|
|
198
|
+
return { top: `${formatValue(y)}px` };
|
|
199
|
+
}
|
|
@@ -109,9 +109,10 @@ button[title="Open agent sidebar"] {
|
|
|
109
109
|
--accent-cyan: 193 82% 31%;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/* Slides keeps the shared visual inspector behavior, but the dock
|
|
112
|
+
/* Slides keeps the shared visual inspector behavior, but the dock and context toolbar need the
|
|
113
113
|
flatter, filled-control treatment used by the slide editor. */
|
|
114
|
-
.slide-style-inspector
|
|
114
|
+
.slide-style-inspector,
|
|
115
|
+
.slide-context-toolbar {
|
|
115
116
|
--slides-inspector-control-background: hsl(var(--muted) / 0.82);
|
|
116
117
|
--slides-inspector-divider: hsl(var(--border) / 0.7);
|
|
117
118
|
--design-editor-control-bg: var(--slides-inspector-control-background);
|
|
@@ -161,11 +162,21 @@ button[title="Open agent sidebar"] {
|
|
|
161
162
|
box-shadow: none;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/* The toolbar sits on a muted surface, so its inputs need the opposite
|
|
166
|
+
treatment to stay readable: a light box rather than a darker fill. */
|
|
167
|
+
.slide-context-toolbar input {
|
|
168
|
+
border: 1px solid var(--slides-inspector-divider);
|
|
169
|
+
border-radius: 0.25rem;
|
|
170
|
+
background: hsl(var(--background));
|
|
171
|
+
box-shadow: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
164
174
|
.slide-style-inspector input:focus {
|
|
165
175
|
border: 0;
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
.slide-style-inspector .slides-inspector-segment
|
|
178
|
+
.slide-style-inspector .slides-inspector-segment,
|
|
179
|
+
.slide-context-toolbar .slides-inspector-segment {
|
|
169
180
|
min-height: 1.5rem;
|
|
170
181
|
border: 0;
|
|
171
182
|
border-radius: 0.125rem;
|
|
@@ -404,6 +404,14 @@ const messages = {
|
|
|
404
404
|
cornerRadius: "Corner radius",
|
|
405
405
|
strokeWeight: "Weight",
|
|
406
406
|
typography: "Typography",
|
|
407
|
+
weight: "Weight",
|
|
408
|
+
italic: "Italic",
|
|
409
|
+
underline: "Underline",
|
|
410
|
+
bulletList: "Bullet list",
|
|
411
|
+
numberedList: "Numbered list",
|
|
412
|
+
align: "Align",
|
|
413
|
+
decreaseSize: "Decrease font size",
|
|
414
|
+
increaseSize: "Increase font size",
|
|
407
415
|
mixed: "Mixed",
|
|
408
416
|
textColor: "Text color",
|
|
409
417
|
primary: "Primary",
|
|
@@ -501,7 +509,6 @@ const messages = {
|
|
|
501
509
|
noAi: "no AI",
|
|
502
510
|
duplicateCurrentSlide: "Duplicate current slide",
|
|
503
511
|
promptPlaceholder: "Describe the slides you want...",
|
|
504
|
-
slides: "Slides",
|
|
505
512
|
},
|
|
506
513
|
presentation: {
|
|
507
514
|
loadFailed: "Could not load this presentation.",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const RECENT_REFERENCES_STORAGE_KEY = "slides:recent-references";
|
|
2
|
+
|
|
3
|
+
export type RecentReferenceKind = "deck" | "design-system";
|
|
4
|
+
|
|
5
|
+
export interface RecentReference {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: RecentReferenceKind;
|
|
8
|
+
lastUsedAt: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RecentReferencesResult {
|
|
12
|
+
items: RecentReference[];
|
|
13
|
+
readable: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MAX_RECENT_REFERENCES = 8;
|
|
17
|
+
|
|
18
|
+
function isRecentReference(value: unknown): value is RecentReference {
|
|
19
|
+
if (!value || typeof value !== "object") return false;
|
|
20
|
+
const candidate = value as Partial<RecentReference>;
|
|
21
|
+
return (
|
|
22
|
+
typeof candidate.id === "string" &&
|
|
23
|
+
candidate.id.length > 0 &&
|
|
24
|
+
(candidate.kind === "deck" || candidate.kind === "design-system") &&
|
|
25
|
+
typeof candidate.lastUsedAt === "number" &&
|
|
26
|
+
Number.isFinite(candidate.lastUsedAt)
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function readRecentReferences(
|
|
31
|
+
storage: Pick<Storage, "getItem"> | null | undefined = typeof window ===
|
|
32
|
+
"undefined"
|
|
33
|
+
? null
|
|
34
|
+
: window.localStorage,
|
|
35
|
+
): RecentReferencesResult {
|
|
36
|
+
if (!storage) return { items: [], readable: true };
|
|
37
|
+
|
|
38
|
+
let raw: string | null;
|
|
39
|
+
try {
|
|
40
|
+
raw = storage.getItem(RECENT_REFERENCES_STORAGE_KEY);
|
|
41
|
+
} catch {
|
|
42
|
+
return { items: [], readable: false };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (raw === null) return { items: [], readable: true };
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const parsed: unknown = JSON.parse(raw);
|
|
49
|
+
if (!Array.isArray(parsed)) return { items: [], readable: false };
|
|
50
|
+
return {
|
|
51
|
+
items: parsed.filter(isRecentReference).slice(0, MAX_RECENT_REFERENCES),
|
|
52
|
+
readable: true,
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return { items: [], readable: false };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function rememberRecentReference(
|
|
60
|
+
reference: Omit<RecentReference, "lastUsedAt">,
|
|
61
|
+
storage:
|
|
62
|
+
| Pick<Storage, "getItem" | "setItem">
|
|
63
|
+
| null
|
|
64
|
+
| undefined = typeof window === "undefined" ? null : window.localStorage,
|
|
65
|
+
): RecentReferencesResult {
|
|
66
|
+
const current = readRecentReferences(storage);
|
|
67
|
+
if (!storage || !current.readable) return current;
|
|
68
|
+
|
|
69
|
+
const next: RecentReference[] = [
|
|
70
|
+
{ ...reference, lastUsedAt: Date.now() },
|
|
71
|
+
...current.items.filter(
|
|
72
|
+
(item) => !(item.id === reference.id && item.kind === reference.kind),
|
|
73
|
+
),
|
|
74
|
+
].slice(0, MAX_RECENT_REFERENCES);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
storage.setItem(RECENT_REFERENCES_STORAGE_KEY, JSON.stringify(next));
|
|
78
|
+
return { items: next, readable: true };
|
|
79
|
+
} catch {
|
|
80
|
+
return { items: current.items, readable: false };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -31,6 +31,7 @@ import { SlideCommentsPanel } from "@/components/comments/SlideCommentsPanel";
|
|
|
31
31
|
import { AnimationsPanel } from "@/components/editor/AnimationsPanel";
|
|
32
32
|
import AssetLibraryPanel from "@/components/editor/AssetLibraryPanel";
|
|
33
33
|
import { DeckEditorSkeleton } from "@/components/editor/DeckEditorSkeleton";
|
|
34
|
+
import { EditorActionCluster } from "@/components/editor/EditorActionCluster";
|
|
34
35
|
import EditorSidebar from "@/components/editor/EditorSidebar";
|
|
35
36
|
import EditorToolbar from "@/components/editor/EditorToolbar";
|
|
36
37
|
import GeneratingOverlay from "@/components/editor/GeneratingOverlay";
|
|
@@ -78,7 +79,7 @@ import { TAB_ID } from "@/lib/tab-id";
|
|
|
78
79
|
import { shouldActivateTextTool } from "@/lib/text-tool-shortcut";
|
|
79
80
|
import { shortcutLabel } from "@/lib/utils";
|
|
80
81
|
|
|
81
|
-
type EditorSidePanel = "
|
|
82
|
+
type EditorSidePanel = "comments" | null;
|
|
82
83
|
|
|
83
84
|
function MissingDeckAccessPane({
|
|
84
85
|
hasTeamJoinOption,
|
|
@@ -184,6 +185,8 @@ export default function DeckEditor() {
|
|
|
184
185
|
const [sidebarOpen, setSidebarOpen] = useState(
|
|
185
186
|
() => typeof window !== "undefined" && window.innerWidth >= 768,
|
|
186
187
|
);
|
|
188
|
+
const [contextToolbarSlot, setContextToolbarSlot] =
|
|
189
|
+
useState<HTMLDivElement | null>(null);
|
|
187
190
|
const [retryingMissingDeck, setRetryingMissingDeck] = useState(false);
|
|
188
191
|
const [checkedDeckAccessKey, setCheckedDeckAccessKey] = useState<
|
|
189
192
|
string | null
|
|
@@ -285,7 +288,6 @@ export default function DeckEditor() {
|
|
|
285
288
|
});
|
|
286
289
|
const { designSystem } = useDeckDesignSystem(deck?.designSystemId);
|
|
287
290
|
const commentsOpen = sidePanel === "comments";
|
|
288
|
-
const styleOpen = sidePanel === "style";
|
|
289
291
|
|
|
290
292
|
const {
|
|
291
293
|
questions: questionFlowQuestions,
|
|
@@ -296,6 +298,9 @@ export default function DeckEditor() {
|
|
|
296
298
|
handleSubmit: handleQuestionSubmit,
|
|
297
299
|
handleSkip: handleQuestionSkip,
|
|
298
300
|
} = useGuidedQuestionFlow({
|
|
301
|
+
stateKey: "guided-questions",
|
|
302
|
+
browserTabId: TAB_ID,
|
|
303
|
+
queryKey: ["guided-questions"],
|
|
299
304
|
submitMessage: "Here are my answers — go ahead and create the slides.",
|
|
300
305
|
skipMessage:
|
|
301
306
|
"Skip the questions — just go ahead and create the slides with your best judgment.",
|
|
@@ -327,6 +332,12 @@ export default function DeckEditor() {
|
|
|
327
332
|
if (!generatingSlideVisible) setGeneratingSlideSelected(false);
|
|
328
333
|
}, [generatingSlideVisible]);
|
|
329
334
|
|
|
335
|
+
// The add-slide request is finished once the agent stops generating, so the
|
|
336
|
+
// rail's placeholder must not outlive it.
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (!generating) setAddSlideGenerating(false);
|
|
339
|
+
}, [generating]);
|
|
340
|
+
|
|
330
341
|
const previousSlideCountRef = useRef(slideCount);
|
|
331
342
|
useEffect(() => {
|
|
332
343
|
if (previousSlideCountRef.current === slideCount) return;
|
|
@@ -938,6 +949,13 @@ export default function DeckEditor() {
|
|
|
938
949
|
? `Current slide: ${currentSlide.id} (index ${currentIndex >= 0 ? currentIndex : 0}). Deck: ${id}.`
|
|
939
950
|
: `Deck: ${id}.`;
|
|
940
951
|
|
|
952
|
+
const handleAddEmptySlide = () => {
|
|
953
|
+
const activeIdx = deck.slides.findIndex((s) => s.id === activeSlideId);
|
|
954
|
+
setActiveSlideId(
|
|
955
|
+
addSlide(id, "blank", activeIdx >= 0 ? activeIdx : undefined),
|
|
956
|
+
);
|
|
957
|
+
};
|
|
958
|
+
|
|
941
959
|
return (
|
|
942
960
|
<div
|
|
943
961
|
className="flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background"
|
|
@@ -975,10 +993,6 @@ export default function DeckEditor() {
|
|
|
975
993
|
onToggleComments={() =>
|
|
976
994
|
setSidePanel((panel) => (panel === "comments" ? null : "comments"))
|
|
977
995
|
}
|
|
978
|
-
styleOpen={styleOpen}
|
|
979
|
-
onToggleStyle={() =>
|
|
980
|
-
setSidePanel((panel) => (panel === "style" ? null : "style"))
|
|
981
|
-
}
|
|
982
996
|
unresolvedCommentCount={unresolvedCommentCount}
|
|
983
997
|
currentUserEmail={session?.email}
|
|
984
998
|
animationsOpen={animationsOpen}
|
|
@@ -1049,8 +1063,19 @@ export default function DeckEditor() {
|
|
|
1049
1063
|
updateDeck(id, { aspectRatio: previous });
|
|
1050
1064
|
});
|
|
1051
1065
|
}}
|
|
1066
|
+
currentSlideId={currentSlide?.id}
|
|
1067
|
+
addSlideGenerating={addSlideGenerating}
|
|
1068
|
+
onAddSlideGeneratingChange={setAddSlideGenerating}
|
|
1069
|
+
onAddEmptySlide={handleAddEmptySlide}
|
|
1070
|
+
onDuplicateCurrentSlide={
|
|
1071
|
+
currentSlide ? () => duplicateSlide(id, currentSlide.id) : undefined
|
|
1072
|
+
}
|
|
1052
1073
|
/>
|
|
1053
1074
|
|
|
1075
|
+
{/* Full-width host for the slide's contextual style toolbar: it spans the
|
|
1076
|
+
* slide rail as well as the canvas, matching the deck toolbar above it. */}
|
|
1077
|
+
<div ref={setContextToolbarSlot} className="shrink-0" />
|
|
1078
|
+
|
|
1054
1079
|
<div className="relative flex min-h-0 flex-1 overflow-hidden">
|
|
1055
1080
|
{sidebarOpen && (
|
|
1056
1081
|
<>
|
|
@@ -1068,24 +1093,12 @@ export default function DeckEditor() {
|
|
|
1068
1093
|
slides={deck.slides}
|
|
1069
1094
|
activeSlideId={currentSlide?.id || ""}
|
|
1070
1095
|
deckId={id}
|
|
1071
|
-
deckTitle={deck.title}
|
|
1072
1096
|
onSelectSlide={(slideId) => {
|
|
1073
1097
|
setGeneratingSlideSelected(false);
|
|
1074
1098
|
setActiveSlideId(slideId);
|
|
1075
1099
|
if (window.innerWidth < 768) setSidebarOpen(false);
|
|
1076
1100
|
}}
|
|
1077
1101
|
onDuplicateSlide={(slideId) => duplicateSlide(id, slideId)}
|
|
1078
|
-
onAddEmptySlide={() => {
|
|
1079
|
-
const activeIdx = deck.slides.findIndex(
|
|
1080
|
-
(s) => s.id === activeSlideId,
|
|
1081
|
-
);
|
|
1082
|
-
const newId = addSlide(
|
|
1083
|
-
id,
|
|
1084
|
-
"blank",
|
|
1085
|
-
activeIdx >= 0 ? activeIdx : undefined,
|
|
1086
|
-
);
|
|
1087
|
-
setActiveSlideId(newId);
|
|
1088
|
-
}}
|
|
1089
1102
|
onDeleteSlide={(slideId) => {
|
|
1090
1103
|
const idx = deck.slides.findIndex((s) => s.id === slideId);
|
|
1091
1104
|
const nextSlide =
|
|
@@ -1110,8 +1123,6 @@ export default function DeckEditor() {
|
|
|
1110
1123
|
setGeneratingSlideSelected(true);
|
|
1111
1124
|
if (window.innerWidth < 768) setSidebarOpen(false);
|
|
1112
1125
|
}}
|
|
1113
|
-
addSlideGenerating={addSlideGenerating}
|
|
1114
|
-
onAddSlideGeneratingChange={setAddSlideGenerating}
|
|
1115
1126
|
/>
|
|
1116
1127
|
</DndContext>
|
|
1117
1128
|
</div>
|
|
@@ -1162,6 +1173,26 @@ export default function DeckEditor() {
|
|
|
1162
1173
|
slide={currentSlide}
|
|
1163
1174
|
deckId={id}
|
|
1164
1175
|
readOnly={!canEdit}
|
|
1176
|
+
contextToolbarSlot={contextToolbarSlot}
|
|
1177
|
+
contextToolbarLeading={
|
|
1178
|
+
canEdit ? (
|
|
1179
|
+
<EditorActionCluster
|
|
1180
|
+
deckId={id}
|
|
1181
|
+
deckTitle={deck.title}
|
|
1182
|
+
currentSlideId={currentSlide.id}
|
|
1183
|
+
slideCount={deck.slides.length}
|
|
1184
|
+
currentSlideIndex={currentIndex >= 0 ? currentIndex : 0}
|
|
1185
|
+
addSlideGenerating={addSlideGenerating}
|
|
1186
|
+
onAddSlideGeneratingChange={setAddSlideGenerating}
|
|
1187
|
+
onAddEmptySlide={handleAddEmptySlide}
|
|
1188
|
+
onDuplicateCurrentSlide={() =>
|
|
1189
|
+
duplicateSlide(id, currentSlide.id)
|
|
1190
|
+
}
|
|
1191
|
+
textBoxMode={textBoxMode}
|
|
1192
|
+
onToggleTextBoxMode={toggleTextBoxMode}
|
|
1193
|
+
/>
|
|
1194
|
+
) : undefined
|
|
1195
|
+
}
|
|
1165
1196
|
onUpdateSlide={(updates, slideIdOverride, options) =>
|
|
1166
1197
|
updateSlide(
|
|
1167
1198
|
id,
|
|
@@ -1194,8 +1225,6 @@ export default function DeckEditor() {
|
|
|
1194
1225
|
slideIndex={currentIndex >= 0 ? currentIndex : 0}
|
|
1195
1226
|
slideCount={deck.slides.length}
|
|
1196
1227
|
designSystem={designSystem}
|
|
1197
|
-
stylePanelOpen={styleOpen}
|
|
1198
|
-
onCloseStylePanel={() => setSidePanel(null)}
|
|
1199
1228
|
aspectRatio={deck.aspectRatio}
|
|
1200
1229
|
collabUser={
|
|
1201
1230
|
currentUser
|