@agent-native/core 0.84.9 → 0.84.12
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/core/CHANGELOG.md +43 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-store.ts +29 -6
- package/corpus/core/src/cli/skills.ts +6 -3
- package/corpus/core/src/client/agent-chat-adapter.ts +31 -11
- package/corpus/core/src/client/require-session.tsx +20 -1
- package/corpus/core/src/client/sharing/ShareButton.tsx +9 -3
- package/corpus/core/src/collab/storage.ts +40 -5
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +7 -6
- package/corpus/templates/assets/AGENTS.md +15 -0
- package/corpus/templates/assets/actions/_helpers.ts +3 -1
- package/corpus/templates/assets/actions/create-generation-preset.ts +12 -1
- package/corpus/templates/assets/actions/generate-asset.ts +5 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +6 -1
- package/corpus/templates/assets/actions/generate-image.ts +18 -11
- package/corpus/templates/assets/actions/open-asset-picker.ts +4 -2
- package/corpus/templates/assets/actions/update-generation-preset.ts +16 -3
- package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/assets/app/i18n-data.ts +30 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +42 -7
- package/corpus/templates/assets/app/routes/library.tsx +4 -2
- package/corpus/templates/assets/changelog/2026-07-01-generation-presets-can-now-composite-your-brand-s-canonical-.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-01-tagging-a-preset-now-briefs-the-agent-on-that-preset-s-aesth.md +6 -0
- package/corpus/templates/assets/server/lib/preset-chat-context.ts +118 -0
- package/corpus/templates/assets/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +1 -0
- package/corpus/templates/design/actions/export-html.ts +4 -2
- package/corpus/templates/design/actions/export-pdf.ts +3 -1
- package/corpus/templates/design/actions/export-svg.ts +4 -2
- package/corpus/templates/design/actions/export-zip.ts +10 -3
- package/corpus/templates/design/actions/get-design-snapshot.ts +41 -4
- package/corpus/templates/design/actions/present-design-variants.ts +21 -6
- package/corpus/templates/design/actions/update-file.ts +68 -17
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +3 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +35 -3
- package/corpus/templates/design/app/components/design/MotionDock.tsx +46 -11
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +80 -30
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +60 -10
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +2 -0
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +4 -0
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +2 -2
- package/corpus/templates/design/app/i18n-data.ts +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +493 -109
- package/corpus/templates/design/app/pages/VisualEdit.tsx +4 -16
- package/corpus/templates/design/changelog/2026-07-01-improved-variant-pick-follow-ups-so-selected-directions-cont.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +2 -2
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +28 -6
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +6 -3
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +26 -11
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/require-session.d.ts +14 -0
- package/dist/client/require-session.d.ts.map +1 -1
- package/dist/client/require-session.js +19 -1
- package/dist/client/require-session.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +4 -2
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -1
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +31 -5
- package/dist/collab/storage.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +6 -6
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +7 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -166,6 +166,7 @@ import {
|
|
|
166
166
|
type InspectorTab,
|
|
167
167
|
} from "@/components/design/EditPanel";
|
|
168
168
|
import type { ExportSettingsValue } from "@/components/design/inspector";
|
|
169
|
+
import { InspectorAiActions } from "@/components/design/inspector/InspectorAiActions";
|
|
169
170
|
import {
|
|
170
171
|
LayersPanel,
|
|
171
172
|
type LayersPanelFile,
|
|
@@ -1108,6 +1109,120 @@ export function hydrateMotionDockTracks(
|
|
|
1108
1109
|
}));
|
|
1109
1110
|
}
|
|
1110
1111
|
|
|
1112
|
+
const MOTION_KEYFRAME_TIME_EPSILON = 0.002;
|
|
1113
|
+
|
|
1114
|
+
function motionCssPropertyName(property: string): string | null {
|
|
1115
|
+
const trimmed = property.trim();
|
|
1116
|
+
if (!trimmed || trimmed.startsWith("--")) return null;
|
|
1117
|
+
const cssName = trimmed
|
|
1118
|
+
.replace(/^css/, "")
|
|
1119
|
+
.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
|
|
1120
|
+
.toLowerCase();
|
|
1121
|
+
return /^-?[a-z][a-z0-9-]*$/i.test(cssName) ? cssName : null;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function camelStyleProperty(property: string): string {
|
|
1125
|
+
return property.replace(/-([a-z])/g, (_, letter: string) =>
|
|
1126
|
+
letter.toUpperCase(),
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function computedMotionStyleValue(
|
|
1131
|
+
computedStyles: Record<string, string> | undefined,
|
|
1132
|
+
property: string,
|
|
1133
|
+
): string | undefined {
|
|
1134
|
+
if (!computedStyles) return undefined;
|
|
1135
|
+
return (
|
|
1136
|
+
computedStyles[property] ??
|
|
1137
|
+
computedStyles[camelStyleProperty(property)] ??
|
|
1138
|
+
computedStyles[
|
|
1139
|
+
property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
|
|
1140
|
+
]
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function defaultMotionBaseValue(property: string, nextValue: string): string {
|
|
1145
|
+
if (property === "opacity") return "1";
|
|
1146
|
+
if (property === "transform" || property === "filter") return "none";
|
|
1147
|
+
return nextValue;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
export function upsertMotionStyleKeyframes(args: {
|
|
1151
|
+
tracks: MotionDockTrack[];
|
|
1152
|
+
targetNodeId: string;
|
|
1153
|
+
label: string;
|
|
1154
|
+
styles: Record<string, string>;
|
|
1155
|
+
computedStyles?: Record<string, string>;
|
|
1156
|
+
playhead: number;
|
|
1157
|
+
defaultEase?: string;
|
|
1158
|
+
}): MotionDockTrack[] {
|
|
1159
|
+
const t = Math.max(0, Math.min(1, args.playhead));
|
|
1160
|
+
const ease = args.defaultEase ?? "ease";
|
|
1161
|
+
let nextTracks = args.tracks;
|
|
1162
|
+
|
|
1163
|
+
for (const [rawProperty, rawValue] of Object.entries(args.styles)) {
|
|
1164
|
+
if (rawValue === undefined) continue;
|
|
1165
|
+
const value = String(rawValue).trim();
|
|
1166
|
+
if (!value) continue;
|
|
1167
|
+
const property = motionCssPropertyName(rawProperty);
|
|
1168
|
+
if (!property) continue;
|
|
1169
|
+
|
|
1170
|
+
const existingIndex = nextTracks.findIndex(
|
|
1171
|
+
(track) =>
|
|
1172
|
+
track.targetNodeId === args.targetNodeId && track.property === property,
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
if (existingIndex >= 0) {
|
|
1176
|
+
nextTracks = nextTracks.map((track, index) => {
|
|
1177
|
+
if (index !== existingIndex) return track;
|
|
1178
|
+
const withoutCurrentTime = track.keyframes.filter(
|
|
1179
|
+
(keyframe) => Math.abs(keyframe.t - t) > MOTION_KEYFRAME_TIME_EPSILON,
|
|
1180
|
+
);
|
|
1181
|
+
return {
|
|
1182
|
+
...track,
|
|
1183
|
+
label: track.label || args.label,
|
|
1184
|
+
keyframes: [...withoutCurrentTime, { t, value, ease }].sort(
|
|
1185
|
+
(a, b) => a.t - b.t,
|
|
1186
|
+
),
|
|
1187
|
+
};
|
|
1188
|
+
});
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const baseValue =
|
|
1193
|
+
computedMotionStyleValue(args.computedStyles, property) ??
|
|
1194
|
+
computedMotionStyleValue(args.computedStyles, rawProperty) ??
|
|
1195
|
+
defaultMotionBaseValue(property, value);
|
|
1196
|
+
const keyframes =
|
|
1197
|
+
t <= MOTION_KEYFRAME_TIME_EPSILON
|
|
1198
|
+
? [
|
|
1199
|
+
{ t: 0, value, ease },
|
|
1200
|
+
{ t: 1, value: baseValue, ease },
|
|
1201
|
+
]
|
|
1202
|
+
: t >= 1 - MOTION_KEYFRAME_TIME_EPSILON
|
|
1203
|
+
? [
|
|
1204
|
+
{ t: 0, value: baseValue, ease },
|
|
1205
|
+
{ t: 1, value, ease },
|
|
1206
|
+
]
|
|
1207
|
+
: [
|
|
1208
|
+
{ t: 0, value: baseValue, ease },
|
|
1209
|
+
{ t, value, ease },
|
|
1210
|
+
{ t: 1, value: baseValue, ease },
|
|
1211
|
+
];
|
|
1212
|
+
nextTracks = [
|
|
1213
|
+
...nextTracks,
|
|
1214
|
+
{
|
|
1215
|
+
targetNodeId: args.targetNodeId,
|
|
1216
|
+
property,
|
|
1217
|
+
keyframes,
|
|
1218
|
+
label: args.label,
|
|
1219
|
+
},
|
|
1220
|
+
];
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return nextTracks;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1111
1226
|
export function motionTimelineFingerprint(
|
|
1112
1227
|
fileId: string,
|
|
1113
1228
|
timeline: MotionTimelineRow | null | undefined,
|
|
@@ -1129,6 +1244,8 @@ function buildSignInHrefForDesignIntent(intent: PostAuthDesignIntent): string {
|
|
|
1129
1244
|
if (typeof window === "undefined") return base;
|
|
1130
1245
|
|
|
1131
1246
|
const returnUrl = new URL(window.location.href);
|
|
1247
|
+
returnUrl.search = "";
|
|
1248
|
+
returnUrl.hash = "";
|
|
1132
1249
|
returnUrl.searchParams.set("intent", intent);
|
|
1133
1250
|
const ret = returnUrl.pathname + returnUrl.search + returnUrl.hash;
|
|
1134
1251
|
return `${base}?return=${encodeURIComponent(ret)}`;
|
|
@@ -1189,8 +1306,11 @@ export function getAvailableContentHistoryChanges(
|
|
|
1189
1306
|
activeFileId?: string | null,
|
|
1190
1307
|
): ContentHistoryChange[] {
|
|
1191
1308
|
const fileIds = new Set(availableFileIds);
|
|
1309
|
+
const activeFileIsAvailable = !!activeFileId && fileIds.has(activeFileId);
|
|
1192
1310
|
return getContentHistoryChanges(entry).filter(
|
|
1193
|
-
(change) =>
|
|
1311
|
+
(change) =>
|
|
1312
|
+
fileIds.has(change.fileId) ||
|
|
1313
|
+
(activeFileIsAvailable && change.fileId === activeFileId),
|
|
1194
1314
|
);
|
|
1195
1315
|
}
|
|
1196
1316
|
|
|
@@ -1325,7 +1445,7 @@ function designVariantGenerationDirectives(
|
|
|
1325
1445
|
...designSystemGenerationDirectives(designSystemId),
|
|
1326
1446
|
"The user's prompt already asks to explore multiple directions, so DO NOT call `show-design-questions` first and DO NOT call `generate-design` first.",
|
|
1327
1447
|
"Call `present-design-variants` with 2-5 concise directions (3 when unspecified). Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens.",
|
|
1328
|
-
"Wait for the user's chat pick, delete the unchosen variant screens, then continue from the kept
|
|
1448
|
+
"Wait for the user's chat pick, delete the unchosen variant screens, call `get-design-snapshot` with `fileId` for the kept screen, then continue from that single file in a bounded pass. Prefer `edit-design`; if `generate-design` is needed, update only the kept file compactly and stop after the first successful save.",
|
|
1329
1449
|
];
|
|
1330
1450
|
}
|
|
1331
1451
|
|
|
@@ -1336,7 +1456,7 @@ function designGenerationDirectives(
|
|
|
1336
1456
|
return [
|
|
1337
1457
|
`Use the \`generate-design --designId="${designId}"\` action with exactly one complete, renderable \`index.html\` file first. The design already exists - DO NOT call create-design.`,
|
|
1338
1458
|
...designSystemGenerationDirectives(designSystemId),
|
|
1339
|
-
"If the user asked to explore variations, call `present-design-variants` with 2-5 concise directions. Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens. Wait for their chat pick, delete the unchosen variant screens, then continue from the kept
|
|
1459
|
+
"If the user asked to explore variations, call `present-design-variants` with 2-5 concise directions. Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens. Wait for their chat pick, delete the unchosen variant screens, call `get-design-snapshot` with `fileId` for the kept screen, then continue from that single file in a bounded pass. Prefer `edit-design`; if `generate-design` is needed, update only the kept file compactly and stop after the first successful save. Otherwise generate one polished first direction.",
|
|
1340
1460
|
"Keep the first pass bounded enough to finish quickly: one self-contained Alpine.js + Tailwind CDN HTML document, polished but concise. Add 3-6 tweaks only when they naturally fit the design.",
|
|
1341
1461
|
"After generate-design succeeds, stop and summarize what was created.",
|
|
1342
1462
|
];
|
|
@@ -1913,6 +2033,14 @@ function appendCanvasPrimitiveToHtml(
|
|
|
1913
2033
|
const explicitPathData = primitive.pathData?.trim()
|
|
1914
2034
|
? primitive.pathData
|
|
1915
2035
|
: null;
|
|
2036
|
+
const pathViewBoxLeft = options?.preserveNegativePosition
|
|
2037
|
+
? geometry.x
|
|
2038
|
+
: Math.max(0, geometry.x);
|
|
2039
|
+
const pathViewBoxTop = options?.preserveNegativePosition
|
|
2040
|
+
? geometry.y
|
|
2041
|
+
: Math.max(0, geometry.y);
|
|
2042
|
+
const pathViewBoxWidth = Math.max(1, geometry.width);
|
|
2043
|
+
const pathViewBoxHeight = Math.max(1, geometry.height);
|
|
1916
2044
|
const points = primitive.points?.length
|
|
1917
2045
|
? primitive.points
|
|
1918
2046
|
: [
|
|
@@ -1977,7 +2105,7 @@ function appendCanvasPrimitiveToHtml(
|
|
|
1977
2105
|
svg.setAttribute(
|
|
1978
2106
|
"viewBox",
|
|
1979
2107
|
explicitPathData
|
|
1980
|
-
? `${
|
|
2108
|
+
? `${pathViewBoxLeft} ${pathViewBoxTop} ${pathViewBoxWidth} ${pathViewBoxHeight}`
|
|
1981
2109
|
: `0 0 ${width} ${height}`,
|
|
1982
2110
|
);
|
|
1983
2111
|
svg.setAttribute(
|
|
@@ -2382,7 +2510,7 @@ function extractLayerPosition(
|
|
|
2382
2510
|
function postBeginTextEditToPreviewIframes(
|
|
2383
2511
|
screenId: string | null,
|
|
2384
2512
|
nodeId: string,
|
|
2385
|
-
):
|
|
2513
|
+
): "active" | "done" | false {
|
|
2386
2514
|
if (typeof document === "undefined" || !nodeId) return false;
|
|
2387
2515
|
const iframes = Array.from(
|
|
2388
2516
|
document.querySelectorAll<HTMLIFrameElement>(
|
|
@@ -2394,16 +2522,18 @@ function postBeginTextEditToPreviewIframes(
|
|
|
2394
2522
|
);
|
|
2395
2523
|
const orderedIframes = targetIframes.length > 0 ? targetIframes : iframes;
|
|
2396
2524
|
const selector = `[data-agent-native-node-id="${nodeId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"][data-agent-native-text-editing]`;
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2525
|
+
for (const iframe of orderedIframes) {
|
|
2526
|
+
try {
|
|
2527
|
+
const doc = iframe.contentDocument;
|
|
2528
|
+
const node = doc?.querySelector<HTMLElement>(
|
|
2529
|
+
`[data-agent-native-node-id="${nodeId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`,
|
|
2530
|
+
);
|
|
2531
|
+
const editing = doc?.querySelector(selector);
|
|
2532
|
+
if (editing && doc?.activeElement === editing) return "active";
|
|
2533
|
+
if (node && (node.textContent ?? "").trim().length > 0) return "done";
|
|
2534
|
+
} catch {
|
|
2535
|
+
// Keep retrying other iframes.
|
|
2536
|
+
}
|
|
2407
2537
|
}
|
|
2408
2538
|
orderedIframes.forEach((iframe) => {
|
|
2409
2539
|
iframe.contentWindow?.postMessage(
|
|
@@ -2419,15 +2549,31 @@ function scheduleBeginTextEditForScreen(
|
|
|
2419
2549
|
nodeId: string,
|
|
2420
2550
|
) {
|
|
2421
2551
|
if (typeof window === "undefined") return;
|
|
2422
|
-
let
|
|
2423
|
-
[
|
|
2552
|
+
let finished = false;
|
|
2553
|
+
[180, 300, 600, 900, 1200, 1800, 2400, 3200, 4200].forEach((delay) => {
|
|
2424
2554
|
window.setTimeout(() => {
|
|
2425
|
-
if (
|
|
2426
|
-
|
|
2555
|
+
if (finished) return;
|
|
2556
|
+
finished = postBeginTextEditToPreviewIframes(screenId, nodeId) === "done";
|
|
2427
2557
|
}, delay);
|
|
2428
2558
|
});
|
|
2429
2559
|
}
|
|
2430
2560
|
|
|
2561
|
+
function postShaderFillPreviewClearToPreviewIframes() {
|
|
2562
|
+
if (typeof document === "undefined") return;
|
|
2563
|
+
document
|
|
2564
|
+
.querySelectorAll<HTMLIFrameElement>("iframe[data-design-preview-iframe]")
|
|
2565
|
+
.forEach((iframe) => {
|
|
2566
|
+
try {
|
|
2567
|
+
iframe.contentWindow?.postMessage(
|
|
2568
|
+
{ type: "shader-fill-preview-clear" },
|
|
2569
|
+
"*",
|
|
2570
|
+
);
|
|
2571
|
+
} catch {
|
|
2572
|
+
// Ignore inaccessible iframe windows; same-origin previews handle this.
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2431
2577
|
function removeElementFromHtml(
|
|
2432
2578
|
content: string,
|
|
2433
2579
|
selector: string,
|
|
@@ -4377,6 +4523,9 @@ export default function DesignEditor() {
|
|
|
4377
4523
|
const [motionTimelineId, setMotionTimelineId] = useState<string | null>(null);
|
|
4378
4524
|
const [motionTracks, setMotionTracks] = useState<MotionDockTrack[]>([]);
|
|
4379
4525
|
const [motionDurationMs, setMotionDurationMs] = useState(1000);
|
|
4526
|
+
const [motionPlayhead, setMotionPlayhead] = useState(0);
|
|
4527
|
+
const [motionAutoKeyframeEnabled, setMotionAutoKeyframeEnabled] =
|
|
4528
|
+
useState(false);
|
|
4380
4529
|
const [motionTracksDirty, setMotionTracksDirty] = useState(false);
|
|
4381
4530
|
const [motionAutosaveRevision, setMotionAutosaveRevision] = useState(0);
|
|
4382
4531
|
const [motionHydrationFingerprint, setMotionHydrationFingerprint] = useState<
|
|
@@ -4444,6 +4593,10 @@ export default function DesignEditor() {
|
|
|
4444
4593
|
nodeId?: string;
|
|
4445
4594
|
css: string;
|
|
4446
4595
|
} | null>(null);
|
|
4596
|
+
const clearShaderFillPreview = useCallback(() => {
|
|
4597
|
+
setShaderFillPreview(null);
|
|
4598
|
+
postShaderFillPreviewClearToPreviewIframes();
|
|
4599
|
+
}, []);
|
|
4447
4600
|
|
|
4448
4601
|
// ── Breakpoint preview state (§6.4) ─────────────────────────────────────────
|
|
4449
4602
|
// Active breakpoint width for the current design (pixels). Controls which
|
|
@@ -5696,9 +5849,14 @@ export default function DesignEditor() {
|
|
|
5696
5849
|
: undefined;
|
|
5697
5850
|
|
|
5698
5851
|
// Exclude the board file — it is rendered by its own DesignCanvas instance
|
|
5699
|
-
// in MultiScreenCanvas and must not appear as a screen frame.
|
|
5852
|
+
// in MultiScreenCanvas and must not appear as a screen frame. Support files
|
|
5853
|
+
// such as CSS are editable files, not visual screens.
|
|
5700
5854
|
return files
|
|
5701
|
-
.filter(
|
|
5855
|
+
.filter(
|
|
5856
|
+
(file) =>
|
|
5857
|
+
normalizedDesignFileType(file.fileType) === "html" &&
|
|
5858
|
+
!isBoardFile(file.filename),
|
|
5859
|
+
)
|
|
5702
5860
|
.map((file) => {
|
|
5703
5861
|
const metadata = getDesignDataRecord(metadataByFileId, file.id);
|
|
5704
5862
|
const stringValue = (key: string) =>
|
|
@@ -6043,14 +6201,29 @@ export default function DesignEditor() {
|
|
|
6043
6201
|
};
|
|
6044
6202
|
}, []);
|
|
6045
6203
|
|
|
6046
|
-
|
|
6204
|
+
const defaultActiveFile =
|
|
6205
|
+
files.find(
|
|
6206
|
+
(file) =>
|
|
6207
|
+
normalizedDesignFileType(file.fileType) === "html" &&
|
|
6208
|
+
!isBoardFile(file.filename) &&
|
|
6209
|
+
file.filename.toLowerCase() === "index.html",
|
|
6210
|
+
) ??
|
|
6211
|
+
files.find(
|
|
6212
|
+
(file) =>
|
|
6213
|
+
normalizedDesignFileType(file.fileType) === "html" &&
|
|
6214
|
+
!isBoardFile(file.filename),
|
|
6215
|
+
) ??
|
|
6216
|
+
files[0];
|
|
6217
|
+
|
|
6218
|
+
// Set active file to the primary screen when data loads.
|
|
6047
6219
|
useEffect(() => {
|
|
6048
|
-
if (
|
|
6049
|
-
setActiveFileId(
|
|
6220
|
+
if (defaultActiveFile && !activeFileId) {
|
|
6221
|
+
setActiveFileId(defaultActiveFile.id);
|
|
6050
6222
|
}
|
|
6051
|
-
}, [
|
|
6223
|
+
}, [activeFileId, defaultActiveFile]);
|
|
6052
6224
|
|
|
6053
|
-
const activeFile =
|
|
6225
|
+
const activeFile =
|
|
6226
|
+
files.find((f) => f.id === activeFileId) ?? defaultActiveFile;
|
|
6054
6227
|
activeFileIdForUndoRef.current = activeFile?.id ?? null;
|
|
6055
6228
|
const motionTimelineQueryParams =
|
|
6056
6229
|
id && activeFile?.id
|
|
@@ -6196,9 +6369,13 @@ export default function DesignEditor() {
|
|
|
6196
6369
|
if (target && !targetFile) return false;
|
|
6197
6370
|
|
|
6198
6371
|
const inspectorTab =
|
|
6199
|
-
command.inspectorTab === "design" ||
|
|
6372
|
+
command.inspectorTab === "design" ||
|
|
6373
|
+
command.inspectorTab === "tweaks" ||
|
|
6374
|
+
command.inspectorTab === "extensions"
|
|
6200
6375
|
? command.inspectorTab
|
|
6201
|
-
: command.inspector === "design" ||
|
|
6376
|
+
: command.inspector === "design" ||
|
|
6377
|
+
command.inspector === "tweaks" ||
|
|
6378
|
+
command.inspector === "extensions"
|
|
6202
6379
|
? command.inspector
|
|
6203
6380
|
: undefined;
|
|
6204
6381
|
if (inspectorTab) setActiveInspectorTab(inspectorTab);
|
|
@@ -7026,12 +7203,22 @@ export default function DesignEditor() {
|
|
|
7026
7203
|
const canvasIframeRef = useMemo<React.RefObject<HTMLIFrameElement | null>>(
|
|
7027
7204
|
() => ({
|
|
7028
7205
|
get current() {
|
|
7029
|
-
|
|
7030
|
-
|
|
7206
|
+
const iframes = Array.from(
|
|
7207
|
+
document.querySelectorAll<HTMLIFrameElement>(
|
|
7208
|
+
"iframe[data-design-preview-iframe]",
|
|
7209
|
+
),
|
|
7210
|
+
);
|
|
7211
|
+
if (!activeFile?.id) return iframes[0] ?? null;
|
|
7212
|
+
return (
|
|
7213
|
+
iframes.find(
|
|
7214
|
+
(iframe) => iframe.dataset.screenIframeId === activeFile.id,
|
|
7215
|
+
) ??
|
|
7216
|
+
iframes[0] ??
|
|
7217
|
+
null
|
|
7031
7218
|
);
|
|
7032
7219
|
},
|
|
7033
7220
|
}),
|
|
7034
|
-
[],
|
|
7221
|
+
[activeFile?.id],
|
|
7035
7222
|
);
|
|
7036
7223
|
|
|
7037
7224
|
const handleEditorDragStateChange = useCallback((active: boolean) => {
|
|
@@ -7423,6 +7610,8 @@ export default function DesignEditor() {
|
|
|
7423
7610
|
setMotionTimelineId(null);
|
|
7424
7611
|
setMotionTracks([]);
|
|
7425
7612
|
setMotionDurationMs(1000);
|
|
7613
|
+
setMotionPlayhead(0);
|
|
7614
|
+
setMotionAutoKeyframeEnabled(false);
|
|
7426
7615
|
setMotionTracksDirty(false);
|
|
7427
7616
|
setMotionAutosaveRevision(0);
|
|
7428
7617
|
setMotionHydrationFingerprint(null);
|
|
@@ -7564,8 +7753,36 @@ export default function DesignEditor() {
|
|
|
7564
7753
|
}, [selectedCodeLayerNode, selectedElement]);
|
|
7565
7754
|
|
|
7566
7755
|
useEffect(() => {
|
|
7567
|
-
|
|
7568
|
-
}, [
|
|
7756
|
+
clearShaderFillPreview();
|
|
7757
|
+
}, [
|
|
7758
|
+
activeFile?.id,
|
|
7759
|
+
clearShaderFillPreview,
|
|
7760
|
+
selectedElement?.selector,
|
|
7761
|
+
selectedElement?.sourceId,
|
|
7762
|
+
]);
|
|
7763
|
+
useEffect(() => {
|
|
7764
|
+
const urlParams = new URLSearchParams(location.search);
|
|
7765
|
+
const urlKeepsExtensionsInspector =
|
|
7766
|
+
urlParams.get("inspector") === "extensions" ||
|
|
7767
|
+
urlParams.get("inspectorTab") === "extensions";
|
|
7768
|
+
if (activeInspectorTab === "extensions" && urlKeepsExtensionsInspector) {
|
|
7769
|
+
return;
|
|
7770
|
+
}
|
|
7771
|
+
clearShaderFillPreview();
|
|
7772
|
+
}, [
|
|
7773
|
+
activeInspectorTab,
|
|
7774
|
+
clearShaderFillPreview,
|
|
7775
|
+
location.pathname,
|
|
7776
|
+
location.search,
|
|
7777
|
+
]);
|
|
7778
|
+
useEffect(() => {
|
|
7779
|
+
window.addEventListener("pagehide", clearShaderFillPreview);
|
|
7780
|
+
window.addEventListener("beforeunload", clearShaderFillPreview);
|
|
7781
|
+
return () => {
|
|
7782
|
+
window.removeEventListener("pagehide", clearShaderFillPreview);
|
|
7783
|
+
window.removeEventListener("beforeunload", clearShaderFillPreview);
|
|
7784
|
+
};
|
|
7785
|
+
}, [clearShaderFillPreview]);
|
|
7569
7786
|
|
|
7570
7787
|
// A friendly default name for the create-component dialog, derived from the
|
|
7571
7788
|
// selected element's layer name / tag.
|
|
@@ -7643,6 +7860,56 @@ export default function DesignEditor() {
|
|
|
7643
7860
|
return motionTracks.map(({ label: _label, ...track }) => track);
|
|
7644
7861
|
}, [motionDockOpen, motionTracks]);
|
|
7645
7862
|
|
|
7863
|
+
const upsertMotionKeyframesFromStyles = useCallback(
|
|
7864
|
+
(
|
|
7865
|
+
styles: Record<string, string>,
|
|
7866
|
+
elementInfo?: ElementInfo,
|
|
7867
|
+
selector?: string,
|
|
7868
|
+
) => {
|
|
7869
|
+
if (!motionDockOpen || !motionAutoKeyframeEnabled) return;
|
|
7870
|
+
const info = elementInfo ?? selectedElement ?? undefined;
|
|
7871
|
+
const targetNode = info
|
|
7872
|
+
? resolveCodeLayerNodeFromElementInfo(activeCodeLayerProjection, info)
|
|
7873
|
+
: selector
|
|
7874
|
+
? resolveCodeLayerNodeFromBridge(activeCodeLayerProjection, selector)
|
|
7875
|
+
: selectedCodeLayerNode;
|
|
7876
|
+
const targetNodeId =
|
|
7877
|
+
targetNode?.dataAttributes["data-agent-native-node-id"]?.trim() ??
|
|
7878
|
+
info?.sourceId ??
|
|
7879
|
+
selectedCodeLayerNode?.dataAttributes[
|
|
7880
|
+
"data-agent-native-node-id"
|
|
7881
|
+
]?.trim();
|
|
7882
|
+
if (!targetNodeId) return;
|
|
7883
|
+
|
|
7884
|
+
const label =
|
|
7885
|
+
targetNode?.layerName ||
|
|
7886
|
+
selectedCodeLayerNode?.layerName ||
|
|
7887
|
+
info?.tagName ||
|
|
7888
|
+
"Selected element";
|
|
7889
|
+
setMotionTracks((current) => {
|
|
7890
|
+
return upsertMotionStyleKeyframes({
|
|
7891
|
+
tracks: current,
|
|
7892
|
+
targetNodeId,
|
|
7893
|
+
label,
|
|
7894
|
+
styles,
|
|
7895
|
+
computedStyles:
|
|
7896
|
+
info?.computedStyles ?? selectedElement?.computedStyles,
|
|
7897
|
+
playhead: motionPlayhead,
|
|
7898
|
+
});
|
|
7899
|
+
});
|
|
7900
|
+
markMotionTracksDirty();
|
|
7901
|
+
},
|
|
7902
|
+
[
|
|
7903
|
+
activeCodeLayerProjection,
|
|
7904
|
+
markMotionTracksDirty,
|
|
7905
|
+
motionAutoKeyframeEnabled,
|
|
7906
|
+
motionDockOpen,
|
|
7907
|
+
motionPlayhead,
|
|
7908
|
+
selectedCodeLayerNode,
|
|
7909
|
+
selectedElement,
|
|
7910
|
+
],
|
|
7911
|
+
);
|
|
7912
|
+
|
|
7646
7913
|
const inspectCodeData = useMemo<InspectCodeData | undefined>(() => {
|
|
7647
7914
|
if (!selectedElement) return undefined;
|
|
7648
7915
|
// Inline/Alpine: the design HTML is the source — show the element's HTML.
|
|
@@ -8311,7 +8578,11 @@ export default function DesignEditor() {
|
|
|
8311
8578
|
);
|
|
8312
8579
|
|
|
8313
8580
|
const handlePrimitiveCreated = useCallback(
|
|
8314
|
-
(
|
|
8581
|
+
(
|
|
8582
|
+
screenId: string,
|
|
8583
|
+
nodeId: string,
|
|
8584
|
+
options?: { selectFrame?: boolean; nextTool?: "move" | "pen" },
|
|
8585
|
+
) => {
|
|
8315
8586
|
// B2/B4 fix: stay in overview mode after drawing a primitive. The user
|
|
8316
8587
|
// drew a shape on the board — they should remain on the board with the
|
|
8317
8588
|
// new primitive selected, matching Figma behaviour. We activate the
|
|
@@ -8330,7 +8601,7 @@ export default function DesignEditor() {
|
|
|
8330
8601
|
setOverviewSelectedScreenIds(
|
|
8331
8602
|
options?.selectFrame === false ? [] : [screenId],
|
|
8332
8603
|
);
|
|
8333
|
-
setActiveTool("move");
|
|
8604
|
+
setActiveTool(options?.nextTool ?? "move");
|
|
8334
8605
|
setMode("edit");
|
|
8335
8606
|
});
|
|
8336
8607
|
// viewMode stays at "overview" — no setViewMode("single") call here.
|
|
@@ -8340,7 +8611,7 @@ export default function DesignEditor() {
|
|
|
8340
8611
|
// inserted HTML over separate renders, so post directly to the target
|
|
8341
8612
|
// iframe with a few short retries instead of relying on a single global
|
|
8342
8613
|
// bridge callback.
|
|
8343
|
-
const textNodeId = pendingTextEditNodeIdRef.current
|
|
8614
|
+
const textNodeId = pendingTextEditNodeIdRef.current;
|
|
8344
8615
|
pendingTextEditNodeIdRef.current = null;
|
|
8345
8616
|
if (textNodeId) {
|
|
8346
8617
|
scheduleBeginTextEditForScreen(screenId, textNodeId);
|
|
@@ -8828,7 +9099,7 @@ export default function DesignEditor() {
|
|
|
8828
9099
|
css,
|
|
8829
9100
|
});
|
|
8830
9101
|
},
|
|
8831
|
-
onShaderFillPreviewClear:
|
|
9102
|
+
onShaderFillPreviewClear: clearShaderFillPreview,
|
|
8832
9103
|
onShaderFillApplied: (fileId, content, updatedAt) => {
|
|
8833
9104
|
applyFileContentUpdate(fileId, content, {
|
|
8834
9105
|
refreshPreview: fileId === activeFile?.id,
|
|
@@ -8845,6 +9116,7 @@ export default function DesignEditor() {
|
|
|
8845
9116
|
activeFile?.updatedAt,
|
|
8846
9117
|
activeTool,
|
|
8847
9118
|
applyFileContentUpdate,
|
|
9119
|
+
clearShaderFillPreview,
|
|
8848
9120
|
design?.title,
|
|
8849
9121
|
files,
|
|
8850
9122
|
id,
|
|
@@ -9150,6 +9422,7 @@ export default function DesignEditor() {
|
|
|
9150
9422
|
([, value]) => value !== undefined,
|
|
9151
9423
|
);
|
|
9152
9424
|
if (entries.length === 0) return;
|
|
9425
|
+
upsertMotionKeyframesFromStyles(styles, options.elementInfo, selector);
|
|
9153
9426
|
// Base every patch off the freshest known content, not the closed-over
|
|
9154
9427
|
// render value. Handlers that fire several onStyleChange calls in one
|
|
9155
9428
|
// synchronous user action (e.g. fixed-size text → width+height+whiteSpace,
|
|
@@ -9466,6 +9739,7 @@ export default function DesignEditor() {
|
|
|
9466
9739
|
selectedElement,
|
|
9467
9740
|
t,
|
|
9468
9741
|
updateLiveScreenSnapshotContent,
|
|
9742
|
+
upsertMotionKeyframesFromStyles,
|
|
9469
9743
|
ydoc,
|
|
9470
9744
|
isSynced,
|
|
9471
9745
|
],
|
|
@@ -9652,8 +9926,13 @@ export default function DesignEditor() {
|
|
|
9652
9926
|
styles,
|
|
9653
9927
|
elementInfo,
|
|
9654
9928
|
);
|
|
9929
|
+
upsertMotionKeyframesFromStyles(styles, elementInfo, selector);
|
|
9655
9930
|
},
|
|
9656
|
-
[
|
|
9931
|
+
[
|
|
9932
|
+
activeFile?.id,
|
|
9933
|
+
recordPendingVisualStyleEdit,
|
|
9934
|
+
upsertMotionKeyframesFromStyles,
|
|
9935
|
+
],
|
|
9657
9936
|
);
|
|
9658
9937
|
|
|
9659
9938
|
const handleVisualStructureChange = useCallback(
|
|
@@ -9971,8 +10250,15 @@ export default function DesignEditor() {
|
|
|
9971
10250
|
elementInfo?: ElementInfo,
|
|
9972
10251
|
) => {
|
|
9973
10252
|
recordPendingVisualStyleEdit(screenId, selector, styles, elementInfo);
|
|
10253
|
+
if (screenId === activeFile?.id) {
|
|
10254
|
+
upsertMotionKeyframesFromStyles(styles, elementInfo, selector);
|
|
10255
|
+
}
|
|
9974
10256
|
},
|
|
9975
|
-
[
|
|
10257
|
+
[
|
|
10258
|
+
activeFile?.id,
|
|
10259
|
+
recordPendingVisualStyleEdit,
|
|
10260
|
+
upsertMotionKeyframesFromStyles,
|
|
10261
|
+
],
|
|
9976
10262
|
);
|
|
9977
10263
|
|
|
9978
10264
|
const handleScreenVisualStructureChange = useCallback(
|
|
@@ -10471,16 +10757,15 @@ export default function DesignEditor() {
|
|
|
10471
10757
|
? boardFileId
|
|
10472
10758
|
: activeFile?.id;
|
|
10473
10759
|
if (!targetFileId || !canEditDesign || entries.length === 0) return;
|
|
10474
|
-
const targetIsBoard = targetFileId === boardFileId;
|
|
10475
10760
|
const baseContent =
|
|
10476
10761
|
targetFileId === activeFile?.id
|
|
10477
10762
|
? getFreshActiveContent()
|
|
10478
10763
|
: (getScreenContent(targetFileId) ?? "");
|
|
10479
10764
|
if (!baseContent && targetFileId !== boardFileId) return;
|
|
10480
10765
|
const layerHtmls = entries.map((entry) => entry.html);
|
|
10481
|
-
const styleSnapshots =
|
|
10482
|
-
|
|
10483
|
-
|
|
10766
|
+
const styleSnapshots = entries.map(
|
|
10767
|
+
(entry) => entry.portableStyleSnapshot,
|
|
10768
|
+
);
|
|
10484
10769
|
const applyPasteContentUpdate = (nextContent: string) => {
|
|
10485
10770
|
if (targetFileId === activeFile?.id) {
|
|
10486
10771
|
applyLocalContentUpdate(nextContent, {
|
|
@@ -10499,7 +10784,11 @@ export default function DesignEditor() {
|
|
|
10499
10784
|
// in normal document flow instead of being an absolutely-positioned body
|
|
10500
10785
|
// child. Fall back to the old position-based clone when nothing is
|
|
10501
10786
|
// selected or a "Paste here" position is provided.
|
|
10502
|
-
if (
|
|
10787
|
+
if (
|
|
10788
|
+
!position &&
|
|
10789
|
+
targetFileId !== boardFileId &&
|
|
10790
|
+
selectedElement?.selector
|
|
10791
|
+
) {
|
|
10503
10792
|
const selector = selectedCanvasSelector ?? selectedElement.selector;
|
|
10504
10793
|
const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
|
|
10505
10794
|
targetSelectors: [selector],
|
|
@@ -10586,7 +10875,6 @@ export default function DesignEditor() {
|
|
|
10586
10875
|
const baseContent = getFreshActiveContent();
|
|
10587
10876
|
if (selectedElement?.boundingRect) {
|
|
10588
10877
|
const { x, y } = selectedElement.boundingRect;
|
|
10589
|
-
const targetIsBoard = activeFile.id === boardFileId;
|
|
10590
10878
|
const result = insertClonedHtmlLayers(
|
|
10591
10879
|
baseContent,
|
|
10592
10880
|
entries.map((entry) => entry.html),
|
|
@@ -10595,9 +10883,7 @@ export default function DesignEditor() {
|
|
|
10595
10883
|
x: x + index * 16,
|
|
10596
10884
|
y: y + index * 16,
|
|
10597
10885
|
})),
|
|
10598
|
-
styleSnapshots:
|
|
10599
|
-
? entries.map((entry) => entry.portableStyleSnapshot)
|
|
10600
|
-
: undefined,
|
|
10886
|
+
styleSnapshots: entries.map((entry) => entry.portableStyleSnapshot),
|
|
10601
10887
|
},
|
|
10602
10888
|
);
|
|
10603
10889
|
if (!result) return;
|
|
@@ -11240,14 +11526,11 @@ export default function DesignEditor() {
|
|
|
11240
11526
|
// there is no anchor, preserve absolute mode and rebase left/top to the
|
|
11241
11527
|
// release point so screen↔board moves behave like Figma absolute layers.
|
|
11242
11528
|
const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
|
|
11243
|
-
const stylePreservedDest =
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
styleSnapshot,
|
|
11249
|
-
)
|
|
11250
|
-
: result.destHtml;
|
|
11529
|
+
const stylePreservedDest = applyPortableStyleSnapshotToHtml(
|
|
11530
|
+
result.destHtml,
|
|
11531
|
+
destNodeAttrId,
|
|
11532
|
+
styleSnapshot,
|
|
11533
|
+
);
|
|
11251
11534
|
const nextDestContent = targetAnchorAttrId
|
|
11252
11535
|
? targetDropMode === "absolute-container"
|
|
11253
11536
|
? targetLocalPoint && targetAnchorRect
|
|
@@ -11398,6 +11681,59 @@ export default function DesignEditor() {
|
|
|
11398
11681
|
removedGeometryRedoEntries,
|
|
11399
11682
|
);
|
|
11400
11683
|
|
|
11684
|
+
const nextContentUndoStack: ContentHistoryEntry[] = [];
|
|
11685
|
+
let removedContentUndoEntries = 0;
|
|
11686
|
+
contentUndoStackRef.current.forEach((entry) => {
|
|
11687
|
+
const remainingChanges = getContentHistoryChanges(entry).filter(
|
|
11688
|
+
(change) => !deleteIds.has(change.fileId),
|
|
11689
|
+
);
|
|
11690
|
+
if (remainingChanges.length === 0) {
|
|
11691
|
+
removedContentUndoEntries += 1;
|
|
11692
|
+
return;
|
|
11693
|
+
}
|
|
11694
|
+
nextContentUndoStack.push(
|
|
11695
|
+
remainingChanges.length === 1
|
|
11696
|
+
? remainingChanges[0]
|
|
11697
|
+
: { changes: remainingChanges },
|
|
11698
|
+
);
|
|
11699
|
+
});
|
|
11700
|
+
contentUndoStackRef.current = nextContentUndoStack;
|
|
11701
|
+
historyOrderRef.current = removeRecentUndoRedoOrderKinds(
|
|
11702
|
+
historyOrderRef.current,
|
|
11703
|
+
"file-content",
|
|
11704
|
+
removedContentUndoEntries,
|
|
11705
|
+
);
|
|
11706
|
+
const nextContentRedoStack: ContentHistoryEntry[] = [];
|
|
11707
|
+
let removedContentRedoEntries = 0;
|
|
11708
|
+
contentRedoStackRef.current.forEach((entry) => {
|
|
11709
|
+
const remainingChanges = getContentHistoryChanges(entry).filter(
|
|
11710
|
+
(change) => !deleteIds.has(change.fileId),
|
|
11711
|
+
);
|
|
11712
|
+
if (remainingChanges.length === 0) {
|
|
11713
|
+
removedContentRedoEntries += 1;
|
|
11714
|
+
return;
|
|
11715
|
+
}
|
|
11716
|
+
nextContentRedoStack.push(
|
|
11717
|
+
remainingChanges.length === 1
|
|
11718
|
+
? remainingChanges[0]
|
|
11719
|
+
: { changes: remainingChanges },
|
|
11720
|
+
);
|
|
11721
|
+
});
|
|
11722
|
+
contentRedoStackRef.current = nextContentRedoStack;
|
|
11723
|
+
redoOrderRef.current = removeRecentUndoRedoOrderKinds(
|
|
11724
|
+
redoOrderRef.current,
|
|
11725
|
+
"file-content",
|
|
11726
|
+
removedContentRedoEntries,
|
|
11727
|
+
);
|
|
11728
|
+
localContentUndoStackRef.current =
|
|
11729
|
+
localContentUndoStackRef.current.filter(
|
|
11730
|
+
(change) => !deleteIds.has(change.fileId),
|
|
11731
|
+
);
|
|
11732
|
+
localContentRedoStackRef.current =
|
|
11733
|
+
localContentRedoStackRef.current.filter(
|
|
11734
|
+
(change) => !deleteIds.has(change.fileId),
|
|
11735
|
+
);
|
|
11736
|
+
|
|
11401
11737
|
writeFrameGeometrySnapshot(nextGeometry);
|
|
11402
11738
|
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
11403
11739
|
if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
|
|
@@ -11541,7 +11877,7 @@ export default function DesignEditor() {
|
|
|
11541
11877
|
if (!canEditDesign) return;
|
|
11542
11878
|
const um = undoManagerRef.current;
|
|
11543
11879
|
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
11544
|
-
let prunedUndoHistory =
|
|
11880
|
+
let prunedUndoHistory = 0;
|
|
11545
11881
|
const undoContent = (scope: "any" | "local" | "global" = "any") => {
|
|
11546
11882
|
if (scope !== "global" && um?.canUndo()) {
|
|
11547
11883
|
um.undo();
|
|
@@ -11623,7 +11959,7 @@ export default function DesignEditor() {
|
|
|
11623
11959
|
);
|
|
11624
11960
|
if (changes.length === 0) {
|
|
11625
11961
|
contentUndoStackRef.current.pop();
|
|
11626
|
-
prunedUndoHistory
|
|
11962
|
+
prunedUndoHistory += 1;
|
|
11627
11963
|
return false;
|
|
11628
11964
|
}
|
|
11629
11965
|
contentUndoStackRef.current.pop();
|
|
@@ -11691,14 +12027,24 @@ export default function DesignEditor() {
|
|
|
11691
12027
|
return true;
|
|
11692
12028
|
};
|
|
11693
12029
|
|
|
11694
|
-
const undoByOrder = (preferred?: UndoRedoOrderKind) =>
|
|
11695
|
-
preferred === "geometry"
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
12030
|
+
const undoByOrder = (preferred?: UndoRedoOrderKind) => {
|
|
12031
|
+
if (preferred === "geometry") return undoGeometry() || undoContent();
|
|
12032
|
+
if (preferred === "file-content") {
|
|
12033
|
+
const prunedBefore = prunedUndoHistory;
|
|
12034
|
+
if (undoContent("global")) return true;
|
|
12035
|
+
if (prunedUndoHistory > prunedBefore) return false;
|
|
12036
|
+
return undoGeometry();
|
|
12037
|
+
}
|
|
12038
|
+
if (preferred === "content") {
|
|
12039
|
+
const prunedBefore = prunedUndoHistory;
|
|
12040
|
+
return (
|
|
12041
|
+
undoContent("local") ||
|
|
12042
|
+
undoContent("global") ||
|
|
12043
|
+
(prunedUndoHistory > prunedBefore ? false : undoGeometry())
|
|
12044
|
+
);
|
|
12045
|
+
}
|
|
12046
|
+
return undoContent() || undoGeometry();
|
|
12047
|
+
};
|
|
11702
12048
|
let didUndo = false;
|
|
11703
12049
|
if (canUseOverviewHistory) {
|
|
11704
12050
|
while (!didUndo) {
|
|
@@ -11709,7 +12055,7 @@ export default function DesignEditor() {
|
|
|
11709
12055
|
} else {
|
|
11710
12056
|
didUndo = undoContent("local");
|
|
11711
12057
|
}
|
|
11712
|
-
if (didUndo || prunedUndoHistory) {
|
|
12058
|
+
if (didUndo || prunedUndoHistory > 0) {
|
|
11713
12059
|
syncUndoRedoState();
|
|
11714
12060
|
}
|
|
11715
12061
|
}, [
|
|
@@ -11731,7 +12077,7 @@ export default function DesignEditor() {
|
|
|
11731
12077
|
if (!canEditDesign) return;
|
|
11732
12078
|
const um = undoManagerRef.current;
|
|
11733
12079
|
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
11734
|
-
let prunedRedoHistory =
|
|
12080
|
+
let prunedRedoHistory = 0;
|
|
11735
12081
|
const redoContent = (scope: "any" | "local" | "global" = "any") => {
|
|
11736
12082
|
if (scope !== "global" && um?.canRedo()) {
|
|
11737
12083
|
um.redo();
|
|
@@ -11813,7 +12159,7 @@ export default function DesignEditor() {
|
|
|
11813
12159
|
);
|
|
11814
12160
|
if (changes.length === 0) {
|
|
11815
12161
|
contentRedoStackRef.current.pop();
|
|
11816
|
-
prunedRedoHistory
|
|
12162
|
+
prunedRedoHistory += 1;
|
|
11817
12163
|
return false;
|
|
11818
12164
|
}
|
|
11819
12165
|
contentRedoStackRef.current.pop();
|
|
@@ -11881,14 +12227,24 @@ export default function DesignEditor() {
|
|
|
11881
12227
|
return true;
|
|
11882
12228
|
};
|
|
11883
12229
|
|
|
11884
|
-
const redoByOrder = (preferred?: UndoRedoOrderKind) =>
|
|
11885
|
-
preferred === "geometry"
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
12230
|
+
const redoByOrder = (preferred?: UndoRedoOrderKind) => {
|
|
12231
|
+
if (preferred === "geometry") return redoGeometry() || redoContent();
|
|
12232
|
+
if (preferred === "file-content") {
|
|
12233
|
+
const prunedBefore = prunedRedoHistory;
|
|
12234
|
+
if (redoContent("global")) return true;
|
|
12235
|
+
if (prunedRedoHistory > prunedBefore) return false;
|
|
12236
|
+
return redoGeometry();
|
|
12237
|
+
}
|
|
12238
|
+
if (preferred === "content") {
|
|
12239
|
+
const prunedBefore = prunedRedoHistory;
|
|
12240
|
+
return (
|
|
12241
|
+
redoContent("local") ||
|
|
12242
|
+
redoContent("global") ||
|
|
12243
|
+
(prunedRedoHistory > prunedBefore ? false : redoGeometry())
|
|
12244
|
+
);
|
|
12245
|
+
}
|
|
12246
|
+
return redoContent() || redoGeometry();
|
|
12247
|
+
};
|
|
11892
12248
|
let didRedo = false;
|
|
11893
12249
|
if (canUseOverviewHistory) {
|
|
11894
12250
|
while (!didRedo) {
|
|
@@ -11899,7 +12255,7 @@ export default function DesignEditor() {
|
|
|
11899
12255
|
} else {
|
|
11900
12256
|
didRedo = redoContent("local");
|
|
11901
12257
|
}
|
|
11902
|
-
if (didRedo || prunedRedoHistory) {
|
|
12258
|
+
if (didRedo || prunedRedoHistory > 0) {
|
|
11903
12259
|
syncUndoRedoState();
|
|
11904
12260
|
}
|
|
11905
12261
|
}, [
|
|
@@ -13234,7 +13590,7 @@ ${serializedHtml}
|
|
|
13234
13590
|
};
|
|
13235
13591
|
|
|
13236
13592
|
useEffect(() => {
|
|
13237
|
-
if (viewMode === "overview") return;
|
|
13593
|
+
if (viewMode === "overview" && !motionDockOpen) return;
|
|
13238
13594
|
if (!activeFile || !activeContent.trim()) return;
|
|
13239
13595
|
const stamped = ensureCodeLayerNodeIdsInHtml(activeContent, {
|
|
13240
13596
|
source: {
|
|
@@ -13246,7 +13602,14 @@ ${serializedHtml}
|
|
|
13246
13602
|
});
|
|
13247
13603
|
if (!stamped.changed || stamped.content === activeContent) return;
|
|
13248
13604
|
applyLocalContentUpdate(stamped.content, { recordHistory: false });
|
|
13249
|
-
}, [
|
|
13605
|
+
}, [
|
|
13606
|
+
activeContent,
|
|
13607
|
+
activeFile,
|
|
13608
|
+
applyLocalContentUpdate,
|
|
13609
|
+
id,
|
|
13610
|
+
motionDockOpen,
|
|
13611
|
+
viewMode,
|
|
13612
|
+
]);
|
|
13250
13613
|
const activeCodeLayerTree = useMemo(
|
|
13251
13614
|
() => buildCodeLayerTree(activeCodeLayerProjection),
|
|
13252
13615
|
[activeCodeLayerProjection],
|
|
@@ -14038,9 +14401,7 @@ ${serializedHtml}
|
|
|
14038
14401
|
|
|
14039
14402
|
// Detect if the active screen is a localhost/local source so we can show a banner.
|
|
14040
14403
|
const activeScreenIsLocalSource =
|
|
14041
|
-
|
|
14042
|
-
Boolean(activeFile) &&
|
|
14043
|
-
activeOverviewScreen?.sourceType === "localhost";
|
|
14404
|
+
Boolean(activeFile) && activeOverviewScreen?.sourceType === "localhost";
|
|
14044
14405
|
const activeScreenRouteSourceFile = activeScreenIsLocalSource
|
|
14045
14406
|
? getLocalhostRouteSourceFile({
|
|
14046
14407
|
sourceFile: activeOverviewScreen?.sourceFile,
|
|
@@ -15357,12 +15718,8 @@ ${serializedHtml}
|
|
|
15357
15718
|
className="h-8 cursor-pointer gap-1.5 rounded-md bg-[var(--design-editor-panel-raised-bg)] px-3 text-sm shadow-none"
|
|
15358
15719
|
aria-label={t("designEditor.signUpToSave")}
|
|
15359
15720
|
>
|
|
15360
|
-
<a
|
|
15361
|
-
|
|
15362
|
-
target="_blank"
|
|
15363
|
-
rel="noopener noreferrer"
|
|
15364
|
-
>
|
|
15365
|
-
<span>{t("home.save")}</span>
|
|
15721
|
+
<a href={signInToSaveHref} role="button">
|
|
15722
|
+
<span>{t("designEditor.signUpToSave")}</span>
|
|
15366
15723
|
</a>
|
|
15367
15724
|
</Button>
|
|
15368
15725
|
</TooltipTrigger>
|
|
@@ -15375,13 +15732,9 @@ ${serializedHtml}
|
|
|
15375
15732
|
variant="default"
|
|
15376
15733
|
size="sm"
|
|
15377
15734
|
className="h-8 cursor-pointer gap-1.5 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
15378
|
-
aria-label={t("designEditor.
|
|
15735
|
+
aria-label={t("designEditor.share")}
|
|
15379
15736
|
>
|
|
15380
|
-
<a
|
|
15381
|
-
href={signInToShareHref}
|
|
15382
|
-
target="_blank"
|
|
15383
|
-
rel="noopener noreferrer"
|
|
15384
|
-
>
|
|
15737
|
+
<a href={signInToShareHref} role="button">
|
|
15385
15738
|
<span>{t("designEditor.share")}</span>
|
|
15386
15739
|
</a>
|
|
15387
15740
|
</Button>
|
|
@@ -16024,20 +16377,22 @@ ${serializedHtml}
|
|
|
16024
16377
|
activeLeftPanel === "agent" ? "flex" : "hidden",
|
|
16025
16378
|
)}
|
|
16026
16379
|
>
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16380
|
+
{canEditDesign ? (
|
|
16381
|
+
<AgentChatSurface
|
|
16382
|
+
mode="panel"
|
|
16383
|
+
className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
|
|
16384
|
+
storageKey={DESIGN_CHAT_STORAGE_KEY}
|
|
16385
|
+
emptyStateText={t("chat.emptyState")}
|
|
16386
|
+
suggestions={[
|
|
16387
|
+
t("chat.suggestionLandingPage"),
|
|
16388
|
+
t("chat.suggestionBrandMatch"),
|
|
16389
|
+
t("chat.suggestionMobile"),
|
|
16390
|
+
]}
|
|
16391
|
+
scope={designChatScope}
|
|
16392
|
+
showScopeBadge={false}
|
|
16393
|
+
browserTabId={browserTabId}
|
|
16394
|
+
/>
|
|
16395
|
+
) : null}
|
|
16041
16396
|
</div>
|
|
16042
16397
|
<div
|
|
16043
16398
|
className={cn(
|
|
@@ -16581,6 +16936,9 @@ ${serializedHtml}
|
|
|
16581
16936
|
}
|
|
16582
16937
|
fusionUrl={designFusionUrl}
|
|
16583
16938
|
onComponentSourceJump={handleComponentSourceJump}
|
|
16939
|
+
motionTracks={
|
|
16940
|
+
screenIsActive ? motionTracksWire : []
|
|
16941
|
+
}
|
|
16584
16942
|
embeddedFrame={{
|
|
16585
16943
|
viewportWidth: Math.max(
|
|
16586
16944
|
1,
|
|
@@ -16962,6 +17320,13 @@ ${serializedHtml}
|
|
|
16962
17320
|
})
|
|
16963
17321
|
}
|
|
16964
17322
|
onRequestTweaks={handleRequestTweaks}
|
|
17323
|
+
extensionsPanel={
|
|
17324
|
+
<DesignExtensionsPanel
|
|
17325
|
+
context={designExtensionContext}
|
|
17326
|
+
hideAssetLibrary
|
|
17327
|
+
title={t("designEditor.extensions")}
|
|
17328
|
+
/>
|
|
17329
|
+
}
|
|
16965
17330
|
onStyleChange={handleStyleChange}
|
|
16966
17331
|
onStylesChange={handleStylesChange}
|
|
16967
17332
|
onExport={handleInspectorExport}
|
|
@@ -16980,6 +17345,21 @@ ${serializedHtml}
|
|
|
16980
17345
|
}
|
|
16981
17346
|
defaultComponentName={defaultComponentName}
|
|
16982
17347
|
inspectCode={inspectCodeData}
|
|
17348
|
+
aiActions={
|
|
17349
|
+
selectedElement && selectedInspectorElements.length <= 1 ? (
|
|
17350
|
+
<InspectorAiActions
|
|
17351
|
+
selector={
|
|
17352
|
+
selectedCanvasSelector ?? selectedElement.selector
|
|
17353
|
+
}
|
|
17354
|
+
sourceId={selectedElement.sourceId}
|
|
17355
|
+
fileId={activeFile?.id}
|
|
17356
|
+
filename={activeFile?.filename}
|
|
17357
|
+
routeSourceFile={activeScreenRouteSourceFile}
|
|
17358
|
+
designId={id}
|
|
17359
|
+
canEdit={canEditDesign}
|
|
17360
|
+
/>
|
|
17361
|
+
) : undefined
|
|
17362
|
+
}
|
|
16983
17363
|
statesPanelProps={
|
|
16984
17364
|
id
|
|
16985
17365
|
? {
|
|
@@ -17082,6 +17462,10 @@ ${serializedHtml}
|
|
|
17082
17462
|
onTracksChange={handleMotionTracksChange}
|
|
17083
17463
|
onDurationChange={handleMotionDurationChange}
|
|
17084
17464
|
canvasIframeRef={canvasIframeRef}
|
|
17465
|
+
autoKeyframe={motionAutoKeyframeEnabled}
|
|
17466
|
+
onAutoKeyframeChange={setMotionAutoKeyframeEnabled}
|
|
17467
|
+
playhead={motionPlayhead}
|
|
17468
|
+
onPlayheadChange={setMotionPlayhead}
|
|
17085
17469
|
selectedTarget={motionSelectedTarget}
|
|
17086
17470
|
applying={motionAutosavePending}
|
|
17087
17471
|
/>
|