diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index 6a825bbe94..6e455cb4b3 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -2933,15 +2933,26 @@ export function StudioApp() { buildDomEditStylePatchOperation("background-size", "contain"), ); } - await persistDomEditOperations(domEditSelection, operations, { - label: "Edit layer style", - skipRefresh: true, - prepareContent: importedFont - ? (html, sourceFile) => ensureImportedFontFace(html, importedFont, sourceFile) - : undefined, - }); + try { + await persistDomEditOperations(domEditSelection, operations, { + label: "Edit layer style", + skipRefresh: true, + prepareContent: importedFont + ? (html, sourceFile) => ensureImportedFontFace(html, importedFont, sourceFile) + : undefined, + }); + } catch (err) { + console.warn("[Studio] Style persist failed:", err instanceof Error ? err.message : err); + } + refreshDomEditSelectionFromPreview(domEditSelection); }, - [activeCompPath, domEditSelection, persistDomEditOperations, resolveImportedFontAsset], + [ + activeCompPath, + domEditSelection, + persistDomEditOperations, + refreshDomEditSelectionFromPreview, + resolveImportedFontAsset, + ], ); const handleDomTextCommit = useCallback( @@ -4197,11 +4208,13 @@ export function StudioApp() { {designPanelActive ? ( 1 ? null : domEditSelection} + multiSelectCount={domEditGroupSelections.length} copiedAgentPrompt={copiedAgentPrompt} onClearSelection={clearDomSelection} onSetStyle={handleDomStyleCommit} onSetManualOffset={handleDomPathOffsetCommit} onSetManualSize={handleDomBoxSizeCommit} + onSetRotation={handleDomRotationCommit} onSetText={handleDomTextCommit} onSetTextFieldStyle={handleDomTextFieldStyleCommit} onResetManualEdits={handleDomManualEditsReset} diff --git a/packages/studio/src/components/editor/PropertyPanel.tsx b/packages/studio/src/components/editor/PropertyPanel.tsx index 581a45d324..d153c27ed8 100644 --- a/packages/studio/src/components/editor/PropertyPanel.tsx +++ b/packages/studio/src/components/editor/PropertyPanel.tsx @@ -11,6 +11,7 @@ import { import { createPortal } from "react-dom"; import { Eye, + Layers, MessageSquare, Move, Palette, @@ -28,7 +29,7 @@ import { type ParsedColor, } from "./colorValue"; import { isTextEditableSelection, type DomEditSelection } from "./domEditing"; -import { readStudioBoxSize, readStudioPathOffset } from "./manualEdits"; +import { readStudioBoxSize, readStudioPathOffset, readStudioRotation } from "./manualEdits"; import { COMMON_LOCAL_FONT_FAMILIES, googleFontStylesheetUrl, @@ -39,11 +40,13 @@ import { resolveFloatingPanelPosition, type FloatingPosition } from "./floatingP interface PropertyPanelProps { element: DomEditSelection | null; + multiSelectCount?: number; copiedAgentPrompt: boolean; onClearSelection: () => void; onSetStyle: (prop: string, value: string) => void | Promise; onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void; onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void; + onSetRotation: (element: DomEditSelection, next: { angle: number }) => void; onSetText: (value: string, fieldKey?: string) => void; onSetTextFieldStyle: (fieldKey: string, property: string, value: string) => void; onResetManualEdits: (element: DomEditSelection) => void; @@ -1568,11 +1571,13 @@ export function isPropertyPanelMediaLikeSelection(input: { export const PropertyPanel = memo(function PropertyPanel({ element, + multiSelectCount = 0, copiedAgentPrompt, onClearSelection, onSetStyle, onSetManualOffset, onSetManualSize, + onSetRotation, onSetText, onSetTextFieldStyle, onResetManualEdits, @@ -1597,12 +1602,28 @@ export const PropertyPanel = memo(function PropertyPanel({ if (!element) { return (
- -

Select an element in the preview.

-

- The inspector is tuned for element edits with safer geometry controls, color picking, and - cleaner grouped layer controls. -

+ {multiSelectCount > 1 ? ( + <> + +

+ {multiSelectCount} elements selected +

+

+ Select a single element to edit its properties. Click an element in the preview or use + the timeline layer panel. +

+ + ) : ( + <> + +

+ Select an element in the preview. +

+

+ Click any element to inspect and edit it. Click deeper to select child elements. +

+ + )}
); } @@ -1618,8 +1639,14 @@ export const PropertyPanel = memo(function PropertyPanel({ tagName: element.tagName, styles, }); - const canShowFillColor = !mediaLikeSelection; - const canShowTextColor = !hasTextControls && !mediaLikeSelection; + const bgColor = styles["background-color"]?.trim(); + const hasExplicitFillColor = + !mediaLikeSelection && + Boolean(bgColor) && + bgColor !== "rgba(0, 0, 0, 0)" && + bgColor !== "transparent"; + const canShowFillColor = hasExplicitFillColor; + const canShowTextColor = !mediaLikeSelection && Boolean(styles.color?.trim()); const visibleSections = getPropertyPanelVisibleSections({ hasSelection: true, canEditStyles: showEditableSections, @@ -1628,6 +1655,7 @@ export const PropertyPanel = memo(function PropertyPanel({ }); const manualOffset = readStudioPathOffset(element.element); const manualSize = readStudioBoxSize(element.element); + const manualRotation = readStudioRotation(element.element); const resolvedWidth = manualSize.width > 0 ? manualSize.width @@ -1837,6 +1865,17 @@ export const PropertyPanel = memo(function PropertyPanel({ disabled={manualSizeEditingDisabled} onCommit={(next) => commitManualSize("height", next)} /> + { + const parsed = parseFloat(next.replace("°", "").trim()); + if (Number.isFinite(parsed)) { + onSetRotation(element, { angle: parsed % 360 }); + } + }} + /> )} diff --git a/packages/studio/src/components/editor/domEditing.ts b/packages/studio/src/components/editor/domEditing.ts index f05adef722..6d40006dbd 100644 --- a/packages/studio/src/components/editor/domEditing.ts +++ b/packages/studio/src/components/editor/domEditing.ts @@ -398,6 +398,7 @@ function buildElementLabel(el: HTMLElement): string { const DOM_LAYER_IGNORED_TAGS = new Set([ "base", "br", + "canvas", "link", "meta", "script", diff --git a/packages/studio/src/components/editor/manualEditingAvailability.test.ts b/packages/studio/src/components/editor/manualEditingAvailability.test.ts index 3d30fcbcaa..e2cd9902a4 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.test.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.test.ts @@ -16,13 +16,13 @@ describe("manual editing availability", () => { vi.resetModules(); }); - it("enables inspector selection by default while motion and manual dragging stay opt-in", async () => { + it("enables inspector selection, motion panel, and manual editing by default", async () => { const availability = await loadAvailabilityWithEnv({}); expect(availability.STUDIO_PREVIEW_MANUAL_EDITING_ENABLED).toBe(false); expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(true); expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true); - expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(false); + expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(true); expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true); }); diff --git a/packages/studio/src/components/editor/manualEditingAvailability.ts b/packages/studio/src/components/editor/manualEditingAvailability.ts index f31e151d97..dc3add3860 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.ts @@ -44,7 +44,7 @@ export const STUDIO_INSPECTOR_PANELS_ENABLED = resolveStudioBooleanEnvFlag( export const STUDIO_MOTION_PANEL_ENABLED = resolveStudioBooleanEnvFlag( env, [STUDIO_MOTION_PANEL_ENV, "VITE_STUDIO_MOTION_PANEL_ENABLED"], - false, + true, ); export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED =