Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make the video in song editor draggable, resizable & snappable #85

Merged
merged 11 commits into from
Nov 19, 2024
175 changes: 135 additions & 40 deletions app/components/DraggableWindow.vue
Original file line number Diff line number Diff line change
@@ -1,81 +1,176 @@
<script setup lang="ts">
import Moveable, { type OnDrag, type OnResize, type OnResizeStart } from 'vue3-moveable'

interface Props {
x?: number
y?: number
initialWidth?: number
initialHeight?: number
minWidth?: number
minHeight?: number
maxInset?: number
resizable?: boolean
}
const props = withDefaults(defineProps<Props>(), {
x: 0,
y: 0,
maxInset: 0,
initialWidth: 240,
initialHeight: 240,
maxInset: 7,
resizable: false,
})

let stickRight = false
let stickBottom = false
const pos = readonly({
x: props.x,
y: props.y,
})

const dragWindow = ref<HTMLElement | null>(null)
const dragHandle = ref<HTMLElement | null>(null)
const dragWindow = useTemplateRef<HTMLElement>('dragWindow')
const dragHandle = useTemplateRef<HTMLElement>('dragHandle')

const { x: dragX, y: dragY, style, isDragging } = useDraggable(dragWindow, {
initialValue: { x: props.x, y: props.y },
handle: dragHandle,
preventDefault: true,
onEnd() {
stickRight = false
stickBottom = false
limitPosition()
},
})
// #region : Draggable
const isDragging = ref(false)
function handleDrag(e: OnDrag) {
isDragging.value = true
e.target.style.transform = e.transform
}
// #endregion

// #region : Avoid the window stayed outside
const moveableRef = useTemplateRef<InstanceType<typeof Moveable>>('moveableRef')

const { width: windowWidth, height: windowHeight } = useWindowSize()
const dragWindowBounding = useElementBounding(dragWindow)

function limitPosition() {
let x = dragWindowBounding.left.value
let y = dragWindowBounding.top.value
if (dragWindowBounding.left.value < props.maxInset) {
dragX.value = props.maxInset
x = props.maxInset
}
else if (stickRight || dragWindowBounding.right.value > windowWidth.value - props.maxInset) {
dragX.value = windowWidth.value - props.maxInset - dragWindowBounding.width.value
stickRight = true
else if (dragWindowBounding.right.value > windowWidth.value - props.maxInset) {
x = Math.max(windowWidth.value - props.maxInset - dragWindowBounding.width.value, 0)
}

if (dragWindowBounding.top.value < props.maxInset) {
dragY.value = props.maxInset
y = props.maxInset
}
else if (stickBottom || dragWindowBounding.bottom.value > windowHeight.value - props.maxInset) {
dragY.value = windowHeight.value - props.maxInset - dragWindowBounding.height.value
stickBottom = true
else if (dragWindowBounding.bottom.value > windowHeight.value - props.maxInset) {
y = Math.max(windowHeight.value - props.maxInset - dragWindowBounding.height.value, 0)
}
moveableRef.value?.request('draggable', { x, y }, true)
}

onMounted(() => {
const limitPositionDebounced = useDebounceFn(limitPosition, 250)
useEventListener('resize', limitPositionDebounced, { passive: true })
})
// #endregion

// #region : Resizable
function handleResize(e: OnResize) {
e.target.style.width = `${e.width}px`
e.target.style.height = `${e.height}px`
e.target.style.transform = e.drag.transform
}
function handleResizeStart(e: OnResizeStart) {
e.setMin([240, 160])
}
// #endregion

// #region : Reset
function reset() {
if (!dragWindow.value || !moveableRef.value)
return
dragWindow.value.style.top = ''
dragWindow.value.style.left = ''
dragWindow.value.style.width = ''
dragWindow.value.style.height = ''
dragWindow.value.style.transform = ''
limitPosition()
moveableRef.value.updateRect()
}
// #endregion
</script>

<template>
<div
ref="dragWindow"
class="draggable-window"
:class="{
'pointer-events-none': isDragging,
}"
fixed
:style
z-floating
>
<Teleport to="body">
<div
ref="dragHandle"
class="@hover:(bg-gray/10 op100)"
flex="~ items-center justify-center"
draggable pointer-events-auto mxa w-20 cursor-move rounded-full op25
ref="dragWindow"
class="drag-window"
:class="{
'pointer-events-none': isDragging,
'pointer-events-auto': !isDragging,
}"
:style="{
'--moveable-left': `${pos.x}px`,
'--moveable-top': `${pos.y}px`,
'--moveable-width': `${props.initialWidth}px`,
'--moveable-height': `${props.initialHeight}px`,
}"
fixed h-full w-full
v-bind="$attrs"
>
<div i-mdi-drag-horizontal ma text-size-xl />
<div
ref="dragHandle"
class="drag-handle left-50% top-0 transform-translate-x--50% transform-translate-y--100% @hover:(bg-gray/10 op100)"
flex="~ items-center justify-center"
draggable absolute mxa h-6 w-20 cursor-move rounded-full op25
@dblclick.left="reset"
>
<div i-mdi-drag-horizontal ma text-size-xl />
</div>
<slot />
</div>
<slot />
</div>
<Moveable
ref="moveableRef"
:target="dragWindow"
:origin="false"
:draggable="true"
:drag-target="dragHandle"
:throttle-drag="3"
:resizable="resizable"
:throttle-resize="3"
:snappable="true"
:bounds="{
left: 7,
right: 7,
top: 31,
bottom: 7,
position: 'css',
}"
:style="{
'--bounds-color': 'transparent',
'--moveable-color': 'transparent',
}"
@resize="handleResize"
@resize-start="handleResizeStart"
@drag="handleDrag"
@drag-end="isDragging = false"
/>
</Teleport>
</template>

