feat: basic sortable list, missing auto-scroll

This commit is contained in:
2026-08-31 16:18:28 +02:00
parent 1d492d5922
commit 0e155831d7
4 changed files with 176 additions and 11 deletions
+3 -3
View File
@@ -1,4 +1,5 @@
<script setup lang="ts">
import SortableList from './SortableList.vue';
import {
beautifyTime
} from '@/ts/util/time';
@@ -32,7 +33,7 @@
</button>
</div>
<div>
<div v-for="(song, index) in queue" :key="index">
<SortableList v-slot="{ item: song, index }" v-model="queue">
<div class="song-cover-wrapper">
<img
v-if="song.cover"
@@ -58,9 +59,8 @@
<div>
<i class="fa-solid fa-trash-can"></i>
<i class="fa-solid fa-pen-to-square"></i>
<i class="fa-solid fa-grip-vertical"></i>
</div>
</div>
</SortableList>
</div>
</div>
</template>
+116 -7
View File
@@ -1,14 +1,123 @@
<script setup lang="ts">
const queue = defineModel<unknown[]>();
<script setup lang="ts" generic="T">
import {
ref,
useTemplateRef
} from 'vue';
const queue = defineModel<T[]>( {
'required': true
} );
defineSlots<{
default( props: {
'item': T,
'index': number
} ): void
}>();
const emit = defineEmits<{
( e: 'dropped' ): void
}>();
const scrollContainer = useTemplateRef( 'scroll-container' );
const movingIdx = ref( -1 );
const movingCurrentIdx = ref( -1 );
const movableSize = ref( -1 );
const offset = ref( -1 );
let mover = -1;
let moveSpeed = 0;
const start = ( ev: MouseEvent, idx: number ) => {
movableSize.value = document.getElementById( 'movable-' + idx )!.scrollHeight;
offset.value = ev.y - ( movableSize.value / 2 );
movingIdx.value = idx;
movingCurrentIdx.value = idx;
};
const move = ( ev: MouseEvent ) => {
if ( movingIdx.value < 0 ) return;
offset.value = ev.y - ( movableSize.value / 2 );
const relToContainer = ev.y - scrollContainer.value!.offsetTop;
// const height = scrollContainer.value!.offsetHeight;
//
// // Scrolling up and down
// if ( relToContainer < 50 ) {
// moveSpeed = Math.ceil( Math.min( 10 / relToContainer, 10 ) );
// startTracker();
// } else if ( relToContainer > height - 50 ) {
// moveSpeed = Math.ceil( Math.min( 10 / ( height - relToContainer ), 10 ) );
// startTracker();
// } else {
// if ( mover >= 0 ) {
// try {
// clearInterval( mover );
// } catch { /* empty */ }
// }
// }
// Determine current index to insert
const percentage = ( relToContainer - scrollContainer.value!.scrollTop ) / scrollContainer.value!.scrollHeight;
const newIdx = Math.min( Math.max( Math.floor( percentage * queue.value.length ), 0 ), queue.value.length - 1 );
if ( newIdx !== movingIdx.value )
movingCurrentIdx.value = newIdx;
else
movingCurrentIdx.value = movingIdx.value;
};
const startTracker = () => {
if ( mover < 0 ) {
mover = setInterval( () => {
scrollContainer.value!.scrollTop += moveSpeed;
}, 100 );
}
};
const end = () => {
offset.value = -1;
movingIdx.value = -1;
// TODO: Actually do the move
try {
clearInterval( mover );
} catch { /* empty */ }
emit( 'dropped' );
};
</script>
<template>
<div>
<div v-for="(song, index) in queue" :key="index">
<slot :item="song"></slot>
<div>
<i class="fa-solid fa-grip-vertical"></i>
<div ref="scroll-container" class="sortable-list">
<div
v-if="movingIdx >= 0"
class="grip-target"
@mousemove="move"
@mouseleave="end"
@mouseup="end"
>
</div>
<div
v-for="(item, index) in queue"
:id="'movable-' + index"
:key="index"
:class="[ 'movable', movingIdx === index ? 'moving' : undefined ]"
:style="
movingIdx >= 0
&& ((movingCurrentIdx === index && movingIdx !== index)
|| (movingCurrentIdx === index - 1 && index - 1 === movingIdx)
)
? `margin-top: ${ movableSize }px;`
: (movingIdx === index
? `top: ${offset}px;`
: ''
)"
>
<slot :item="item" :index="index"></slot>
<i class="fa-solid fa-grip-vertical" @mousedown="( e ) => start( e, index )"></i>
</div>
</div>
</template>
<style lang="scss" scoped>
@import '@/scss/components/sortablelist.scss'
</style>
+25
View File
@@ -0,0 +1,25 @@
.sortable-list {
overflow-y: scroll;
overflow-x: hidden;
height: 100%;
position: relative;
>.grip-target {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
cursor: grabbing;
}
>.movable {
&.moving {
position: fixed;
}
>i {
cursor: move;
}
}
}
+31
View File
@@ -1,5 +1,36 @@
<script setup lang="ts">
import SortableList from '@/components/SortableList.vue';
import {
ref
} from 'vue';
const items = ref( [
'test',
'test2',
'test3',
'test4',
'test5',
'test6',
'test7',
'test8',
'test9',
'test10'
] );
</script>
<template>
<div>
<h1>MusicPlayer</h1>
<div class="test">
<SortableList v-slot="{ item, index }" v-model="items">
{{ item }} at {{ index }}
</SortableList>
</div>
</div>
</template>
<style lang="scss" scoped>
.test {
height: 150px;
}
</style>