Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions packages/studio/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export function StudioApp() {
const panelLayout = usePanelLayout({
rightCollapsed: initialUrlStateRef.current.rightCollapsed,
rightPanelTab: initialUrlStateRef.current.rightPanelTab,
rightPanelTabs: initialUrlStateRef.current.rightPanelTabs,
});
const editHistory = usePersistentEditHistory({ projectId });
const domEditSaveTimestampRef = useRef(0);
Expand Down Expand Up @@ -194,7 +195,7 @@ export function StudioApp() {
compositionPath: result!.compositionPath,
});
panelLayout.setRightCollapsed(false);
panelLayout.setRightPanelTab("block-params");
panelLayout.focusRightPanelTab("block-params");
}
})();
},
Expand Down Expand Up @@ -294,7 +295,7 @@ export function StudioApp() {
currentTime,
setSelectedTimelineElementId,
setRightCollapsed: panelLayout.setRightCollapsed,
setRightPanelTab: panelLayout.setRightPanelTab,
focusRightPanelTab: panelLayout.focusRightPanelTab,
showToast,
refreshPreviewDocumentVersion,
queueDomEditSave: manifestPersistence.queueDomEditSave,
Expand All @@ -308,7 +309,7 @@ export function StudioApp() {
projectIdRef: fileManager.projectIdRef,
previewIframe,
refreshKey,
rightPanelTab: panelLayout.rightPanelTab,
rightPanelTabs: panelLayout.rightPanelTabs,
applyStudioManualEditsToPreviewRef: manifestPersistence.applyStudioManualEditsToPreviewRef,
syncPreviewHistoryHotkey: appHotkeys.syncPreviewHistoryHotkey,
reloadPreview,
Expand Down Expand Up @@ -391,14 +392,17 @@ export function StudioApp() {
? readStudioMotionFromElement(domEditSession.domEditSelection.element)
: null;
const layersPanelActive =
STUDIO_INSPECTOR_PANELS_ENABLED && panelLayout.rightPanelTab === "layers";
STUDIO_INSPECTOR_PANELS_ENABLED && panelLayout.rightPanelTabs.includes("layers");
const designPanelActive =
STUDIO_INSPECTOR_PANELS_ENABLED && panelLayout.rightPanelTab === "design";
STUDIO_INSPECTOR_PANELS_ENABLED && panelLayout.rightPanelTabs.includes("design");
const motionPanelActive =
STUDIO_INSPECTOR_PANELS_ENABLED &&
STUDIO_MOTION_PANEL_ENABLED &&
panelLayout.rightPanelTab === "motion";
const inspectorPanelActive = layersPanelActive || designPanelActive || motionPanelActive;
panelLayout.rightPanelTabs.includes("motion");
const cssPanelActive =
STUDIO_INSPECTOR_PANELS_ENABLED && panelLayout.rightPanelTabs.includes("css");
const inspectorPanelActive =
layersPanelActive || designPanelActive || motionPanelActive || cssPanelActive;
const shouldShowSelectedDomBounds =
inspectorPanelActive && !panelLayout.rightCollapsed && !isPlaying;
const inspectorButtonActive =
Expand All @@ -413,7 +417,8 @@ export function StudioApp() {
compositionLoading,
refreshKey,
previewIframeRef,
rightPanelTab: panelLayout.rightPanelTab,
rightPanelTab: panelLayout.rightPanelFocusTab,
rightPanelTabs: panelLayout.rightPanelTabs,
rightCollapsed: panelLayout.rightCollapsed,
timelineVisible,
activeCompPathHydrated,
Expand Down Expand Up @@ -526,16 +531,13 @@ export function StudioApp() {
setCompositionLoading={setCompositionLoading}
shouldShowSelectedDomBounds={shouldShowSelectedDomBounds}
/>

{!panelLayout.rightCollapsed && (
<StudioRightPanel
selectedStudioMotion={selectedStudioMotion}
designPanelActive={designPanelActive}
motionPanelActive={motionPanelActive}
activeBlockParams={activeBlockParams}
onCloseBlockParams={() => {
setActiveBlockParams(null);
panelLayout.setRightPanelTab("design");
panelLayout.focusRightPanelTab("design");
}}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions packages/studio/src/components/StudioHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export function StudioHeader({
inspectorPanelActive,
}: StudioHeaderProps) {
const { projectId, editHistory, handleUndo, handleRedo } = useStudioContext();
const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
const { rightCollapsed, setRightCollapsed, ensureDesignVisible } = usePanelLayoutContext();
const { clearDomSelection } = useDomEditContext();

return (
Expand Down Expand Up @@ -217,7 +217,7 @@ export function StudioHeader({
onClick={() => {
if (!STUDIO_INSPECTOR_PANELS_ENABLED) return;
if (rightCollapsed || !inspectorPanelActive) {
setRightPanelTab("design");
ensureDesignVisible();
setRightCollapsed(false);
return;
}
Expand Down
147 changes: 147 additions & 0 deletions packages/studio/src/components/StudioRightPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// @vitest-environment happy-dom

import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, describe, expect, it, vi } from "vitest";
import { StudioRightPanel } from "./StudioRightPanel";

(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

const mockState = vi.hoisted(() => ({
inspectorEnabled: true,
motionEnabled: false,
rightPanelTabs: ["design", "renders"] as Array<
"design" | "renders" | "layers" | "css" | "motion"
>,
}));

vi.mock("./editor/manualEditingAvailability", () => ({
get STUDIO_INSPECTOR_PANELS_ENABLED() {
return mockState.inspectorEnabled;
},
get STUDIO_MOTION_PANEL_ENABLED() {
return mockState.motionEnabled;
},
}));

vi.mock("../contexts/PanelLayoutContext", () => ({
usePanelLayoutContext: () => ({
rightWidth: 420,
rightPanelTabs: mockState.rightPanelTabs,
rightPanelFocusTab: "design",
focusRightPanelTab: () => {},
toggleRightPanelTab: () => {},
handlePanelResizeStart: () => {},
handlePanelResizeMove: () => {},
handlePanelResizeEnd: () => {},
}),
}));

vi.mock("../contexts/StudioContext", () => ({
useStudioContext: () => ({
captionEditMode: false,
previewIframeRef: { current: null },
projectId: "demo",
activeCompPath: null,
compositionDimensions: { width: 1920, height: 1080 },
waitForPendingDomEditSaves: async () => {},
renderQueue: {
jobs: [],
deleteRender: () => {},
clearCompleted: () => {},
startRender: async () => {},
isRendering: false,
},
}),
}));

vi.mock("../contexts/DomEditContext", () => ({
useDomEditContext: () => ({
domEditSelection: null,
domEditGroupSelections: [],
copiedAgentPrompt: null,
clearDomSelection: () => {},
handleDomStyleCommit: () => {},
handleDomAttributeCommit: () => {},
handleDomPathOffsetCommit: () => {},
handleDomBoxSizeCommit: () => {},
handleDomRotationCommit: () => {},
handleDomTextCommit: () => {},
handleDomTextFieldStyleCommit: () => {},
handleDomAddTextField: () => {},
handleDomRemoveTextField: () => {},
handleAskAgent: () => {},
handleDomMotionCommit: () => {},
handleDomMotionClear: () => {},
applyDomSelection: () => {},
}),
}));

vi.mock("../contexts/FileManagerContext", () => ({
useFileManagerContext: () => ({
assets: [],
fontAssets: [],
handleImportFiles: () => {},
handleImportFonts: () => {},
}),
}));

vi.mock("./editor/PropertyPanel", () => ({
PropertyPanel: () => React.createElement("div", {}, "PropertyPanel"),
}));
vi.mock("./editor/MotionPanel", () => ({
MotionPanel: () => React.createElement("div", {}, "MotionPanel"),
}));
vi.mock("./editor/LayersPanel", () => ({
LayersPanel: () => React.createElement("div", {}, "LayersPanel"),
}));
vi.mock("./editor/LayerCssRulesPanel", () => ({
LayerCssRulesPanel: () => React.createElement("div", {}, "LayerCssRulesPanel"),
}));
vi.mock("./renders/RenderQueue", () => ({
RenderQueue: () => React.createElement("div", {}, "RenderQueue"),
}));
vi.mock("../captions/components/CaptionPropertyPanel", () => ({
CaptionPropertyPanel: () => React.createElement("div", {}, "CaptionPropertyPanel"),
}));

function renderPanel() {
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(React.createElement(StudioRightPanel, { selectedStudioMotion: null }));
});
return {
host,
cleanup: () =>
act(() => {
root.unmount();
host.remove();
}),
};
}

afterEach(() => {
document.body.innerHTML = "";
mockState.inspectorEnabled = true;
mockState.motionEnabled = false;
mockState.rightPanelTabs = ["design", "renders"];
});

describe("StudioRightPanel", () => {
it("renders inspector content and renders queue when both tabs are open", () => {
const { host, cleanup } = renderPanel();
expect(host.textContent).toContain("PropertyPanel");
expect(host.textContent).toContain("RenderQueue");
cleanup();
});

it("hides inspector content when inspector panels are disabled", () => {
mockState.inspectorEnabled = false;
const { host, cleanup } = renderPanel();
expect(host.textContent).not.toContain("PropertyPanel");
expect(host.textContent).toContain("RenderQueue");
cleanup();
});
});
Loading
Loading