feat: initial release - MorkNetVizualiser v1.0
This commit is contained in:
commit
5f8a43e652
19 files changed
+2954
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
||||
|
||||
const MIN_W = 220;
|
||||
const MIN_H = 160;
|
||||
|
||||
export function DraggablePanel({ id, title, badge, extra, onClose, children, defaultPos }) {
|
||||
const [pos, setPos] = useState(() => {
|
||||
try { const s = localStorage.getItem(`panel-pos-${id}`); return s ? JSON.parse(s) : defaultPos; }
|
||||
catch { return defaultPos; }
|
||||
});
|
||||
const [size, setSize] = useState(() => {
|
||||
try { const s = localStorage.getItem(`panel-size-${id}`); return s ? JSON.parse(s) : { w: 272, h: 480 }; }
|
||||
catch { return { w: 272, h: 480 }; }
|
||||
});
|
||||
|
||||
const savePos = (p) => { try { localStorage.setItem(`panel-pos-${id}`, JSON.stringify(p)); } catch {} };
|
||||
const saveSize = (s) => { try { localStorage.setItem(`panel-size-${id}`, JSON.stringify(s)); } catch {} };
|
||||
|
||||
const dragRef = useRef(null);
|
||||
const resizeRef = useRef(null);
|
||||
const panelRef = useRef(null);
|
||||
|
||||
// ── Drag ──────────────────────────────────────────────────────────────────
|
||||
const onDragMouseDown = useCallback((e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragRef.current = { startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y };
|
||||
}, [pos]);
|
||||
|
||||
// ── Resize ────────────────────────────────────────────────────────────────
|
||||
const onResizeMouseDown = useCallback((e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
resizeRef.current = { startX: e.clientX, startY: e.clientY, origW: size.w, origH: size.h };
|
||||
}, [size]);
|
||||
|
||||
useEffect(() => {
|
||||
const onMouseMove = (e) => {
|
||||
if (dragRef.current) {
|
||||
const d = dragRef.current;
|
||||
const nx = Math.max(0, Math.min(window.innerWidth - size.w, d.origX + (e.clientX - d.startX)));
|
||||
const ny = Math.max(0, Math.min(window.innerHeight - 60, d.origY + (e.clientY - d.startY)));
|
||||
const p = { x: nx, y: ny };
|
||||
setPos(p);
|
||||
savePos(p);
|
||||
}
|
||||
if (resizeRef.current) {
|
||||
const d = resizeRef.current;
|
||||
const nw = Math.max(MIN_W, d.origW + (e.clientX - d.startX));
|
||||
const nh = Math.max(MIN_H, d.origH + (e.clientY - d.startY));
|
||||
const s = { w: nw, h: nh };
|
||||
setSize(s);
|
||||
saveSize(s);
|
||||
}
|
||||
};
|
||||
const onMouseUp = () => { dragRef.current = null; resizeRef.current = null; };
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
return () => { window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); };
|
||||
}, [size.w]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={panelRef}
|
||||
className="floating-panel panel"
|
||||
style={{ left: pos.x, top: pos.y, width: size.w, height: size.h }}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{/* Header — drag handle */}
|
||||
<div className="fp-header" onMouseDown={onDragMouseDown}>
|
||||
<span className="fp-title">{title}</span>
|
||||
{badge != null && <span className="tab-count">{badge}</span>}
|
||||
{extra}
|
||||
<button className="close-btn" onMouseDown={e=>e.stopPropagation()} onClick={onClose}>✕</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="fp-body">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Resize handle — bottom-right corner */}
|
||||
<div className="fp-resize" onMouseDown={onResizeMouseDown}>⌟</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user