Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
60 changes: 50 additions & 10 deletions src/components/videoEdit/VideoCropOverlay.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<template>
<div
ref="rootEl"
class="pointer-events-none absolute inset-0 overflow-hidden"
class="pointer-events-none absolute inset-0"
data-testid="video-crop-overlay"
>
<div
:class="
cn(
'pointer-events-auto absolute cursor-move border-2 border-white shadow-[0_0_0_9999px_rgba(0,0,0,0.5)]',
'pointer-events-auto absolute -m-0.5 box-content cursor-move border-2 border-white shadow-[0_0_0_9999px_rgba(0,0,0,0.5)]',
disabled && 'pointer-events-none opacity-60'
)
"
Expand All @@ -17,11 +17,26 @@
@pointerdown.stop="startDrag('move', $event)"
/>
<div
v-for="handle in HANDLES"
v-for="handle in edgeHandles"
:key="handle.dir"
:class="
cn(
'pointer-events-auto absolute size-2.5 -translate-1/2 border border-black/40 bg-white',
'pointer-events-auto absolute',
handle.strip,
handle.cursor,
disabled && 'pointer-events-none'
)
"
:style="edgeStyle(handle.dir)"
:data-testid="`crop-handle-${handle.dir}`"
@pointerdown.stop="startDrag(handle.dir, $event)"
/>
Comment thread
jtydhr88 marked this conversation as resolved.
<div
v-for="handle in CORNER_HANDLES"
:key="handle.dir"
:class="
cn(
'pointer-events-auto absolute size-2.5 -translate-1/2 rounded-sm bg-white/80',
handle.cursor,
disabled && 'pointer-events-none opacity-60'
)
Expand All @@ -41,15 +56,22 @@ import type { CropResizeDir } from '@/composables/video/useCropBoxEditor'
import type { Bounds } from '@/renderer/core/layout/types'
import { cn } from '@comfyorg/tailwind-utils'

const HANDLES: Array<{ dir: CropResizeDir; cursor: string }> = [
const CORNER_HANDLES: Array<{ dir: CropResizeDir; cursor: string }> = [
{ dir: 'nw', cursor: 'cursor-nwse-resize' },
{ dir: 'n', cursor: 'cursor-ns-resize' },
{ dir: 'ne', cursor: 'cursor-nesw-resize' },
{ dir: 'e', cursor: 'cursor-ew-resize' },
{ dir: 'se', cursor: 'cursor-nwse-resize' },
{ dir: 's', cursor: 'cursor-ns-resize' },
{ dir: 'sw', cursor: 'cursor-nesw-resize' },
{ dir: 'w', cursor: 'cursor-ew-resize' }
{ dir: 'sw', cursor: 'cursor-nesw-resize' }
]

const EDGE_HANDLES: Array<{
dir: CropResizeDir
cursor: string
strip: string
}> = [
{ dir: 'n', cursor: 'cursor-ns-resize', strip: 'h-2 -translate-y-1/2' },
{ dir: 'e', cursor: 'cursor-ew-resize', strip: 'w-2 -translate-x-1/2' },
{ dir: 's', cursor: 'cursor-ns-resize', strip: 'h-2 -translate-y-1/2' },
{ dir: 'w', cursor: 'cursor-ew-resize', strip: 'w-2 -translate-x-1/2' }
]

const {
Expand Down Expand Up @@ -101,4 +123,22 @@ function handleStyle(dir: CropResizeDir) {
: y + height / 2
return { left: pct(cx, sourceWidth), top: pct(cy, sourceHeight) }
}

const edgeHandles = computed(() => (lockedRatio != null ? [] : EDGE_HANDLES))

function edgeStyle(dir: CropResizeDir) {
const { x, y, width, height } = bounds.value
if (dir === 'n' || dir === 's') {
return {
left: pct(x, sourceWidth),
top: pct(dir === 'n' ? y : y + height, sourceHeight),
width: pct(width, sourceWidth)
}
}
return {
left: pct(dir === 'w' ? x : x + width, sourceWidth),
top: pct(y, sourceHeight),
height: pct(height, sourceHeight)
}
}
</script>
87 changes: 42 additions & 45 deletions src/components/videoEdit/VideoEditPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@
<div
v-else
data-testid="video-preview-container"
class="relative w-full"
:style="videoAspectRatioStyle"
:class="
cn(
'overflow-hidden rounded-lg bg-node-component-surface',
hasCrop && 'p-1.5'
)
"
>
<div
class="relative size-full overflow-hidden rounded-lg bg-node-component-surface"
>
<div class="relative w-full" :style="videoAspectRatioStyle">
<video
ref="videoRef"
data-testid="video-preview"
:src="videoUrl"
:muted="isMuted"
:controls="isFullscreen"
class="size-full object-contain"
class="block size-full object-contain"
preload="metadata"
crossorigin="anonymous"
playsinline
Expand Down Expand Up @@ -183,48 +185,43 @@
:widget="endFrameWidget"
/>

<div
v-if="hasCrop"
class="col-span-full grid grid-cols-subgrid items-center"
>
<label class="truncate text-node-component-slot-text">
<div v-if="hasCrop" class="col-span-full flex items-center gap-2">
<label class="text-xs text-muted-foreground">
{{ t('imageCrop.ratio') }}
</label>
<div class="flex min-w-0 items-center gap-1">
<Select v-model="selectedRatio" :disabled="!canLockRatio">
<SelectTrigger
class="h-8 min-w-0 flex-1 text-xs"
:aria-label="t('imageCrop.ratio')"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="key in ratioKeys" :key="key" :value="key">
{{ key === 'custom' ? t('imageCrop.custom') : key }}
</SelectItem>
</SelectContent>
</Select>
<Button
size="icon"
:variant="isLockEnabled ? 'primary' : 'secondary'"
class="size-8 shrink-0"
:disabled="!canLockRatio"
:aria-label="
<Select v-model="selectedRatio" :disabled="!canLockRatio">
<SelectTrigger
class="h-7 w-24 text-xs"
:aria-label="t('imageCrop.ratio')"
>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="key in ratioKeys" :key="key" :value="key">
{{ key === 'custom' ? t('imageCrop.custom') : key }}
</SelectItem>
</SelectContent>
</Select>
<Button
size="icon"
:variant="isLockEnabled ? 'primary' : 'secondary'"
class="size-7"
:disabled="!canLockRatio"
:aria-label="
isLockEnabled
? t('imageCrop.unlockRatio')
: t('imageCrop.lockRatio')
"
@click="isLockEnabled = !isLockEnabled"
>
<i
:class="
isLockEnabled
? t('imageCrop.unlockRatio')
: t('imageCrop.lockRatio')
? 'icon-[lucide--lock] size-3.5'
: 'icon-[lucide--lock-open] size-3.5'
"
@click="isLockEnabled = !isLockEnabled"
>
<i
:class="
isLockEnabled
? 'icon-[lucide--lock] size-3.5'
: 'icon-[lucide--lock-open] size-3.5'
"
/>
</Button>
</div>
/>
</Button>
</div>

<WidgetBoundingBox
Expand Down Expand Up @@ -495,7 +492,7 @@ const metadataRows = computed(() => [
watch(
toRef(() => videoUrl),
() => {
playheadFrame.value = 0
playheadFrame.value = hasTrim.value ? startFrame.value : 0
isPlaying.value = false
videoIntrinsicSize.value = null
}
Expand Down
Loading