diff options
Diffstat (limited to 'apps/web/src/timeline/components')
| -rw-r--r-- | apps/web/src/timeline/components/audio-tab.tsx | 151 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/audio-volume-line.tsx | 13 | ||||
| -rw-r--r-- | apps/web/src/timeline/components/timeline-element.tsx | 8 |
3 files changed, 10 insertions, 162 deletions
diff --git a/apps/web/src/timeline/components/audio-tab.tsx b/apps/web/src/timeline/components/audio-tab.tsx deleted file mode 100644 index 1e47dcc8..00000000 --- a/apps/web/src/timeline/components/audio-tab.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { NumberField } from "@/components/ui/number-field"; -import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants"; -import { isSourceAudioSeparated } from "@/timeline/audio-separation"; -import { DEFAULTS } from "@/timeline/defaults"; -import { - clamp, - formatNumberForDisplay, - getFractionDigitsForStep, - isNearlyEqual, - snapToStep, -} from "@/utils/math"; -import type { AudioElement, VideoElement } from "@/timeline"; -import { resolveNumberAtTime } from "@/animation/values"; -import { useEditor } from "@/editor/use-editor"; -import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; -import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; -import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { VolumeHighIcon } from "@hugeicons/core-free-icons"; -import { - Section, - SectionContent, - SectionField, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; - -const VOLUME_STEP = 0.1; -const VOLUME_FRACTION_DIGITS = getFractionDigitsForStep({ step: VOLUME_STEP }); - -export function AudioTab({ - element, - trackId, -}: { - element: AudioElement | VideoElement; - trackId: string; -}) { - const editor = useEditor(); - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedVolume = resolveNumberAtTime({ - baseValue: element.volume ?? DEFAULTS.element.volume, - animations: element.animations, - propertyPath: "volume", - localTime, - }); - const volume = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "volume", - localTime, - isPlayheadWithinElementRange, - displayValue: formatNumberForDisplay({ - value: resolvedVolume, - fractionDigits: VOLUME_FRACTION_DIGITS, - }), - parse: (input) => { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) { - return null; - } - - return clamp({ - value: snapToStep({ value: parsed, step: VOLUME_STEP }), - min: VOLUME_DB_MIN, - max: VOLUME_DB_MAX, - }); - }, - valueAtPlayhead: resolvedVolume, - step: VOLUME_STEP, - buildBaseUpdates: ({ value }) => ({ - volume: value, - }), - }); - const isDefault = - volume.hasAnimatedKeyframes && isPlayheadWithinElementRange - ? isNearlyEqual({ - leftValue: resolvedVolume, - rightValue: DEFAULTS.element.volume, - }) - : (element.volume ?? DEFAULTS.element.volume) === DEFAULTS.element.volume; - const isSeparated = - element.type === "video" && isSourceAudioSeparated({ element }); - - return ( - <> - {isSeparated && ( - <div className="mx-4 mt-4 rounded-md border bg-muted/30 p-3"> - <p className="text-sm">Audio has been separated.</p> - <Button - className="mt-3" - size="sm" - variant="secondary" - onClick={() => - editor.timeline.toggleSourceAudioSeparation({ - trackId, - elementId: element.id, - }) - } - > - Recover audio - </Button> - </div> - )} - <Section collapsible sectionKey={`${element.id}:audio`}> - <SectionHeader> - <SectionTitle>Audio</SectionTitle> - </SectionHeader> - <SectionContent> - <SectionFields> - <SectionField - label="Volume" - beforeLabel={ - <KeyframeToggle - isActive={volume.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle volume keyframe" - onToggle={volume.toggleKeyframe} - /> - } - > - <NumberField - icon={<HugeiconsIcon icon={VolumeHighIcon} />} - value={volume.displayValue} - onFocus={volume.onFocus} - onChange={volume.onChange} - onBlur={volume.onBlur} - dragSensitivity="slow" - scrubClamp={{ min: VOLUME_DB_MIN, max: VOLUME_DB_MAX }} - onScrub={volume.scrubTo} - onScrubEnd={volume.commitScrub} - onReset={() => - volume.commitValue({ - value: DEFAULTS.element.volume, - }) - } - isDefault={isDefault} - suffix="dB" - /> - </SectionField> - </SectionFields> - </SectionContent> - </Section> - </> - ); -} diff --git a/apps/web/src/timeline/components/audio-volume-line.tsx b/apps/web/src/timeline/components/audio-volume-line.tsx index ac2fd32a..659bc5dc 100644 --- a/apps/web/src/timeline/components/audio-volume-line.tsx +++ b/apps/web/src/timeline/components/audio-volume-line.tsx @@ -3,13 +3,12 @@ import { useCallback, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useEditor } from "@/editor/use-editor"; -import { DEFAULTS } from "@/timeline/defaults"; import { getDbFromLinePos, getLinePosFromDb, } from "@/timeline/audio-display"; import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants"; -import { hasAnimatedVolume } from "@/timeline/audio-state"; +import { getElementVolume, hasAnimatedVolume } from "@/timeline/audio-state"; import type { AudioElement } from "@/timeline/types"; import { clamp, @@ -60,10 +59,8 @@ export function AudioVolumeLine({ const editor = useEditor(); const surfaceRef = useRef<HTMLDivElement>(null); const activePointerIdRef = useRef<number | null>(null); - const startVolumeRef = useRef(element.volume ?? DEFAULTS.element.volume); - const lastPreviewVolumeRef = useRef( - element.volume ?? DEFAULTS.element.volume, - ); + const startVolumeRef = useRef(getElementVolume({ element })); + const lastPreviewVolumeRef = useRef(getElementVolume({ element })); const hasChangedRef = useRef(false); const [isDragging, setIsDragging] = useState(false); const [tooltipClientPos, setTooltipClientPos] = useState<{ @@ -72,7 +69,7 @@ export function AudioVolumeLine({ } | null>(null); const hasAnimatedEnvelope = hasAnimatedVolume({ element }); - const currentVolume = element.volume ?? DEFAULTS.element.volume; + const currentVolume = getElementVolume({ element }); const lineTop = `${getLinePosFromDb({ db: currentVolume })}%`; const volumeLabel = `${formatNumberForDisplay({ @@ -96,7 +93,7 @@ export function AudioVolumeLine({ { trackId, elementId: element.id, - updates: { volume: nextVolume }, + updates: { params: { volume: nextVolume } }, }, ], }); diff --git a/apps/web/src/timeline/components/timeline-element.tsx b/apps/web/src/timeline/components/timeline-element.tsx index be8409f3..74f026dc 100644 --- a/apps/web/src/timeline/components/timeline-element.tsx +++ b/apps/web/src/timeline/components/timeline-element.tsx @@ -47,7 +47,7 @@ import { getSourceAudioActionLabel, isSourceAudioSeparated, } from "@/timeline/audio-separation"; -import { buildWaveformGainSamples } from "@/timeline/audio-state"; +import { buildWaveformGainSamples, isElementMuted } from "@/timeline/audio-state"; import { getTimelinePixelsPerSecond } from "@/timeline"; import { buildWaveformSourceKey } from "@/media/waveform-summary"; import { addMediaTime, type MediaTime, TICKS_PER_SECOND } from "@/wasm"; @@ -335,7 +335,7 @@ export function TimelineElement({ } }; - const isMuted = canElementHaveAudio(element) && element.muted === true; + const isMuted = canElementHaveAudio(element) && isElementMuted({ element }); const canToggleCurrentSourceAudio = selectedElements.length === 1 && isCurrentElementSelected && @@ -908,7 +908,9 @@ function TextElementContent({ }) { return ( <div className="flex size-full items-center justify-start pl-2"> - <span className="truncate text-xs text-white">{element.content}</span> + <span className="truncate text-xs text-white"> + {typeof element.params.content === "string" ? element.params.content : ""} + </span> </div> ); } |
