@@ -1907,6 +1900,14 @@ function ClipSegmentConfig(props: {
}) {
const { setProject, setEditorState, project } = useEditorContext();
+ // Local state for the input field
+ const [inputValue, setInputValue] = createSignal(props.segment.timescale.toString());
+
+ // Update input value when timescale changes externally (e.g., from slider or buttons)
+ createEffect(() => {
+ setInputValue(props.segment.timescale.toString());
+ });
+
return (
<>
@@ -1945,6 +1946,149 @@ function ClipSegmentConfig(props: {
Delete
+
}>
+
+
+
+ setProject(
+ "timeline",
+ "segments",
+ props.segmentIndex,
+ "timescale",
+ v[0]
+ )
+ }
+ minValue={0.1}
+ maxValue={10}
+ step={0.01}
+ formatTooltip={(v) => `${v.toFixed(2)}x`}
+ class="flex-1"
+ />
+
+ {
+ const value = e.currentTarget.value;
+ setInputValue(value);
+
+ const numValue = parseFloat(value);
+ if (!isNaN(numValue) && numValue > 0 && numValue <= 10) {
+ setProject(
+ "timeline",
+ "segments",
+ props.segmentIndex,
+ "timescale",
+ numValue
+ );
+ }
+ }}
+ onFocus={(e) => {
+ // Select all text on focus for easy replacement
+ e.currentTarget.select();
+ }}
+ onBlur={(e) => {
+ const value = parseFloat(e.currentTarget.value);
+ if (isNaN(value) || value <= 0) {
+ setInputValue(props.segment.timescale.toFixed(2));
+ } else if (value > 10) {
+ setProject(
+ "timeline",
+ "segments",
+ props.segmentIndex,
+ "timescale",
+ 10
+ );
+ setInputValue("10.00");
+ } else {
+ // Format to 2 decimal places on blur
+ setInputValue(value.toFixed(2));
+ }
+ }}
+ class="w-16 px-2 py-1 text-xs text-center border rounded-md bg-gray-2 text-gray-12"
+ min="0.01"
+ max="10"
+ step="0.01"
+ />
+ x
+
+
+
+
+
+
+
+
+
+
+
} />
diff --git a/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx b/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
index 2f3ea8128..329ad7df0 100644
--- a/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
+++ b/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
@@ -71,7 +71,7 @@ export function ClipTrack(
const relativeSegment = mergeProps(segment, () => ({
start: prevDuration(),
- end: segment.end - segment.start + prevDuration(),
+ end: prevDuration() + (segment.end - segment.start) / segment.timescale,
}));
const segmentX = useSegmentTranslateX(() => relativeSegment);
@@ -79,7 +79,7 @@ export function ClipTrack(
const segmentRecording = (s = i()) =>
editorInstance.recordings.segments[
- segments()[s].recordingSegment ?? 0
+ segments()[s].recordingSegment ?? 0
];
const marker = useSectionMarker(() => ({
@@ -112,9 +112,8 @@ export function ClipTrack(
@@ -277,7 +276,7 @@ export function ClipTrack(
segmentI === i()
? acc
: acc +
- (segment.end - segment.start) / segment.timescale,
+ (segment.end - segment.start) / segment.timescale,
0
);
@@ -290,7 +289,7 @@ export function ClipTrack(
const prevSegmentIsSameClip =
prevSegment?.recordingSegment !== undefined
? prevSegment.recordingSegment ===
- segment.recordingSegment
+ segment.recordingSegment
: false;
function update(event: MouseEvent) {
@@ -344,6 +343,12 @@ export function ClipTrack(
{" "}
{(segment.end - segment.start).toFixed(1)}s
+
+
+
+ {segment.timescale}x
+
+
);
@@ -367,7 +372,7 @@ export function ClipTrack(
segmentI === i()
? acc
: acc +
- (segment.end - segment.start) / segment.timescale,
+ (segment.end - segment.start) / segment.timescale,
0
);
@@ -375,7 +380,7 @@ export function ClipTrack(
const nextSegmentIsSameClip =
nextSegment?.recordingSegment !== undefined
? nextSegment.recordingSegment ===
- segment.recordingSegment
+ segment.recordingSegment
: false;
function update(event: MouseEvent) {
@@ -482,9 +487,8 @@ function Markings(props: { segment: TimelineSegment; prevDuration: number }) {
{(marking) => (