summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/properties
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components/editor/panels/properties')
-rw-r--r--apps/web/src/components/editor/panels/properties/components/property-param-field.tsx8
-rw-r--r--apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx24
-rw-r--r--apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx9
-rw-r--r--apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx57
-rw-r--r--apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx7
5 files changed, 76 insertions, 29 deletions
diff --git a/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx b/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx
index e69c6918..9cc2b02e 100644
--- a/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx
+++ b/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx
@@ -1,7 +1,11 @@
"use client";
import type { ParamDefinition, NumberParamDefinition } from "@/lib/params";
-import { formatNumberForDisplay, snapToStep } from "@/utils/math";
+import {
+ formatNumberForDisplay,
+ getFractionDigitsForStep,
+ snapToStep,
+} from "@/utils/math";
import { SectionField } from "@/components/section";
import { NumberField } from "@/components/ui/number-field";
import { Switch } from "@/components/ui/switch";
@@ -156,7 +160,7 @@ function NumberParamField({
onPreview(clamped / displayMultiplier);
};
- const maxFractionDigits = step >= 1 ? 0 : Math.round(-Math.log10(step));
+ const maxFractionDigits = getFractionDigitsForStep({ step });
const draft = usePropertyDraft({
displayValue: formatNumberForDisplay({
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)