mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
feat: basic sortable list, missing auto-scroll
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import SortableList from './SortableList.vue';
|
||||||
import {
|
import {
|
||||||
beautifyTime
|
beautifyTime
|
||||||
} from '@/ts/util/time';
|
} from '@/ts/util/time';
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<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">
|
<div class="song-cover-wrapper">
|
||||||
<img
|
<img
|
||||||
v-if="song.cover"
|
v-if="song.cover"
|
||||||
@@ -58,9 +59,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<i class="fa-solid fa-trash-can"></i>
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
<i class="fa-solid fa-pen-to-square"></i>
|
<i class="fa-solid fa-pen-to-square"></i>
|
||||||
<i class="fa-solid fa-grip-vertical"></i>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SortableList>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,14 +1,123 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="T">
|
||||||
const queue = defineModel<unknown[]>();
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div ref="scroll-container" class="sortable-list">
|
||||||
<div v-for="(song, index) in queue" :key="index">
|
<div
|
||||||
<slot :item="song"></slot>
|
v-if="movingIdx >= 0"
|
||||||
<div>
|
class="grip-target"
|
||||||
<i class="fa-solid fa-grip-vertical"></i>
|
@mousemove="move"
|
||||||
</div>
|
@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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/scss/components/sortablelist.scss'
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>MusicPlayer</h1>
|
<h1>MusicPlayer</h1>
|
||||||
|
<div class="test">
|
||||||
|
<SortableList v-slot="{ item, index }" v-model="items">
|
||||||
|
{{ item }} at {{ index }}
|
||||||
|
</SortableList>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.test {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user