Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 59 additions & 8 deletions packages/timeline/src/ClapTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Stats } from "@react-three/drei"
import {
TimelineControls,
HorizontalScroller,
TimelineQuickActions,
Timeline
} from "@/components"
import { ClapProject, isValidNumber } from "@aitube/clap"
Expand All @@ -20,7 +21,7 @@ import { cn } from "./utils"
import { TimelineCamera } from "./components/camera"
import { useTimeline } from "./hooks"
import { topBarTimeScaleHeight } from "./constants/themes"
import { TimelineStore } from "./types"
import { SegmentEditionStatus, TimelineStore } from "./types"

export function ClapTimeline({
clap,
Expand Down Expand Up @@ -69,12 +70,52 @@ export function ClapTimeline({

const handleMouseMove = (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement>) => {
const timeline: TimelineStore = useTimeline.getState()
const { editedSegment } = timeline

// do something based on the current status of the edited segment
// for instance if the edited segment is being grabbed,
// we are going to want to display the segments that are around it
// console.log(`TODO @julian: implement edit here`)
const {
editedSegment,
cellWidth,
durationInMsPerStep,
getVerticalCellPosition,
tracks,
scrollX,
moveSegment,
} = timeline

if (editedSegment?.editionStatus === SegmentEditionStatus.DRAGGING && canvas) {
const rect = canvas.getBoundingClientRect()
const clientX = "touches" in event ? event.touches[0]?.clientX : event.clientX
const clientY = "touches" in event ? event.touches[0]?.clientY : event.clientY

if (typeof clientX === "number" && typeof clientY === "number") {
const localX = clientX - rect.left
const localY = clientY - rect.top
const pointerTimeInMs = Math.round(((localX + scrollX) / cellWidth) * durationInMsPerStep)
const segmentDurationInMs = editedSegment.endTimeInMs - editedSegment.startTimeInMs
const startTimeInMs = Math.max(
0,
pointerTimeInMs - Math.round(segmentDurationInMs / 2)
)

let track = editedSegment.track
const trackY = Math.max(0, localY - topBarTimeScaleHeight)
let top = 0
for (const candidate of tracks) {
const height = timeline.getCellHeight(candidate.id)
const trackTop = top
const trackBottom = top + height
if (trackY >= trackTop && trackY <= trackBottom) {
track = candidate.id
break
}
top = getVerticalCellPosition(0, candidate.id + 1)
}

moveSegment({
segment: editedSegment,
startTimeInMs,
track,
})
}
}

// since we are un frameloop="demand" mode, we need to manual invalidate the scene
invalidate()
Expand All @@ -83,6 +124,13 @@ export function ClapTimeline({
return false
}

const handlePointerRelease = () => {
const { editedSegment, setEditedSegment } = useTimeline.getState()
if (editedSegment?.editionStatus === SegmentEditionStatus.DRAGGING) {
setEditedSegment({ segment: undefined })
}
}

const handleWheel = (event: React.WheelEvent<HTMLDivElement>) => {
const rect = canvas?.getBoundingClientRect()
if (!rect) { return }
Expand Down Expand Up @@ -110,7 +158,7 @@ export function ClapTimeline({

return (
<div
className={cn(`w-full h-full`, className)}
className={cn(`relative w-full h-full`, className)}
style={{
backgroundColor: theme.grid.backgroundColor
}}>
Expand All @@ -121,6 +169,7 @@ export function ClapTimeline({
{({ height, width }: Size) => (
<div className="flex flex-grow flex-row w-full h-full">
<div className="flex flex-grow flex-col w-full h-full">
<TimelineQuickActions />
<HorizontalScroller />
<Canvas
ref={(canvas) => {
Expand Down Expand Up @@ -155,6 +204,8 @@ export function ClapTimeline({
onWheel={handleWheel}
onMouseMove={handleMouseMove}
onTouchMove={handleMouseMove}
onMouseUp={handlePointerRelease}
onTouchEnd={handlePointerRelease}
>
<TimelineCamera />
<TimelineControls
Expand Down
59 changes: 59 additions & 0 deletions packages/timeline/src/components/controls/TimelineQuickActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useState } from "react"
import { ClapSegmentCategory } from "@aitube/clap"

import { useTimeline } from "@/hooks"

const quickActionCategories = [
ClapSegmentCategory.VIDEO,
ClapSegmentCategory.IMAGE,
ClapSegmentCategory.DIALOGUE,
ClapSegmentCategory.MUSIC,
ClapSegmentCategory.SOUND,
ClapSegmentCategory.CAMERA,
ClapSegmentCategory.ACTION,
ClapSegmentCategory.GENERIC,
]

export function TimelineQuickActions() {
const [category, setCategory] = useState<ClapSegmentCategory>(ClapSegmentCategory.VIDEO)
const addTrack = useTimeline((s) => s.addTrack)
const createClip = useTimeline((s) => s.createClip)
const cursorTimestampAtInMs = useTimeline((s) => s.cursorTimestampAtInMs)

return (
<div
className="absolute right-3 top-3 z-10 flex items-center gap-2 rounded-md border border-neutral-700/70 bg-neutral-950/85 px-3 py-2 text-xs text-white shadow-lg"
onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => event.stopPropagation()}
>
<label className="flex items-center gap-2">
<span className="font-semibold text-neutral-300">Type</span>
<select
className="rounded border border-neutral-700 bg-neutral-900 px-2 py-1 text-white"
value={category}
onChange={(event) => setCategory(event.target.value as ClapSegmentCategory)}
>
{quickActionCategories.map((candidate) => (
<option key={candidate} value={candidate}>
{candidate}
</option>
))}
</select>
</label>
<button
className="rounded bg-neutral-800 px-2 py-1 font-semibold hover:bg-neutral-700"
type="button"
onClick={() => addTrack({ category })}
>
Add track
</button>
<button
className="rounded bg-yellow-500 px-2 py-1 font-semibold text-neutral-950 hover:bg-yellow-400"
type="button"
onClick={() => createClip({ category, startTimeInMs: cursorTimestampAtInMs })}
>
Add clip
</button>
</div>
)
}
3 changes: 2 additions & 1 deletion packages/timeline/src/components/controls/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { TimelineControls } from "./TimelineControls"
export { TimelineControls } from "./TimelineControls"
export { TimelineQuickActions } from "./TimelineQuickActions"
4 changes: 2 additions & 2 deletions packages/timeline/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export {
type SpecializedCellProps
} from "./cells"

export { TimelineControls } from "./controls"
export { TimelineControls, TimelineQuickActions } from "./controls"
export { HorizontalScroller, VerticalScroller } from "./scroller"
export { Timeline, TopBarTimeScale, Cells, Grid, type JumpAt } from "./timeline"
export { Timeline, TopBarTimeScale, Cells, Grid, type JumpAt } from "./timeline"
Loading