diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 74413ae..249e622 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -2743,6 +2743,8 @@ const ProfileModal = ({ const [msg, setMsg] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const [dragOfs, setDragOfs] = useState({ x: 0, y: 0 }); + const dragRef = useRef({ active: false, startX: 0, startY: 0, ofsX: 0, ofsY: 0 }); // Projects list state const [projectsList, setProjectsList] = useState([]); @@ -2755,6 +2757,26 @@ const ProfileModal = ({ // Confirmation modal state const [confirmModal, setConfirmModal] = useState(null); + const handleDragStart = (e) => { + const r = dragRef.current; + r.active = true; + r.startX = e.clientX; + r.startY = e.clientY; + r.ofsX = dragOfs.x; + r.ofsY = dragOfs.y; + const onMove = (ev) => { + if (!r.active) return; + setDragOfs({ x: r.ofsX + ev.clientX - r.startX, y: r.ofsY + ev.clientY - r.startY }); + }; + const onUp = () => { + r.active = false; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }; + useEffect(() => { if (isOpen) { fetchProfile(); @@ -2890,10 +2912,18 @@ const ProfileModal = ({ } }; - return /*#__PURE__*/React.createElement("div", { - className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm relative" - }, confirmModal && /*#__PURE__*/React.createElement("div", { - className: "absolute inset-0 z-[60] flex items-center justify-center bg-black/50" + const modalStyle = { + left: `calc(50% + ${dragOfs.x}px)`, + top: `calc(50% + ${dragOfs.y}px)`, + transform: 'translate(-50%, -50%)' + }; + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { + key: "backdrop", + className: "fixed inset-0 z-40 bg-black/70 backdrop-blur-sm", + onClick: onClose + }), confirmModal && /*#__PURE__*/React.createElement("div", { + key: "confirm-overlay", + className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/50" }, /*#__PURE__*/React.createElement("div", { className: "bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200" }, /*#__PURE__*/React.createElement("h4", { @@ -2909,9 +2939,12 @@ const ProfileModal = ({ onClick: () => { const fn = confirmModal.onConfirm; setConfirmModal(null); fn(); }, className: "px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold" }, "Xác nhận xóa")))), /*#__PURE__*/React.createElement("div", { - className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]" + key: "dialog", + className: "fixed z-50 bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]", + style: modalStyle }, /*#__PURE__*/React.createElement("div", { - className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0" + className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0 cursor-grab active:cursor-grabbing select-none", + onMouseDown: handleDragStart }, /*#__PURE__*/React.createElement("h3", { className: "text-md font-bold text-teal-400 flex items-center gap-1.5" }, "👤 Hồ Sơ Cá Nhân & Quản Lý Dự Án"), /*#__PURE__*/React.createElement("button", { @@ -8514,7 +8547,7 @@ const App = () => { }, [showAIPanel, showExportPanel, showSelectionPanel, showPythonToolsPanel, showMediaExplorer, showFxRack, showMidiEvents, panelPositions, rightSidebarWidth, mediaExplorerHeight, selectedProviderId, currentUser]); - return /*#__PURE__*/React.createElement("div", { + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { className: "h-full w-full flex flex-col bg-[#1e1e1e]" }, /*#__PURE__*/React.createElement("header", { className: "h-9 bg-[#2e2e2e] border-b border-[#181818] flex items-center px-1 shrink-0 select-none" @@ -10982,7 +11015,7 @@ const App = () => { setToastMessage(null); }, className: "ml-2 px-2 py-0.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-[10px] uppercase font-bold" - }, toastMessage.actionText || 'Tải về')), /*#__PURE__*/React.createElement(AuthModal, { + }, toastMessage.actionText || 'Tải về'))), /*#__PURE__*/React.createElement(AuthModal, { isOpen: authModalOpen, mode: authMode, forceMandatory: isMandatoryLogin, diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 18d6913..3dae6e1 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -2780,6 +2780,17 @@ const ProfileModal = ({ const [msg, setMsg] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const [dragOfs, setDragOfs] = useState({ + x: 0, + y: 0 + }); + const dragRef = useRef({ + active: false, + startX: 0, + startY: 0, + ofsX: 0, + ofsY: 0 + }); // Projects list state const [projectsList, setProjectsList] = useState([]); @@ -2791,6 +2802,28 @@ const ProfileModal = ({ // Confirmation modal state const [confirmModal, setConfirmModal] = useState(null); + const handleDragStart = e => { + const r = dragRef.current; + r.active = true; + r.startX = e.clientX; + r.startY = e.clientY; + r.ofsX = dragOfs.x; + r.ofsY = dragOfs.y; + const onMove = ev => { + if (!r.active) return; + setDragOfs({ + x: r.ofsX + ev.clientX - r.startX, + y: r.ofsY + ev.clientY - r.startY + }); + }; + const onUp = () => { + r.active = false; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }; useEffect(() => { if (isOpen) { fetchProfile(); @@ -2917,10 +2950,18 @@ const ProfileModal = ({ setLoading(false); } }; - return /*#__PURE__*/React.createElement("div", { - className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm relative" - }, confirmModal && /*#__PURE__*/React.createElement("div", { - className: "absolute inset-0 z-[60] flex items-center justify-center bg-black/50" + const modalStyle = { + left: `calc(50% + ${dragOfs.x}px)`, + top: `calc(50% + ${dragOfs.y}px)`, + transform: 'translate(-50%, -50%)' + }; + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { + key: "backdrop", + className: "fixed inset-0 z-40 bg-black/70 backdrop-blur-sm", + onClick: onClose + }), confirmModal && /*#__PURE__*/React.createElement("div", { + key: "confirm-overlay", + className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/50" }, /*#__PURE__*/React.createElement("div", { className: "bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200" }, /*#__PURE__*/React.createElement("h4", { @@ -2940,9 +2981,12 @@ const ProfileModal = ({ }, className: "px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold" }, "Xác nhận xóa")))), /*#__PURE__*/React.createElement("div", { - className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]" + key: "dialog", + className: "fixed z-50 bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]", + style: modalStyle }, /*#__PURE__*/React.createElement("div", { - className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0" + className: "flex justify-between items-center pb-3 border-b border-[#383838] shrink-0 cursor-grab active:cursor-grabbing select-none", + onMouseDown: handleDragStart }, /*#__PURE__*/React.createElement("h3", { className: "text-md font-bold text-teal-400 flex items-center gap-1.5" }, "👤 Hồ Sơ Cá Nhân & Quản Lý Dự Án"), /*#__PURE__*/React.createElement("button", { @@ -8994,7 +9038,7 @@ const App = () => { }, 2000); return () => clearTimeout(timer); }, [showAIPanel, showExportPanel, showSelectionPanel, showPythonToolsPanel, showMediaExplorer, showFxRack, showMidiEvents, panelPositions, rightSidebarWidth, mediaExplorerHeight, selectedProviderId, currentUser]); - return /*#__PURE__*/React.createElement("div", { + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { className: "h-full w-full flex flex-col bg-[#1e1e1e]" }, /*#__PURE__*/React.createElement("header", { className: "h-9 bg-[#2e2e2e] border-b border-[#181818] flex items-center px-1 shrink-0 select-none" @@ -11530,7 +11574,7 @@ const App = () => { setToastMessage(null); }, className: "ml-2 px-2 py-0.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-[10px] uppercase font-bold" - }, toastMessage.actionText || 'Tải về')), /*#__PURE__*/React.createElement(AuthModal, { + }, toastMessage.actionText || 'Tải về'))), /*#__PURE__*/React.createElement(AuthModal, { isOpen: authModalOpen, mode: authMode, forceMandatory: isMandatoryLogin, diff --git a/app/storage/sonicforge.db b/app/storage/sonicforge.db index 82ddacc..52340cf 100644 Binary files a/app/storage/sonicforge.db and b/app/storage/sonicforge.db differ