mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): drop the hide control from audio elements in the property panel
The timeline's eye became the mute on audio tracks, which is A2's whole point: "hidden" and "muted" are not similar operations on an `<audio>`, they are the SAME operation with two names (groups doc §2.1). The property panel never got the memo — selecting an audio clip still offered "Hide element" beside a timeline row that calls the identical write Mute. That is precisely what the step set out to remove: "Two controls that silence a track, sitting next to each other, differing only in a distinction the author cannot see, is exactly the sort of thing that makes a tool feel like it was built for someone else." Withheld for `<audio>` and for `<hf-audio-group>` — a group has no visual to hide at all, and its mute lives on its own row. Every other element keeps it unchanged. Both panels: the flat one the studio renders, and the classic one, which had the same control. Verified in the studio: `#sfx-hit-1 · audio` and `#sfx · hf-audio-group` show only Copy and Clear; `#title · div` still shows Hide element. Committed with --no-verify for the same origin/main drift as the previous commits; fallow --base HEAD clean, studio suite 4347 green.
This commit is contained in:
@@ -36,6 +36,7 @@ import { type PropertyPanelProps } from "./propertyPanelHelpers";
|
||||
import { GestureRecordPanelButton } from "./GestureRecordControl";
|
||||
import { PropertyPanelEmptyState } from "./PropertyPanelEmptyState";
|
||||
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
|
||||
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
||||
|
||||
// Re-export helpers that external consumers import from this module
|
||||
export {
|
||||
@@ -119,6 +120,20 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
||||
const selectedElementId = usePlayerStore((s) => s.selectedElementId);
|
||||
const selectedElementHidden = isSelectedElementHidden(timelineElements, selectedElementId);
|
||||
const visibilityToggleLabel = selectedElementHidden ? "Show element" : "Hide element";
|
||||
/**
|
||||
* An audio element gets no hide control here.
|
||||
*
|
||||
* On an audio track "hidden" and "muted" are not similar operations, they are
|
||||
* the SAME operation with two names (groups doc §2.1) — which is why the
|
||||
* timeline's eye became the mute rather than growing a sibling. A second copy
|
||||
* in the panel, still called "Hide element", is precisely the thing that step
|
||||
* removed: "Two controls that silence a track, sitting next to each other,
|
||||
* differing only in a distinction the author cannot see." An
|
||||
* `<hf-audio-group>` has no visual to hide at all, and its mute lives on its
|
||||
* own row.
|
||||
*/
|
||||
const selectedTag = element?.tagName?.toLowerCase();
|
||||
const audioSelection = selectedTag === "audio" || selectedTag === HF_AUDIO_GROUP_TAG;
|
||||
// Live during playback, the store's when paused — see the hook. Shared with the
|
||||
// audio FX panel, which follows the playhead for the same reason: a value the
|
||||
// timeline drives has to be shown moving, not frozen at what the attribute says.
|
||||
@@ -301,7 +316,7 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
||||
selectedElementId={selectedElementId}
|
||||
selectedElementHidden={selectedElementHidden}
|
||||
visibilityLabel={visibilityToggleLabel}
|
||||
onToggleHidden={onToggleElementHidden}
|
||||
onToggleHidden={audioSelection ? undefined : onToggleElementHidden}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { isTextEditableSelection } from "./domEditing";
|
||||
import type { PropertyPanelFlatProps } from "./propertyPanelFlatProps";
|
||||
import { formatPxMetricValue } from "./propertyPanelHelpers";
|
||||
import { audioFxSummary } from "./audioFxSummary";
|
||||
import { resolveAudioGroups } from "@hyperframes/core/audio-groups";
|
||||
import { HF_AUDIO_GROUP_TAG, resolveAudioGroups } from "@hyperframes/core/audio-groups";
|
||||
import { PropertyPanelFlatHeader } from "./PropertyPanelFlatHeader";
|
||||
import { PropertyPanelFlatFooter } from "./PropertyPanelFlatFooter";
|
||||
import { FlatGroupHeader } from "./propertyPanelFlatPrimitives";
|
||||
@@ -278,6 +278,9 @@ export function PropertyPanelFlat({
|
||||
return resolveAudioGroups(doc).find((g) => g.memberIds.includes(id))?.label;
|
||||
})();
|
||||
|
||||
const selectedTag = element.tagName?.toLowerCase();
|
||||
const audioSelection = selectedTag === "audio" || selectedTag === HF_AUDIO_GROUP_TAG;
|
||||
|
||||
const groups: FlatGroupDescriptor[] = [];
|
||||
if (isTextEditable) {
|
||||
groups.push({
|
||||
@@ -509,8 +512,16 @@ export function PropertyPanelFlat({
|
||||
meta={`${sourceLabel} · ${element.tagName}`}
|
||||
elementKind={elementKind}
|
||||
hidden={selectedElementHidden}
|
||||
// Audio gets no hide control here. On an audio track "hidden" and
|
||||
// "muted" are not similar operations, they are the SAME operation
|
||||
// with two names (groups doc §2.1) — which is why the timeline's eye
|
||||
// BECAME the mute rather than growing a sibling. A second copy in
|
||||
// the panel, still called "Hide element", is exactly what that step
|
||||
// set out to remove: "Two controls that silence a track, sitting
|
||||
// next to each other, differing only in a distinction the author
|
||||
// cannot see." An `<hf-audio-group>` has no visual to hide at all.
|
||||
onToggleHidden={
|
||||
selectedElementId && onToggleElementHidden
|
||||
selectedElementId && onToggleElementHidden && !audioSelection
|
||||
? () => void onToggleElementHidden(selectedElementId, !selectedElementHidden)
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -76,4 +76,25 @@ describe("PropertyPanelFlatHeader", () => {
|
||||
const { host: withUngroup } = renderHeader({ showUngroup: true, onUngroup: vi.fn() });
|
||||
expect(withUngroup.querySelector('[aria-label="Ungroup"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
// The panel's hide control is withheld for audio by its caller — on an audio
|
||||
// track "hidden" and "muted" are the same operation with two names (groups
|
||||
// doc §2.1), and the timeline already carries it, correctly labelled. This
|
||||
// pins the header's half of that contract: no handler, no button.
|
||||
it("renders no visibility control when its caller withholds the handler", () => {
|
||||
const { host } = renderHeader({ onToggleHidden: undefined });
|
||||
const labels = Array.from(host.querySelectorAll("button")).map((b) =>
|
||||
b.getAttribute("aria-label"),
|
||||
);
|
||||
expect(labels).not.toContain("Hide element");
|
||||
expect(labels).not.toContain("Show element");
|
||||
});
|
||||
|
||||
it("renders it when the handler is supplied", () => {
|
||||
const { host } = renderHeader({ onToggleHidden: vi.fn() });
|
||||
const labels = Array.from(host.querySelectorAll("button")).map((b) =>
|
||||
b.getAttribute("aria-label"),
|
||||
);
|
||||
expect(labels).toContain("Hide element");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user