<style>
.drag-window {
left: var(--moveable-left);
top: var(--moveable-top);
width: var(--moveable-width);
height: var(--moveable-height);
max-width: calc(100vw - 14px);
max-height: calc(100vh - 38px);
max-height: calc(100dvh - 38px);
}
.drag-window:hover + .moveable-control-box,
.moveable-control-box:hover {
--moveable-control-opacity: 0.2;
}
.moveable-control-box .moveable-control {
opacity: var(--moveable-control-opacity, 0);
background: rgba(0 0 0 / 0.5) !important;
transition: opacity 150ms ease;
}
.moveable-control-box .moveable-control:hover {
--moveable-control-opacity: 1;
}
</style>
22 changes: 18 additions & 4 deletions app/components/editors/SongEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,17 @@ onMounted(() => {
</script>

<template>
<DraggableWindow :x="windowWidth - 32 - 480" :y="60">
<div flex="~ col">
<YouTubePlayer w-120 rounded-lg border="~ base" />
<div flex="~ gap-2 items-center" mt--2 w-max self-end p1 px2 text-sm floating-glass>
<DraggableWindow
id="editor-video"
:x="windowWidth - 32 - 480"
:y="84"
:initial-width="480"
:initial-height="300"
resizable
>
<div flex="~ col" relative h-full w-full pb-6.5>
<YouTubePlayer h-full w-full rounded-lg border="~ base" />
<div flex="~ gap-2 items-center" absolute bottom-0 mt--2 self-end p1 px2 text-sm floating-glass>
<!-- <IconButton
:icon="controls.status.value === 'playing' ? 'i-uil-pause' : 'i-uil-play'"
@click="controls.toggle()"
Expand Down Expand Up @@ -484,3 +491,10 @@ onMounted(() => {
</div>
</div>
</template>

<style>
#editor-video iframe {
width: 100% !important;
height: 100% !important;
}
</style>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"uqr": "^0.1.2",
"valibot": "^0.42.1",
"vue-virtual-scroller": "2.0.0-beta.8",
"vue3-moveable": "^0.28.0",
"weq8": "^0.2.2"
},
"devDependencies": {
Expand Down
Loading
Loading