diff options
Diffstat (limited to 'apps/web/src/components/editor/panels/properties/tabs')
4 files changed, 70 insertions, 27 deletions
diff --git a/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx index a92f7be2..4dbc1a17 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx @@ -1,7 +1,13 @@ import { NumberField } from "@/components/ui/number-field";
import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants";
import { DEFAULTS } from "@/lib/timeline/defaults";
-import { clamp, isNearlyEqual } from "@/utils/math";
+import {
+ clamp,
+ formatNumberForDisplay,
+ getFractionDigitsForStep,
+ isNearlyEqual,
+ snapToStep,
+} from "@/utils/math";
import type { AudioElement, VideoElement } from "@/lib/timeline";
import { resolveNumberAtTime } from "@/lib/animation";
import { useElementPlayhead } from "../hooks/use-element-playhead";
@@ -18,6 +24,9 @@ import { SectionTitle,
} from "@/components/section";
+const VOLUME_STEP = 0.1;
+const VOLUME_FRACTION_DIGITS = getFractionDigitsForStep({ step: VOLUME_STEP });
+
export function AudioTab({
element,
trackId,
@@ -42,17 +51,24 @@ export function AudioTab({ propertyPath: "volume",
localTime,
isPlayheadWithinElementRange,
- displayValue: resolvedVolume.toFixed(1),
+ displayValue: formatNumberForDisplay({
+ value: resolvedVolume,
+ fractionDigits: VOLUME_FRACTION_DIGITS,
+ }),
parse: (input) => {
const parsed = parseFloat(input);
if (Number.isNaN(parsed)) {
return null;
}
- return clamp({ value: parsed, min: VOLUME_DB_MIN, max: VOLUME_DB_MAX });
+ return clamp({
+ value: snapToStep({ value: parsed, step: VOLUME_STEP }),
+ min: VOLUME_DB_MIN,
+ max: VOLUME_DB_MAX,
+ });
},
valueAtPlayhead: resolvedVolume,
- step: 0.1,
+ step: VOLUME_STEP,
buildBaseUpdates: ({ value }) => ({
volume: value,
}),
diff --git a/apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx index 6a5d0d0b..db8bd728 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx @@ -38,7 +38,12 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; -import { clamp, formatNumberForDisplay, snapToStep } from "@/utils/math"; +import { + clamp, + formatNumberForDisplay, + getFractionDigitsForStep, + snapToStep, +} from "@/utils/math"; import { Section, SectionContent, @@ -599,7 +604,7 @@ function MaskNumberField({ const max = isPercent ? 100 : param.max; const step = isPercent ? 1 : param.step; const displayValue = value * displayMultiplier; - const maxFractionDigits = step >= 1 ? 0 : Math.round(-Math.log10(step)); + const maxFractionDigits = getFractionDigitsForStep({ step }); const clampDisplay = (nextDisplayValue: number) => max !== undefined diff --git a/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx index 4434890e..da86c626 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx @@ -22,16 +22,28 @@ import { SectionTitle,
} from "@/components/section";
import { usePropertyDraft } from "../hooks/use-property-draft";
+import {
+ formatNumberForDisplay,
+ getFractionDigitsForStep,
+ snapToStep,
+} from "@/utils/math";
+
+const SPEED_STEP = 0.01;
+const SPEED_FRACTION_DIGITS = getFractionDigitsForStep({ step: SPEED_STEP });
function rateToDisplay({ rate }: { rate: number }): string {
- return rate.toFixed(2);
+ return formatNumberForDisplay({
+ value: rate,
+ fractionDigits: SPEED_FRACTION_DIGITS,
+ });
}
function parseSpeedInput({ input }: { input: string }): number | null {
const parsed = parseFloat(input);
if (Number.isNaN(parsed)) return null;
- const rounded = Math.round(parsed * 100) / 100;
- return clampRetimeRate({ rate: rounded });
+ return clampRetimeRate({
+ rate: snapToStep({ value: parsed, step: SPEED_STEP }),
+ });
}
function buildRetime({
@@ -56,24 +68,21 @@ export function SpeedTab({ const rate = clampRetimeRate({
rate: element.retime?.rate ?? DEFAULT_RETIME_RATE,
});
- const maintainPitchAvailable = canMaintainPitch({ rate });
+ const isPitchPreserveAvailable = canMaintainPitch({ rate });
const maintainPitch = element.retime?.maintainPitch ?? false;
const pendingRateRef = useRef(rate);
- const applyRetime = ({
+ const commitRetime = ({
rate: nextRate,
maintainPitch: nextMaintainPitch,
- pushHistory = true,
}: {
rate: number;
maintainPitch: boolean;
- pushHistory?: boolean;
}) => {
editor.timeline.updateElementRetime({
trackId,
elementId: element.id,
retime: buildRetime({ rate: nextRate, maintainPitch: nextMaintainPitch }),
- pushHistory,
});
};
@@ -82,10 +91,20 @@ export function SpeedTab({ parse: (input) => parseSpeedInput({ input }),
onPreview: (nextRate) => {
pendingRateRef.current = nextRate;
- applyRetime({ rate: nextRate, maintainPitch, pushHistory: false });
+ editor.timeline.previewElements({
+ updates: [
+ {
+ trackId,
+ elementId: element.id,
+ updates: {
+ retime: buildRetime({ rate: nextRate, maintainPitch }),
+ },
+ },
+ ],
+ });
},
onCommit: () => {
- applyRetime({ rate: pendingRateRef.current, maintainPitch });
+ commitRetime({ rate: pendingRateRef.current, maintainPitch });
},
});
@@ -115,20 +134,20 @@ export function SpeedTab({ onScrub={speedDraft.scrubTo}
onScrubEnd={speedDraft.commitScrub}
onReset={() =>
- applyRetime({ rate: DEFAULT_RETIME_RATE, maintainPitch })
+ commitRetime({ rate: DEFAULT_RETIME_RATE, maintainPitch })
}
isDefault={rate === DEFAULT_RETIME_RATE}
/>
</SectionField>
<div className="flex items-center justify-between">
- <span className="text-sm">Change pitch</span>
- <Switch
- checked={!maintainPitch}
- disabled={!maintainPitchAvailable}
- onCheckedChange={(checked) =>
- applyRetime({ rate, maintainPitch: !checked })
- }
- />
+ <span className="text-sm">Change pitch</span>
+ <Switch
+ checked={!maintainPitch}
+ disabled={!isPitchPreserveAvailable}
+ onCheckedChange={(checked) =>
+ commitRetime({ rate, maintainPitch: !checked })
+ }
+ />
</div>
</SectionFields>
</SectionContent>
diff --git a/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx index 45831dc0..be85bede 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx @@ -16,7 +16,7 @@ import { import { ColorPicker } from "@/components/ui/color-picker";
import { Button } from "@/components/ui/button";
import { uppercase } from "@/utils/string";
-import { clamp } from "@/utils/math";
+import { clamp, formatNumberForDisplay } from "@/utils/math";
import { useEditor } from "@/hooks/use-editor";
import { DEFAULT_COLOR } from "@/constants/project-constants";
import {
@@ -251,7 +251,10 @@ function SpacingSection({ });
const lineHeight = usePropertyDraft({
- displayValue: (element.lineHeight ?? DEFAULTS.text.lineHeight).toFixed(1),
+ displayValue: formatNumberForDisplay({
+ value: element.lineHeight ?? DEFAULTS.text.lineHeight,
+ fractionDigits: 1,
+ }),
parse: (input) => {
const parsed = parseFloat(input);
return Number.isNaN(parsed)
|
