mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): wire the flat Media group into the one-open accordion
This commit is contained in:
@@ -615,3 +615,119 @@ describe("PropertyPanel — flat Layout currentPct basis (currentPct follow-up f
|
|||||||
RENDER_TIMEOUT_MS,
|
RENDER_TIMEOUT_MS,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Media fixtures (Plan 4 Task 7): the three tag values resolveEditingSections
|
||||||
|
// turns `media` on for (video/audio/img). Each carries no text fields (so the
|
||||||
|
// Text group never renders) and sets `element` to a real media node so the
|
||||||
|
// FlatMediaSection reads a live media element. `as never` casts around the
|
||||||
|
// element-type mismatch with baseElement()'s HTMLDivElement.
|
||||||
|
function videoElement() {
|
||||||
|
return {
|
||||||
|
...baseElement(),
|
||||||
|
id: "s1-bg",
|
||||||
|
selector: "#s1-bg",
|
||||||
|
label: "S1 Background",
|
||||||
|
tagName: "video",
|
||||||
|
textFields: [],
|
||||||
|
element: document.createElement("video"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageElement() {
|
||||||
|
return {
|
||||||
|
...baseElement(),
|
||||||
|
id: "s1-img",
|
||||||
|
selector: "#s1-img",
|
||||||
|
label: "S1 Image",
|
||||||
|
tagName: "img",
|
||||||
|
textFields: [],
|
||||||
|
element: document.createElement("img"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function audioElement() {
|
||||||
|
return {
|
||||||
|
...baseElement(),
|
||||||
|
id: "s1-audio",
|
||||||
|
selector: "#s1-audio",
|
||||||
|
label: "S1 Audio",
|
||||||
|
tagName: "audio",
|
||||||
|
textFields: [],
|
||||||
|
element: document.createElement("audio"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// All FlatGroup titles currently mounted (open row + every collapsed row).
|
||||||
|
function flatGroupTitles(host: HTMLElement): string[] {
|
||||||
|
const open = Array.from(
|
||||||
|
host.querySelectorAll('[data-flat-group-open="true"] .text-panel-text-0'),
|
||||||
|
).map((el) => el.textContent ?? "");
|
||||||
|
const collapsed = Array.from(
|
||||||
|
host.querySelectorAll('[data-flat-group-collapsed="true"] .text-panel-text-2'),
|
||||||
|
).map((el) => el.textContent ?? "");
|
||||||
|
return [...open, ...collapsed];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("PropertyPanel — Media group (Plan 4)", () => {
|
||||||
|
it(
|
||||||
|
"renders the flat Media group and not the legacy MediaSection, for a video element",
|
||||||
|
async () => {
|
||||||
|
const { host, root } = await renderPanel(true, videoElement() as never);
|
||||||
|
// A Media FlatGroup exists (open or collapsed).
|
||||||
|
expect(flatGroupTitles(host)).toContain("Media");
|
||||||
|
// The legacy MediaSection renders its rows inside a `Section` whose
|
||||||
|
// data-panel-section slug is the media title ("video"/"image"/"audio").
|
||||||
|
// On the flat path it's fully replaced, so none of those may appear.
|
||||||
|
expect(host.querySelector('[data-panel-section="video"]')).toBeNull();
|
||||||
|
expect(host.querySelector('[data-panel-section="image"]')).toBeNull();
|
||||||
|
expect(host.querySelector('[data-panel-section="audio"]')).toBeNull();
|
||||||
|
act(() => root.unmount());
|
||||||
|
},
|
||||||
|
RENDER_TIMEOUT_MS,
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"one-open accordion: opening Media closes whichever other group was open, and vice versa (5-way exclusivity)",
|
||||||
|
async () => {
|
||||||
|
// videoElement() has canEditStyles: true and no text fields, so Style is
|
||||||
|
// the default-open group; Layout and Media render collapsed alongside it.
|
||||||
|
const { host, root } = await renderPanel(true, videoElement() as never);
|
||||||
|
expect(openGroupText(host)).toContain("Style");
|
||||||
|
|
||||||
|
// Opening Media closes Style.
|
||||||
|
openFlatGroup(host, "Media");
|
||||||
|
const afterMedia = openGroupText(host);
|
||||||
|
expect(afterMedia).toContain("Media");
|
||||||
|
expect(afterMedia).not.toContain("Style");
|
||||||
|
|
||||||
|
// Reverse direction: opening Layout closes Media — same shared openGroupId.
|
||||||
|
openFlatGroup(host, "Layout");
|
||||||
|
const afterLayout = openGroupText(host);
|
||||||
|
expect(afterLayout).toContain("Layout");
|
||||||
|
expect(afterLayout).not.toContain("Media");
|
||||||
|
act(() => root.unmount());
|
||||||
|
},
|
||||||
|
RENDER_TIMEOUT_MS,
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"gates the Media group exactly like the legacy MediaSection: present for video/img/audio, absent for a plain div/text element",
|
||||||
|
async () => {
|
||||||
|
for (const fixture of [videoElement, imageElement, audioElement]) {
|
||||||
|
const { host, root } = await renderPanel(true, fixture() as never);
|
||||||
|
expect(flatGroupTitles(host)).toContain("Media");
|
||||||
|
act(() => root.unmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseElement() is a plain <div> with text — sections.media is false, so
|
||||||
|
// no Media group (flat or legacy) may render.
|
||||||
|
const { host, root } = await renderPanel(true);
|
||||||
|
expect(flatGroupTitles(host)).not.toContain("Media");
|
||||||
|
expect(host.querySelector('[data-panel-section="video"]')).toBeNull();
|
||||||
|
expect(host.querySelector('[data-panel-section="image"]')).toBeNull();
|
||||||
|
expect(host.querySelector('[data-panel-section="audio"]')).toBeNull();
|
||||||
|
act(() => root.unmount());
|
||||||
|
},
|
||||||
|
RENDER_TIMEOUT_MS,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import { FlatTextSection } from "./propertyPanelFlatTextSection";
|
|||||||
import { FlatStyleSection } from "./propertyPanelFlatStyleSections";
|
import { FlatStyleSection } from "./propertyPanelFlatStyleSections";
|
||||||
import { FlatLayoutSection } from "./propertyPanelFlatLayoutSection";
|
import { FlatLayoutSection } from "./propertyPanelFlatLayoutSection";
|
||||||
import { FlatMotionSection } from "./propertyPanelFlatMotionSection";
|
import { FlatMotionSection } from "./propertyPanelFlatMotionSection";
|
||||||
|
import { FlatMediaSection } from "./propertyPanelFlatMediaSection";
|
||||||
import { deriveElementTiming } from "./propertyPanelFlatTimingDerivation";
|
import { deriveElementTiming } from "./propertyPanelFlatTimingDerivation";
|
||||||
import { createGsapLivePreview } from "./gsapLivePreview";
|
import { createGsapLivePreview } from "./gsapLivePreview";
|
||||||
import { formatTextFieldPreview, StyleSections } from "./propertyPanelSections";
|
import { formatTextFieldPreview, StyleSections } from "./propertyPanelSections";
|
||||||
import { STUDIO_GSAP_PANEL_ENABLED } from "./manualEditingAvailability";
|
import { STUDIO_GSAP_PANEL_ENABLED } from "./manualEditingAvailability";
|
||||||
import { ColorGradingSection } from "./propertyPanelColorGradingSection";
|
import { ColorGradingSection } from "./propertyPanelColorGradingSection";
|
||||||
import { MediaSection } from "./propertyPanelMediaSection";
|
|
||||||
|
|
||||||
type EditingSections = ReturnType<typeof resolveEditingSections>;
|
type EditingSections = ReturnType<typeof resolveEditingSections>;
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ const EMPTY_GSAP_EFFECT_HANDLERS = {
|
|||||||
* (same one-directional-import precedent as FlatTextSection). Rendered only
|
* (same one-directional-import precedent as FlatTextSection). Rendered only
|
||||||
* when STUDIO_FLAT_INSPECTOR_ENABLED is on; owns the one-open/pin group state.
|
* when STUDIO_FLAT_INSPECTOR_ENABLED is on; owns the one-open/pin group state.
|
||||||
*
|
*
|
||||||
* The Text/Style/Layout/Motion groups share the one-open accordion. The legacy
|
* The Text/Style/Layout/Motion/Media groups share the one-open accordion. The
|
||||||
* Media and Color-Grading sections render unchanged below the flat groups.
|
* legacy Color-Grading section renders unchanged below the flat groups.
|
||||||
*/
|
*/
|
||||||
// fallow-ignore-next-line complexity
|
// fallow-ignore-next-line complexity
|
||||||
export function PropertyPanelFlat({
|
export function PropertyPanelFlat({
|
||||||
@@ -215,7 +215,13 @@ export function PropertyPanelFlat({
|
|||||||
// switching the selection re-mounts this component and re-derives the
|
// switching the selection re-mounts this component and re-derives the
|
||||||
// default instead of preserving stale state across unrelated elements.
|
// default instead of preserving stale state across unrelated elements.
|
||||||
const [openGroupId, setOpenGroupId] = useState<string>(() =>
|
const [openGroupId, setOpenGroupId] = useState<string>(() =>
|
||||||
isTextEditableSelection(element) ? "text" : showEditableSections ? "style" : "layout",
|
isTextEditableSelection(element)
|
||||||
|
? "text"
|
||||||
|
: showEditableSections
|
||||||
|
? "style"
|
||||||
|
: sections.media
|
||||||
|
? "media"
|
||||||
|
: "layout",
|
||||||
);
|
);
|
||||||
const [pinnedGroupIds, setPinnedGroupIds] = useState<string[]>([]);
|
const [pinnedGroupIds, setPinnedGroupIds] = useState<string[]>([]);
|
||||||
|
|
||||||
@@ -427,7 +433,15 @@ export function PropertyPanelFlat({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sections.media && (
|
{sections.media && (
|
||||||
<MediaSection
|
<FlatGroup
|
||||||
|
title="Media"
|
||||||
|
isOpen={openGroupId === "media" || pinnedGroupIds.includes("media")}
|
||||||
|
isPinned={pinnedGroupIds.includes("media")}
|
||||||
|
onToggleOpen={() => toggleOpen("media")}
|
||||||
|
onTogglePin={() => togglePin("media")}
|
||||||
|
summary={element.tagName}
|
||||||
|
>
|
||||||
|
<FlatMediaSection
|
||||||
projectDir={projectDir}
|
projectDir={projectDir}
|
||||||
element={element}
|
element={element}
|
||||||
styles={styles}
|
styles={styles}
|
||||||
@@ -436,6 +450,7 @@ export function PropertyPanelFlat({
|
|||||||
onSetHtmlAttribute={onSetHtmlAttribute}
|
onSetHtmlAttribute={onSetHtmlAttribute}
|
||||||
onRemoveBackground={onRemoveBackground}
|
onRemoveBackground={onRemoveBackground}
|
||||||
/>
|
/>
|
||||||
|
</FlatGroup>
|
||||||
)}
|
)}
|
||||||
{showEditableSections && (
|
{showEditableSections && (
|
||||||
<StyleSections
|
<StyleSections
|
||||||
|
|||||||
Reference in New Issue
Block a user