Schnellzugriff: Reihenfolge per Drag & Drop änderbar

This commit is contained in:
2026-05-29 10:32:37 +02:00
parent 3d5925b603
commit 2e033d8ad4
2 changed files with 69 additions and 2 deletions

View File

@@ -1408,6 +1408,8 @@ function QuickLinksSettings({ toast }) {
const [showForm, setForm] = useState(false);
const [form, setF] = useState({ title:'', url:'', icon:'🔗' });
const [editId, setEditId] = useState(null);
const dragRef = useRef(null);
const touchRef = useRef({});
const { confirm, ConfirmDialog } = useConfirm();
useEffect(() => { api('/dashboard/links').then(setLinks).catch(() => {}); }, []);
@@ -1441,6 +1443,52 @@ function QuickLinksSettings({ toast }) {
catch(e) { toast(e.message, 'error'); }
};
const reorder = async (newLinks) => {
setLinks(newLinks);
try { await api('/dashboard/links-order', { method:'PUT', body:{ ids: newLinks.map(l=>l.id) } }); }
catch(e) { toast(e.message,'error'); }
};
// Desktop drag handlers
const onDragStart = (e, id) => { dragRef.current = id; e.dataTransfer.effectAllowed = 'move'; };
const onDragOver = (e, id) => {
e.preventDefault();
if (dragRef.current === id) return;
const from = links.findIndex(l => l.id === dragRef.current);
const to = links.findIndex(l => l.id === id);
if (from < 0 || to < 0) return;
const next = [...links]; next.splice(to, 0, next.splice(from, 1)[0]);
setLinks(next);
};
const onDrop = () => reorder(links);
// Touch handlers (long press + drag)
const onTouchStart = (e, id) => {
touchRef.current = { id, startY: e.touches[0].clientY, timer: setTimeout(() => {
touchRef.current.active = true;
}, 300) };
};
const onTouchMove = (e) => {
if (!touchRef.current.active) return;
e.preventDefault();
const y = e.touches[0].clientY;
const els = document.elementsFromPoint(e.touches[0].clientX, y);
const target = els.find(el => el.dataset?.linkId);
if (!target) return;
const targetId = parseInt(target.dataset.linkId);
if (targetId === touchRef.current.id) return;
const from = links.findIndex(l => l.id === touchRef.current.id);
const to = links.findIndex(l => l.id === targetId);
if (from < 0 || to < 0) return;
const next = [...links]; next.splice(to, 0, next.splice(from, 1)[0]);
setLinks(next);
};
const onTouchEnd = () => {
clearTimeout(touchRef.current.timer);
if (touchRef.current.active) reorder(links);
touchRef.current = {};
};
return (
<div>
<ConfirmDialog/>
@@ -1448,8 +1496,19 @@ function QuickLinksSettings({ toast }) {
{links.length > 0 && (
<div style={{ marginBottom:12 }}>
{links.map(l => (
<div key={l.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 0',
borderBottom:'1px solid rgba(255,255,255,0.05)' }}>
<div key={l.id}
data-link-id={l.id}
draggable
onDragStart={e => onDragStart(e, l.id)}
onDragOver={e => onDragOver(e, l.id)}
onDrop={onDrop}
onTouchStart={e => onTouchStart(e, l.id)}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 0',
borderBottom:'1px solid rgba(255,255,255,0.05)',
cursor:'grab', userSelect:'none' }}>
<span style={{ color:'rgba(255,255,255,0.2)', fontSize:14, flexShrink:0, cursor:'grab' }}></span>
<span style={{ fontSize:18, width:24, textAlign:'center', flexShrink:0 }}>
{l.icon && l.icon.startsWith('http')
? <img src={l.icon} alt="" style={{width:18,height:18,objectFit:'contain'}} onError={e=>e.target.style.display='none'}/>