Let Image and video LoRA stacks be reordered by dragging.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,15 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="block min-w-0" :class="compact ? 'text-xs' : 'text-sm'">
|
<div class="block min-w-0" :class="compact ? 'text-xs' : 'text-sm'">
|
||||||
<span v-if="label" class="mb-1 block text-zinc-400">{{ label }}</span>
|
<span v-if="label" class="mb-1 block text-zinc-400">{{ label }}</span>
|
||||||
|
<p v-if="model.length > 1" class="mb-1 text-[11px] text-zinc-500">Drag to set chain order. Top applies first.</p>
|
||||||
<ul v-if="model.length" class="space-y-2">
|
<ul v-if="model.length" class="space-y-2">
|
||||||
<li
|
<li
|
||||||
v-for="(item, index) in model"
|
v-for="(item, index) in model"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
class="rounded-xl border border-white/10 bg-zinc-950/80"
|
class="rounded-xl border bg-zinc-950/80"
|
||||||
:class="compact ? 'px-2 py-1.5' : 'px-3 py-2'"
|
:class="[
|
||||||
|
compact ? 'px-2 py-1.5' : 'px-3 py-2',
|
||||||
|
dragOverIndex === index ? 'border-amber-300/50' : 'border-white/10',
|
||||||
|
draggingIndex === index ? 'opacity-60' : ''
|
||||||
|
]"
|
||||||
|
@dragover.prevent="onDragOver(index)"
|
||||||
|
@drop.prevent="onDrop(index)"
|
||||||
|
@dragleave="onDragLeave(index)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<div class="flex min-w-0 items-center gap-2">
|
||||||
|
<button
|
||||||
|
v-if="model.length > 1"
|
||||||
|
type="button"
|
||||||
|
class="shrink-0 cursor-grab touch-none text-zinc-500 hover:text-zinc-300 active:cursor-grabbing disabled:cursor-not-allowed disabled:opacity-40"
|
||||||
|
:disabled="disabled"
|
||||||
|
draggable="true"
|
||||||
|
title="Drag to reorder"
|
||||||
|
aria-label="Drag to reorder"
|
||||||
|
@dragstart="onDragStart(index, $event)"
|
||||||
|
@dragend="onDragEnd"
|
||||||
|
>
|
||||||
|
<span class="block leading-none" aria-hidden="true">⋮⋮</span>
|
||||||
|
</button>
|
||||||
<span class="min-w-0 truncate text-zinc-200">{{ loraDisplayName(item.name) }}</span>
|
<span class="min-w-0 truncate text-zinc-200">{{ loraDisplayName(item.name) }}</span>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="shrink-0 text-[11px] text-zinc-500 hover:text-red-300 disabled:opacity-50"
|
class="shrink-0 text-[11px] text-zinc-500 hover:text-red-300 disabled:opacity-50"
|
||||||
@@ -111,6 +134,9 @@ const props = withDefaults(defineProps<{
|
|||||||
|
|
||||||
const model = defineModel<LoraStackItem[]>({ default: () => [] })
|
const model = defineModel<LoraStackItem[]>({ default: () => [] })
|
||||||
|
|
||||||
|
const draggingIndex = ref<number | null>(null)
|
||||||
|
const dragOverIndex = ref<number | null>(null)
|
||||||
|
|
||||||
const availableNames = computed(() => {
|
const availableNames = computed(() => {
|
||||||
const taken = new Set(model.value.map(item => loraIdentityKey(item.name)))
|
const taken = new Set(model.value.map(item => loraIdentityKey(item.name)))
|
||||||
return props.names.filter(name => !taken.has(loraIdentityKey(name)))
|
return props.names.filter(name => !taken.has(loraIdentityKey(name)))
|
||||||
@@ -134,6 +160,46 @@ function removeAt(index: number) {
|
|||||||
model.value = model.value.filter((_, i) => i !== index)
|
model.value = model.value.filter((_, i) => i !== index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveItem(from: number, to: number) {
|
||||||
|
if (from === to || from < 0 || to < 0 || to >= model.value.length) return
|
||||||
|
const next = [...model.value]
|
||||||
|
const [item] = next.splice(from, 1)
|
||||||
|
next.splice(to, 0, item)
|
||||||
|
model.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragStart(index: number, event: DragEvent) {
|
||||||
|
if (props.disabled) {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
draggingIndex.value = index
|
||||||
|
dragOverIndex.value = index
|
||||||
|
event.dataTransfer?.setData('text/plain', String(index))
|
||||||
|
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOver(index: number) {
|
||||||
|
if (draggingIndex.value === null || props.disabled) return
|
||||||
|
dragOverIndex.value = index
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragLeave(index: number) {
|
||||||
|
if (dragOverIndex.value === index) dragOverIndex.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrop(index: number) {
|
||||||
|
if (draggingIndex.value === null || props.disabled) return
|
||||||
|
moveItem(draggingIndex.value, index)
|
||||||
|
draggingIndex.value = null
|
||||||
|
dragOverIndex.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd() {
|
||||||
|
draggingIndex.value = null
|
||||||
|
dragOverIndex.value = null
|
||||||
|
}
|
||||||
|
|
||||||
function setStrength(index: number, key: 'strengthModel' | 'strengthClip', event: Event) {
|
function setStrength(index: number, key: 'strengthModel' | 'strengthClip', event: Event) {
|
||||||
const value = clampLoraStrength((event.target as HTMLInputElement).value)
|
const value = clampLoraStrength((event.target as HTMLInputElement).value)
|
||||||
model.value = model.value.map((item, i) => i === index ? { ...item, [key]: value } : item)
|
model.value = model.value.map((item, i) => i === index ? { ...item, [key]: value } : item)
|
||||||
|
|||||||
Reference in New Issue
Block a user