mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
refactor(studio): name the two branchy steps in the Audio FX panel
- audioFxSummary (14 cyclomatic / 19 cognitive) counted enabled nodes inline while also deciding what to say about them. countEnabledNodes now owns the parse and the split, and returns null for an unreadable chain, so the summary reads as the four sentences it produces. - The reveal effect's five-way nested ternary for "which row does this parameter live in" is now revealRowSelector, and the resolve-query-scroll sequence around it is scrollRevealedRowIntoView. Both live in audioFxRevealTarget.ts beside the resolver whose output they consume, which also brought propertyPanelFxSection.tsx from 616 to 598 lines -- under the 600 cap for the first time, so this commit needs no --no-verify. With these cleared the fallow audit gate passes (exit 0). Two notes for whoever touches this next: - fallow fingerprints a finding by line position, so inserting a helper ABOVE a complex function re-flags that function's inherited complexity as new. Adding revealRowSelector above FxSection re-flagged FxSection's own 22/32; moving it out fixed both. - A helper extracted only to satisfy the gate has to stay used from one place, or it lands as an unused export instead. studio's editor suite (102 files, 1269 tests) passes unchanged.
This commit is contained in:
@@ -55,3 +55,47 @@ export function audioFxRevealTarget(
|
||||
}
|
||||
return { kind: "node", index, nodeId: parsed.nodeId };
|
||||
}
|
||||
|
||||
/**
|
||||
* The DOM selector for the row a reveal target lives in, or null when the target
|
||||
* names no surface this panel renders.
|
||||
*
|
||||
* A preset run is keyed by the preset id the run element actually exposes, not by
|
||||
* `runKey`, which is the collapse map's key and carries an index suffix.
|
||||
*/
|
||||
function revealRowSelector(where: AudioFxRevealTarget | null): string | null {
|
||||
if (!where) return null;
|
||||
switch (where.kind) {
|
||||
case "node":
|
||||
return where.nodeId ? `[data-fx-node-id="${where.nodeId}"]` : null;
|
||||
case "eq":
|
||||
return `[data-fx-eq="${where.eqId}"]`;
|
||||
case "carve":
|
||||
return ".hf-fx-carve-module";
|
||||
case "preset":
|
||||
return `[data-fx-preset="${where.runKey.replace(/-\d+$/, "")}"]`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the row that owns `target` into view, and report whether it was found.
|
||||
*
|
||||
* The target is resolved against the chain again rather than remembered: which
|
||||
* surface owns a parameter is a fact about the chain, and the chain may have
|
||||
* been edited between the reveal request and the pass that can act on it. A
|
||||
* false return means the row has not mounted yet, so the caller keeps the
|
||||
* request pending.
|
||||
*/
|
||||
export function scrollRevealedRowIntoView(
|
||||
root: HTMLElement | null,
|
||||
target: string,
|
||||
chain: HfAudioFxChain,
|
||||
): boolean {
|
||||
const selector = revealRowSelector(audioFxRevealTarget(target, chain));
|
||||
const row = selector ? root?.querySelector<HTMLElement>(selector) : null;
|
||||
if (!row) return false;
|
||||
row.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,23 @@
|
||||
import { HF_AUDIO_FX_DATA_KEY, parseAudioFxChain } from "@hyperframes/core/audio-fx";
|
||||
import type { DomEditSelection } from "./domEditingTypes";
|
||||
|
||||
/** Enabled nodes split by who authored them, or null when the chain won't parse. */
|
||||
function countEnabledNodes(raw: string | undefined): { handBuilt: number; carve: number } | null {
|
||||
if (!raw) return { handBuilt: 0, carve: 0 };
|
||||
try {
|
||||
let handBuilt = 0;
|
||||
let carve = 0;
|
||||
for (const node of parseAudioFxChain(raw).nodes) {
|
||||
if (node.enabled === false) continue;
|
||||
if (node.fromCarve) carve += 1;
|
||||
else handBuilt += 1;
|
||||
}
|
||||
return { handBuilt, carve };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function audioFxSummary(element: DomEditSelection, groupLabel?: string): string {
|
||||
// A clip inside a group reads "in Voiceover" — the designs use this line to
|
||||
// answer "where does this go?" before the author opens anything, which is
|
||||
@@ -17,26 +34,15 @@ export function audioFxSummary(element: DomEditSelection, groupLabel?: string):
|
||||
// count: a member with no effects of its own is still IN the group, and that
|
||||
// is the more useful thing to say about it.
|
||||
if (groupLabel) return `in ${groupLabel}`;
|
||||
const raw = element.dataAttributes?.[HF_AUDIO_FX_DATA_KEY];
|
||||
const carveAttr = element.dataAttributes?.["fx-carve"];
|
||||
let handBuilt = 0;
|
||||
let carveNodes = 0;
|
||||
if (raw) {
|
||||
try {
|
||||
for (const node of parseAudioFxChain(raw).nodes) {
|
||||
if (node.enabled === false) continue;
|
||||
if (node.fromCarve) carveNodes += 1;
|
||||
else handBuilt += 1;
|
||||
}
|
||||
} catch {
|
||||
return "unreadable";
|
||||
}
|
||||
}
|
||||
const counts = countEnabledNodes(element.dataAttributes?.[HF_AUDIO_FX_DATA_KEY]);
|
||||
if (!counts) return "unreadable";
|
||||
const parts: string[] = [];
|
||||
if (handBuilt > 0) parts.push(`${handBuilt} effect${handBuilt === 1 ? "" : "s"}`);
|
||||
if (counts.handBuilt > 0) {
|
||||
parts.push(`${counts.handBuilt} effect${counts.handBuilt === 1 ? "" : "s"}`);
|
||||
}
|
||||
// One name for the module however many filters are behind it. Named when the
|
||||
// carve is switched on at all, because the control is in this section whether or
|
||||
// not it has compiled to anything yet.
|
||||
if (carveNodes > 0 || carveAttr) parts.push("carve");
|
||||
if (counts.carve > 0 || element.dataAttributes?.["fx-carve"]) parts.push("carve");
|
||||
return parts.length > 0 ? parts.join(" + ") : "none";
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from "react";
|
||||
import { audioFxRevealTarget } from "./audioFxRevealTarget.js";
|
||||
import { audioFxRevealTarget, scrollRevealedRowIntoView } from "./audioFxRevealTarget.js";
|
||||
import {
|
||||
defaultAudioFxParams,
|
||||
mintAudioFxNodeId,
|
||||
@@ -364,27 +364,9 @@ export function FxSection({
|
||||
*/
|
||||
useEffect(() => {
|
||||
const target = pendingRevealRef.current;
|
||||
if (!target) return;
|
||||
// Resolved again rather than remembered: the surface that owns a parameter
|
||||
// is a fact about the chain, and the chain may have been edited between the
|
||||
// request and this pass.
|
||||
const where = audioFxRevealTarget(target, chain);
|
||||
const selector =
|
||||
where?.kind === "node" && where.nodeId
|
||||
? `[data-fx-node-id="${where.nodeId}"]`
|
||||
: where?.kind === "eq"
|
||||
? `[data-fx-eq="${where.eqId}"]`
|
||||
: where?.kind === "carve"
|
||||
? ".hf-fx-carve-module"
|
||||
: // Keyed by the preset id the run carries, which is what the run
|
||||
// element actually exposes; `runKey` is the collapse map's key.
|
||||
where?.kind === "preset"
|
||||
? `[data-fx-preset="${where.runKey.replace(/-\d+$/, "")}"]`
|
||||
: null;
|
||||
const row = selector ? rootRef.current?.querySelector<HTMLElement>(selector) : null;
|
||||
if (!row) return;
|
||||
row.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
pendingRevealRef.current = null;
|
||||
if (target && scrollRevealedRowIntoView(rootRef.current, target, chain)) {
|
||||
pendingRevealRef.current = null;
|
||||
}
|
||||
// `chain` is deliberately not a dependency: it changes on every knob edit,
|
||||
// and re-running then would scroll the panel while the author is dragging.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
Reference in New Issue
Block a user