diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ce61ec..f0bb3e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,12 +21,33 @@ jobs: with: python-version: "3.11" + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Build monitor dashboard (SPA) + run: | + cd damiao_motor/gui/webapp + npm ci + npm run build + - name: Install build tools run: python -m pip install --upgrade build twine - name: Build package run: python -m build + - name: Verify SPA bundle is in the wheel + run: | + python - <<'PY' + import glob, zipfile, sys + whl = sorted(glob.glob("dist/*.whl"))[-1] + names = zipfile.ZipFile(whl).namelist() + assert any("gui/webapp/dist/index.html" in n for n in names), "SPA index.html missing from wheel" + assert any("monitor/server.py" in n for n in names), "monitor package missing from wheel" + print("OK: SPA + monitor present in", whl) + PY + - name: Upload to PyPI run: python -m twine upload dist/* env: diff --git a/.gitignore b/.gitignore index 8e5c2ab..549c460 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,12 @@ dist/ *.egg damiao_motor/_version.py +# ...but DO ship the built monitor SPA (end users have no node toolchain). +# These negations override the unanchored build/ and dist/ rules above. +!damiao_motor/gui/webapp/dist/ +!damiao_motor/gui/webapp/dist/** +damiao_motor/gui/webapp/node_modules/ + # IDE / editor .vscode/ .idea/ diff --git a/damiao_motor/cli/__init__.py b/damiao_motor/cli/__init__.py index 47d77c2..ee37292 100644 --- a/damiao_motor/cli/__init__.py +++ b/damiao_motor/cli/__init__.py @@ -19,6 +19,7 @@ cmd_send_cmd_vel, cmd_send_cmd_force_pos, cmd_gui, + cmd_monitor, ) from .formatter import ColorizedHelpFormatter @@ -161,6 +162,78 @@ def unified_main() -> None: ) gui_parser.set_defaults(func=cmd_gui) + # monitor command (passive, listen-only dashboard) + monitor_parser = subparsers.add_parser( + "monitor", + help="Launch passive (listen-only) realtime monitoring dashboard", + description=( + "Launch a listen-only dashboard that decodes both the commands another " + "controller is sending and the motors' feedback, in realtime. Never transmits." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Monitor a socketcan bus while another controller drives the motors + damiao monitor --channel can0 + + # I2RT-style feedback id scheme is +16 (the default); override if needed + damiao monitor --channel can_arm_l --feedback-offset 16 + + # Try the dashboard with synthetic traffic (no CAN hardware needed) + damiao monitor --demo + """, + ) + monitor_parser.add_argument( + "--host", + type=str, + default="127.0.0.1", + help="Host to bind to (default: 127.0.0.1)", + ) + monitor_parser.add_argument( + "--port", type=int, default=5001, help="Port to bind to (default: 5001)" + ) + monitor_parser.add_argument( + "--channel", + type=str, + default="can0", + help="CAN channel to listen on (default: can0)", + ) + monitor_parser.add_argument( + "--bustype", + type=str, + default="socketcan", + help="CAN bus type (default: socketcan)", + ) + monitor_parser.add_argument( + "--bitrate", + type=int, + default=None, + help="CAN bitrate (required for some interfaces, e.g. gs_usb)", + ) + monitor_parser.add_argument( + "--feedback-offset", + type=int, + default=16, + dest="feedback_offset", + help="feedback arb id = motor id + offset (default: 16, the p16 scheme)", + ) + monitor_parser.add_argument( + "--motor-type", + type=str, + default="DM4310", + dest="default_motor_type", + help="Default motor type for value scaling (default: DM4310)", + ) + monitor_parser.add_argument( + "--demo", + action="store_true", + help="Synthesize traffic instead of opening a CAN bus", + ) + monitor_parser.add_argument( + "--debug", action="store_true", help="Enable debug mode" + ) + monitor_parser.set_defaults(func=cmd_monitor) + # Helper function to add global arguments to subcommands def add_global_args(subparser, include_motor_type: bool = True): """Add global arguments to a subcommand parser.""" diff --git a/damiao_motor/cli/commands.py b/damiao_motor/cli/commands.py index 4a90e4b..f9f2830 100644 --- a/damiao_motor/cli/commands.py +++ b/damiao_motor/cli/commands.py @@ -6,7 +6,6 @@ import time from damiao_motor.core.controller import DaMiaoController -from damiao_motor.gui import web_gui from .display import ( BOX_CORNER_TL, BOX_CORNER_TR, @@ -718,8 +717,39 @@ def cmd_gui(args) -> None: - debug: Enable debug mode (default: False) - production: Use production WSGI server (default: False) """ - web_gui.run_server( - host=args.host, port=args.port, debug=args.debug, production=args.production + # `gui` now launches the unified DaMiao Studio in control mode (active control + + # the realtime monitor in one UI). The legacy Flask GUI remains in web_gui.py. + from damiao_motor.monitor import server as studio_server + + studio_server.run_server( + host=args.host, port=args.port, mode="control", debug=args.debug + ) + + +def cmd_monitor(args) -> None: + """ + Handle 'monitor' subcommand. + + Launches the passive (listen-only) realtime monitoring dashboard. It decodes both + the commands another controller is sending and the motors' feedback, and never + transmits on the bus. + + Args: + args: Parsed command-line arguments containing host, port, channel, bustype, + bitrate, feedback_offset, default_motor_type, demo, debug. + """ + from damiao_motor.monitor import server as monitor_server + + monitor_server.run_server( + host=args.host, + port=args.port, + channel=args.channel, + bustype=args.bustype, + bitrate=args.bitrate, + feedback_offset=args.feedback_offset, + default_motor_type=args.default_motor_type, + debug=args.debug, + demo=args.demo, ) diff --git a/damiao_motor/core/motor.py b/damiao_motor/core/motor.py index 40b9dcb..8e01e8f 100644 --- a/damiao_motor/core/motor.py +++ b/damiao_motor/core/motor.py @@ -279,7 +279,7 @@ def __init__( motor_id: int, feedback_id: int, bus: can.Bus, - fd: bool, + fd: bool = False, *, motor_type: str, p_min: Optional[float] = None, diff --git a/damiao_motor/gui/webapp/dist/assets/index-CDw8n6KX.js b/damiao_motor/gui/webapp/dist/assets/index-CDw8n6KX.js new file mode 100644 index 0000000..173ee68 --- /dev/null +++ b/damiao_motor/gui/webapp/dist/assets/index-CDw8n6KX.js @@ -0,0 +1,54 @@ +var Wv=Object.defineProperty;var Bv=(l,t,r)=>t in l?Wv(l,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):l[t]=r;var mo=(l,t,r)=>Bv(l,typeof t!="symbol"?t+"":t,r);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const u of o)if(u.type==="childList")for(const c of u.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&i(c)}).observe(document,{childList:!0,subtree:!0});function r(o){const u={};return o.integrity&&(u.integrity=o.integrity),o.referrerPolicy&&(u.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?u.credentials="include":o.crossOrigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function i(o){if(o.ep)return;o.ep=!0;const u=r(o);fetch(o.href,u)}})();function Mm(l){return l&&l.__esModule&&Object.prototype.hasOwnProperty.call(l,"default")?l.default:l}var tf={exports:{}},go={},nf={exports:{}},Ue={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var sp;function Uv(){if(sp)return Ue;sp=1;var l=Symbol.for("react.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),i=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),u=Symbol.for("react.provider"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),v=Symbol.iterator;function x(M){return M===null||typeof M!="object"?null:(M=v&&M[v]||M["@@iterator"],typeof M=="function"?M:null)}var T={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,C={};function L(M,W,X){this.props=M,this.context=W,this.refs=C,this.updater=X||T}L.prototype.isReactComponent={},L.prototype.setState=function(M,W){if(typeof M!="object"&&typeof M!="function"&&M!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,M,W,"setState")},L.prototype.forceUpdate=function(M){this.updater.enqueueForceUpdate(this,M,"forceUpdate")};function U(){}U.prototype=L.prototype;function A(M,W,X){this.props=M,this.context=W,this.refs=C,this.updater=X||T}var V=A.prototype=new U;V.constructor=A,N(V,L.prototype),V.isPureReactComponent=!0;var z=Array.isArray,$=Object.prototype.hasOwnProperty,G={current:null},Y={key:!0,ref:!0,__self:!0,__source:!0};function Z(M,W,X){var ee,be={},he=null,Ee=null;if(W!=null)for(ee in W.ref!==void 0&&(Ee=W.ref),W.key!==void 0&&(he=""+W.key),W)$.call(W,ee)&&!Y.hasOwnProperty(ee)&&(be[ee]=W[ee]);var Ie=arguments.length-2;if(Ie===1)be.children=X;else if(1>>1,W=oe[M];if(0>>1;Mo(be,J))heo(Ee,be)?(oe[M]=Ee,oe[he]=J,M=he):(oe[M]=be,oe[ee]=J,M=ee);else if(heo(Ee,J))oe[M]=Ee,oe[he]=J,M=he;else break e}}return ae}function o(oe,ae){var J=oe.sortIndex-ae.sortIndex;return J!==0?J:oe.id-ae.id}if(typeof performance=="object"&&typeof performance.now=="function"){var u=performance;l.unstable_now=function(){return u.now()}}else{var c=Date,d=c.now();l.unstable_now=function(){return c.now()-d}}var p=[],g=[],y=1,v=null,x=3,T=!1,N=!1,C=!1,L=typeof setTimeout=="function"?setTimeout:null,U=typeof clearTimeout=="function"?clearTimeout:null,A=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function V(oe){for(var ae=r(g);ae!==null;){if(ae.callback===null)i(g);else if(ae.startTime<=oe)i(g),ae.sortIndex=ae.expirationTime,t(p,ae);else break;ae=r(g)}}function z(oe){if(C=!1,V(oe),!N)if(r(p)!==null)N=!0,ke($);else{var ae=r(g);ae!==null&&le(z,ae.startTime-oe)}}function $(oe,ae){N=!1,C&&(C=!1,U(Z),Z=-1),T=!0;var J=x;try{for(V(ae),v=r(p);v!==null&&(!(v.expirationTime>ae)||oe&&!q());){var M=v.callback;if(typeof M=="function"){v.callback=null,x=v.priorityLevel;var W=M(v.expirationTime<=ae);ae=l.unstable_now(),typeof W=="function"?v.callback=W:v===r(p)&&i(p),V(ae)}else i(p);v=r(p)}if(v!==null)var X=!0;else{var ee=r(g);ee!==null&&le(z,ee.startTime-ae),X=!1}return X}finally{v=null,x=J,T=!1}}var G=!1,Y=null,Z=-1,K=5,fe=-1;function q(){return!(l.unstable_now()-feoe||125M?(oe.sortIndex=J,t(g,oe),r(p)===null&&oe===r(g)&&(C?(U(Z),Z=-1):C=!0,le(z,J-M))):(oe.sortIndex=W,t(p,oe),N||T||(N=!0,ke($))),oe},l.unstable_shouldYield=q,l.unstable_wrapCallback=function(oe){var ae=x;return function(){var J=x;x=ae;try{return oe.apply(this,arguments)}finally{x=J}}}})(lf)),lf}var cp;function Kv(){return cp||(cp=1,sf.exports=Gv()),sf.exports}/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var fp;function Yv(){if(fp)return sr;fp=1;var l=Hf(),t=Kv();function r(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,s=1;s"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),p=Object.prototype.hasOwnProperty,g=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,y={},v={};function x(e){return p.call(v,e)?!0:p.call(y,e)?!1:g.test(e)?v[e]=!0:(y[e]=!0,!1)}function T(e,n,s,a){if(s!==null&&s.type===0)return!1;switch(typeof n){case"function":case"symbol":return!0;case"boolean":return a?!1:s!==null?!s.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function N(e,n,s,a){if(n===null||typeof n>"u"||T(e,n,s,a))return!0;if(a)return!1;if(s!==null)switch(s.type){case 3:return!n;case 4:return n===!1;case 5:return isNaN(n);case 6:return isNaN(n)||1>n}return!1}function C(e,n,s,a,f,h,w){this.acceptsBooleans=n===2||n===3||n===4,this.attributeName=a,this.attributeNamespace=f,this.mustUseProperty=s,this.propertyName=e,this.type=n,this.sanitizeURL=h,this.removeEmptyString=w}var L={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){L[e]=new C(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var n=e[0];L[n]=new C(n,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){L[e]=new C(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){L[e]=new C(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){L[e]=new C(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){L[e]=new C(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){L[e]=new C(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){L[e]=new C(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){L[e]=new C(e,5,!1,e.toLowerCase(),null,!1,!1)});var U=/[\-:]([a-z])/g;function A(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var n=e.replace(U,A);L[n]=new C(n,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var n=e.replace(U,A);L[n]=new C(n,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var n=e.replace(U,A);L[n]=new C(n,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){L[e]=new C(e,1,!1,e.toLowerCase(),null,!1,!1)}),L.xlinkHref=new C("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){L[e]=new C(e,1,!1,e.toLowerCase(),null,!0,!0)});function V(e,n,s,a){var f=L.hasOwnProperty(n)?L[n]:null;(f!==null?f.type!==0:a||!(2k||f[w]!==h[k]){var b=` +`+f[w].replace(" at new "," at ");return e.displayName&&b.includes("")&&(b=b.replace("",e.displayName)),b}while(1<=w&&0<=k);break}}}finally{X=!1,Error.prepareStackTrace=s}return(e=e?e.displayName||e.name:"")?W(e):""}function be(e){switch(e.tag){case 5:return W(e.type);case 16:return W("Lazy");case 13:return W("Suspense");case 19:return W("SuspenseList");case 0:case 2:case 15:return e=ee(e.type,!1),e;case 11:return e=ee(e.type.render,!1),e;case 1:return e=ee(e.type,!0),e;default:return""}}function he(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Y:return"Fragment";case G:return"Portal";case K:return"Profiler";case Z:return"StrictMode";case ce:return"Suspense";case ge:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case q:return(e.displayName||"Context")+".Consumer";case fe:return(e._context.displayName||"Context")+".Provider";case xe:var n=e.render;return e=e.displayName,e||(e=n.displayName||n.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case ye:return n=e.displayName||null,n!==null?n:he(e.type)||"Memo";case ke:n=e._payload,e=e._init;try{return he(e(n))}catch{}}return null}function Ee(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:return(n.displayName||"Context")+".Consumer";case 10:return(n._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=n.render,e=e.displayName||e.name||"",n.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return n;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return he(n);case 8:return n===Z?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n}return null}function Ie(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Fe(e){var n=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(n==="checkbox"||n==="radio")}function Oe(e){var n=Fe(e)?"checked":"value",s=Object.getOwnPropertyDescriptor(e.constructor.prototype,n),a=""+e[n];if(!e.hasOwnProperty(n)&&typeof s<"u"&&typeof s.get=="function"&&typeof s.set=="function"){var f=s.get,h=s.set;return Object.defineProperty(e,n,{configurable:!0,get:function(){return f.call(this)},set:function(w){a=""+w,h.call(this,w)}}),Object.defineProperty(e,n,{enumerable:s.enumerable}),{getValue:function(){return a},setValue:function(w){a=""+w},stopTracking:function(){e._valueTracker=null,delete e[n]}}}}function Gt(e){e._valueTracker||(e._valueTracker=Oe(e))}function At(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var s=n.getValue(),a="";return e&&(a=Fe(e)?e.checked?"true":"false":e.value),e=a,e!==s?(n.setValue(e),!0):!1}function jt(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function It(e,n){var s=n.checked;return J({},n,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:s??e._wrapperState.initialChecked})}function Qn(e,n){var s=n.defaultValue==null?"":n.defaultValue,a=n.checked!=null?n.checked:n.defaultChecked;s=Ie(n.value!=null?n.value:s),e._wrapperState={initialChecked:a,initialValue:s,controlled:n.type==="checkbox"||n.type==="radio"?n.checked!=null:n.value!=null}}function kn(e,n){n=n.checked,n!=null&&V(e,"checked",n,!1)}function Er(e,n){kn(e,n);var s=Ie(n.value),a=n.type;if(s!=null)a==="number"?(s===0&&e.value===""||e.value!=s)&&(e.value=""+s):e.value!==""+s&&(e.value=""+s);else if(a==="submit"||a==="reset"){e.removeAttribute("value");return}n.hasOwnProperty("value")?An(e,n.type,s):n.hasOwnProperty("defaultValue")&&An(e,n.type,Ie(n.defaultValue)),n.checked==null&&n.defaultChecked!=null&&(e.defaultChecked=!!n.defaultChecked)}function Xr(e,n,s){if(n.hasOwnProperty("value")||n.hasOwnProperty("defaultValue")){var a=n.type;if(!(a!=="submit"&&a!=="reset"||n.value!==void 0&&n.value!==null))return;n=""+e._wrapperState.initialValue,s||n===e.value||(e.value=n),e.defaultValue=n}s=e.name,s!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,s!==""&&(e.name=s)}function An(e,n,s){(n!=="number"||jt(e.ownerDocument)!==e)&&(s==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+s&&(e.defaultValue=""+s))}var et=Array.isArray;function rn(e,n,s,a){if(e=e.options,n){n={};for(var f=0;f"+n.valueOf().toString()+"",n=ln.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;n.firstChild;)e.appendChild(n.firstChild)}});function Kt(e,n){if(n){var s=e.firstChild;if(s&&s===e.lastChild&&s.nodeType===3){s.nodeValue=n;return}}e.textContent=n}var Nt={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},on=["Webkit","ms","Moz","O"];Object.keys(Nt).forEach(function(e){on.forEach(function(n){n=n+e.charAt(0).toUpperCase()+e.substring(1),Nt[n]=Nt[e]})});function gn(e,n,s){return n==null||typeof n=="boolean"||n===""?"":s||typeof n!="number"||n===0||Nt.hasOwnProperty(e)&&Nt[e]?(""+n).trim():n+"px"}function Yt(e,n){e=e.style;for(var s in n)if(n.hasOwnProperty(s)){var a=s.indexOf("--")===0,f=gn(s,n[s],a);s==="float"&&(s="cssFloat"),a?e.setProperty(s,f):e[s]=f}}var vn=J({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Jr(e,n){if(n){if(vn[e]&&(n.children!=null||n.dangerouslySetInnerHTML!=null))throw Error(r(137,e));if(n.dangerouslySetInnerHTML!=null){if(n.children!=null)throw Error(r(60));if(typeof n.dangerouslySetInnerHTML!="object"||!("__html"in n.dangerouslySetInnerHTML))throw Error(r(61))}if(n.style!=null&&typeof n.style!="object")throw Error(r(62))}}function Zr(e,n){if(e.indexOf("-")===-1)return typeof n.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var or=null;function ar(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var ei=null,Dt=null,ot=null;function Qt(e){if(e=Zl(e)){if(typeof ei!="function")throw Error(r(280));var n=e.stateNode;n&&(n=fa(n),ei(e.stateNode,e.type,n))}}function an(e){Dt?ot?ot.push(e):ot=[e]:Dt=e}function ur(){if(Dt){var e=Dt,n=ot;if(ot=Dt=null,Qt(e),n)for(e=0;e>>=0,e===0?32:31-(jl(e)/bn|0)|0}var cs=64,Di=4194304;function zi(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Bs(e,n){var s=e.pendingLanes;if(s===0)return 0;var a=0,f=e.suspendedLanes,h=e.pingedLanes,w=s&268435455;if(w!==0){var k=w&~f;k!==0?a=zi(k):(h&=w,h!==0&&(a=zi(h)))}else w=s&~f,w!==0?a=zi(w):h!==0&&(a=zi(h));if(a===0)return 0;if(n!==0&&n!==a&&(n&f)===0&&(f=a&-a,h=n&-n,f>=h||f===16&&(h&4194240)!==0))return n;if((a&4)!==0&&(a|=s&16),n=e.entangledLanes,n!==0)for(e=e.entanglements,n&=a;0s;s++)n.push(e);return n}function Oi(e,n,s){e.pendingLanes|=n,n!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,n=31-In(n),e[n]=s}function Hl(e,n){var s=e.pendingLanes&~n;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=n,e.mutableReadLanes&=n,e.entangledLanes&=n,n=e.entanglements;var a=e.eventTimes;for(e=e.expirationTimes;0=gi),ia=" ",Js=!1;function m(e,n){switch(e){case"keyup":return Tt.indexOf(n.keyCode)!==-1;case"keydown":return n.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function S(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var _=!1;function E(e,n){switch(e){case"compositionend":return S(n);case"keypress":return n.which!==32?null:(Js=!0,ia);case"textInput":return e=n.data,e===ia&&Js?null:e;default:return null}}function D(e,n){if(_)return e==="compositionend"||!Xs&&m(e,n)?(e=hr(),dr=Vl=fr=null,_=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(n.ctrlKey||n.altKey||n.metaKey)||n.ctrlKey&&n.altKey){if(n.char&&1=n)return{node:s,offset:n-e};e=a}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=Zn(s)}}function Mn(e,n){return e&&n?e===n?!0:e&&e.nodeType===3?!1:n&&n.nodeType===3?Mn(e,n.parentNode):"contains"in e?e.contains(n):e.compareDocumentPosition?!!(e.compareDocumentPosition(n)&16):!1:!1}function Bn(){for(var e=window,n=jt();n instanceof e.HTMLIFrameElement;){try{var s=typeof n.contentWindow.location.href=="string"}catch{s=!1}if(s)e=n.contentWindow;else break;n=jt(e.document)}return n}function Un(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&(n==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||n==="textarea"||e.contentEditable==="true")}function br(e){var n=Bn(),s=e.focusedElem,a=e.selectionRange;if(n!==s&&s&&s.ownerDocument&&Mn(s.ownerDocument.documentElement,s)){if(a!==null&&Un(s)){if(n=a.start,e=a.end,e===void 0&&(e=n),"selectionStart"in s)s.selectionStart=n,s.selectionEnd=Math.min(e,s.value.length);else if(e=(n=s.ownerDocument||document)&&n.defaultView||window,e.getSelection){e=e.getSelection();var f=s.textContent.length,h=Math.min(a.start,f);a=a.end===void 0?h:Math.min(a.end,f),!e.extend&&h>a&&(f=a,a=h,h=f),f=mr(s,h);var w=mr(s,a);f&&w&&(e.rangeCount!==1||e.anchorNode!==f.node||e.anchorOffset!==f.offset||e.focusNode!==w.node||e.focusOffset!==w.offset)&&(n=n.createRange(),n.setStart(f.node,f.offset),e.removeAllRanges(),h>a?(e.addRange(n),e.extend(w.node,w.offset)):(n.setEnd(w.node,w.offset),e.addRange(n)))}}for(n=[],e=s;e=e.parentNode;)e.nodeType===1&&n.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof s.focus=="function"&&s.focus(),s=0;s=document.documentMode,Ut=null,Hr=null,Lt=null,Zs=!1;function dd(e,n,s){var a=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;Zs||Ut==null||Ut!==jt(a)||(a=Ut,"selectionStart"in a&&Un(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Lt&&fn(Lt,a)||(Lt=a,a=aa(Hr,"onSelect"),0il||(e.current=Ju[il],Ju[il]=null,il--)}function ht(e,n){il++,Ju[il]=e.current,e.current=n}var Ki={},Dn=Gi(Ki),er=Gi(!1),xs=Ki;function sl(e,n){var s=e.type.contextTypes;if(!s)return Ki;var a=e.stateNode;if(a&&a.__reactInternalMemoizedUnmaskedChildContext===n)return a.__reactInternalMemoizedMaskedChildContext;var f={},h;for(h in s)f[h]=n[h];return a&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=f),f}function tr(e){return e=e.childContextTypes,e!=null}function da(){gt(er),gt(Dn)}function Nd(e,n,s){if(Dn.current!==Ki)throw Error(r(168));ht(Dn,n),ht(er,s)}function bd(e,n,s){var a=e.stateNode;if(n=n.childContextTypes,typeof a.getChildContext!="function")return s;a=a.getChildContext();for(var f in a)if(!(f in n))throw Error(r(108,Ee(e)||"Unknown",f));return J({},s,a)}function ha(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ki,xs=Dn.current,ht(Dn,e),ht(er,er.current),!0}function Td(e,n,s){var a=e.stateNode;if(!a)throw Error(r(169));s?(e=bd(e,n,xs),a.__reactInternalMemoizedMergedChildContext=e,gt(er),gt(Dn),ht(Dn,e)):gt(er),ht(er,s)}var yi=null,pa=!1,Zu=!1;function Md(e){yi===null?yi=[e]:yi.push(e)}function lv(e){pa=!0,Md(e)}function Yi(){if(!Zu&&yi!==null){Zu=!0;var e=0,n=Ge;try{var s=yi;for(Ge=1;e>=w,f-=w,wi=1<<32-In(n)+f|s<je?(pn=ze,ze=null):pn=ze.sibling;var qe=te(j,ze,H[je],ue);if(qe===null){ze===null&&(ze=pn);break}e&&ze&&qe.alternate===null&&n(j,ze),O=h(qe,O,je),De===null?Ne=qe:De.sibling=qe,De=qe,ze=pn}if(je===H.length)return s(j,ze),xt&&Es(j,je),Ne;if(ze===null){for(;jeje?(pn=ze,ze=null):pn=ze.sibling;var rs=te(j,ze,qe.value,ue);if(rs===null){ze===null&&(ze=pn);break}e&&ze&&rs.alternate===null&&n(j,ze),O=h(rs,O,je),De===null?Ne=rs:De.sibling=rs,De=rs,ze=pn}if(qe.done)return s(j,ze),xt&&Es(j,je),Ne;if(ze===null){for(;!qe.done;je++,qe=H.next())qe=ie(j,qe.value,ue),qe!==null&&(O=h(qe,O,je),De===null?Ne=qe:De.sibling=qe,De=qe);return xt&&Es(j,je),Ne}for(ze=a(j,ze);!qe.done;je++,qe=H.next())qe=we(ze,j,je,qe.value,ue),qe!==null&&(e&&qe.alternate!==null&&ze.delete(qe.key===null?je:qe.key),O=h(qe,O,je),De===null?Ne=qe:De.sibling=qe,De=qe);return e&&ze.forEach(function(Hv){return n(j,Hv)}),xt&&Es(j,je),Ne}function Pt(j,O,H,ue){if(typeof H=="object"&&H!==null&&H.type===Y&&H.key===null&&(H=H.props.children),typeof H=="object"&&H!==null){switch(H.$$typeof){case $:e:{for(var Ne=H.key,De=O;De!==null;){if(De.key===Ne){if(Ne=H.type,Ne===Y){if(De.tag===7){s(j,De.sibling),O=f(De,H.props.children),O.return=j,j=O;break e}}else if(De.elementType===Ne||typeof Ne=="object"&&Ne!==null&&Ne.$$typeof===ke&&Ad(Ne)===De.type){s(j,De.sibling),O=f(De,H.props),O.ref=eo(j,De,H),O.return=j,j=O;break e}s(j,De);break}else n(j,De);De=De.sibling}H.type===Y?(O=Ds(H.props.children,j.mode,ue,H.key),O.return=j,j=O):(ue=Ba(H.type,H.key,H.props,null,j.mode,ue),ue.ref=eo(j,O,H),ue.return=j,j=ue)}return w(j);case G:e:{for(De=H.key;O!==null;){if(O.key===De)if(O.tag===4&&O.stateNode.containerInfo===H.containerInfo&&O.stateNode.implementation===H.implementation){s(j,O.sibling),O=f(O,H.children||[]),O.return=j,j=O;break e}else{s(j,O);break}else n(j,O);O=O.sibling}O=qc(H,j.mode,ue),O.return=j,j=O}return w(j);case ke:return De=H._init,Pt(j,O,De(H._payload),ue)}if(et(H))return _e(j,O,H,ue);if(ae(H))return Ce(j,O,H,ue);ya(j,H)}return typeof H=="string"&&H!==""||typeof H=="number"?(H=""+H,O!==null&&O.tag===6?(s(j,O.sibling),O=f(O,H),O.return=j,j=O):(s(j,O),O=Qc(H,j.mode,ue),O.return=j,j=O),w(j)):s(j,O)}return Pt}var ul=jd(!0),Id=jd(!1),wa=Gi(null),Sa=null,cl=null,sc=null;function lc(){sc=cl=Sa=null}function oc(e){var n=wa.current;gt(wa),e._currentValue=n}function ac(e,n,s){for(;e!==null;){var a=e.alternate;if((e.childLanes&n)!==n?(e.childLanes|=n,a!==null&&(a.childLanes|=n)):a!==null&&(a.childLanes&n)!==n&&(a.childLanes|=n),e===s)break;e=e.return}}function fl(e,n){Sa=e,sc=cl=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&n)!==0&&(nr=!0),e.firstContext=null)}function Dr(e){var n=e._currentValue;if(sc!==e)if(e={context:e,memoizedValue:n,next:null},cl===null){if(Sa===null)throw Error(r(308));cl=e,Sa.dependencies={lanes:0,firstContext:e}}else cl=cl.next=e;return n}var Cs=null;function uc(e){Cs===null?Cs=[e]:Cs.push(e)}function Fd(e,n,s,a){var f=n.interleaved;return f===null?(s.next=s,uc(n)):(s.next=f.next,f.next=s),n.interleaved=s,xi(e,a)}function xi(e,n){e.lanes|=n;var s=e.alternate;for(s!==null&&(s.lanes|=n),s=e,e=e.return;e!==null;)e.childLanes|=n,s=e.alternate,s!==null&&(s.childLanes|=n),s=e,e=e.return;return s.tag===3?s.stateNode:null}var Qi=!1;function cc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Hd(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function _i(e,n){return{eventTime:e,lane:n,tag:0,payload:null,callback:null,next:null}}function qi(e,n,s){var a=e.updateQueue;if(a===null)return null;if(a=a.shared,(Ye&2)!==0){var f=a.pending;return f===null?n.next=n:(n.next=f.next,f.next=n),a.pending=n,xi(e,s)}return f=a.interleaved,f===null?(n.next=n,uc(a)):(n.next=f.next,f.next=n),a.interleaved=n,xi(e,s)}function xa(e,n,s){if(n=n.updateQueue,n!==null&&(n=n.shared,(s&4194240)!==0)){var a=n.lanes;a&=e.pendingLanes,s|=a,n.lanes=s,Li(e,s)}}function Wd(e,n){var s=e.updateQueue,a=e.alternate;if(a!==null&&(a=a.updateQueue,s===a)){var f=null,h=null;if(s=s.firstBaseUpdate,s!==null){do{var w={eventTime:s.eventTime,lane:s.lane,tag:s.tag,payload:s.payload,callback:s.callback,next:null};h===null?f=h=w:h=h.next=w,s=s.next}while(s!==null);h===null?f=h=n:h=h.next=n}else f=h=n;s={baseState:a.baseState,firstBaseUpdate:f,lastBaseUpdate:h,shared:a.shared,effects:a.effects},e.updateQueue=s;return}e=s.lastBaseUpdate,e===null?s.firstBaseUpdate=n:e.next=n,s.lastBaseUpdate=n}function _a(e,n,s,a){var f=e.updateQueue;Qi=!1;var h=f.firstBaseUpdate,w=f.lastBaseUpdate,k=f.shared.pending;if(k!==null){f.shared.pending=null;var b=k,B=b.next;b.next=null,w===null?h=B:w.next=B,w=b;var re=e.alternate;re!==null&&(re=re.updateQueue,k=re.lastBaseUpdate,k!==w&&(k===null?re.firstBaseUpdate=B:k.next=B,re.lastBaseUpdate=b))}if(h!==null){var ie=f.baseState;w=0,re=B=b=null,k=h;do{var te=k.lane,we=k.eventTime;if((a&te)===te){re!==null&&(re=re.next={eventTime:we,lane:0,tag:k.tag,payload:k.payload,callback:k.callback,next:null});e:{var _e=e,Ce=k;switch(te=n,we=s,Ce.tag){case 1:if(_e=Ce.payload,typeof _e=="function"){ie=_e.call(we,ie,te);break e}ie=_e;break e;case 3:_e.flags=_e.flags&-65537|128;case 0:if(_e=Ce.payload,te=typeof _e=="function"?_e.call(we,ie,te):_e,te==null)break e;ie=J({},ie,te);break e;case 2:Qi=!0}}k.callback!==null&&k.lane!==0&&(e.flags|=64,te=f.effects,te===null?f.effects=[k]:te.push(k))}else we={eventTime:we,lane:te,tag:k.tag,payload:k.payload,callback:k.callback,next:null},re===null?(B=re=we,b=ie):re=re.next=we,w|=te;if(k=k.next,k===null){if(k=f.shared.pending,k===null)break;te=k,k=te.next,te.next=null,f.lastBaseUpdate=te,f.shared.pending=null}}while(!0);if(re===null&&(b=ie),f.baseState=b,f.firstBaseUpdate=B,f.lastBaseUpdate=re,n=f.shared.interleaved,n!==null){f=n;do w|=f.lane,f=f.next;while(f!==n)}else h===null&&(f.shared.lanes=0);Ns|=w,e.lanes=w,e.memoizedState=ie}}function Bd(e,n,s){if(e=n.effects,n.effects=null,e!==null)for(n=0;ns?s:4,e(!0);var a=mc.transition;mc.transition={};try{e(!1),n()}finally{Ge=s,mc.transition=a}}function oh(){return zr().memoizedState}function cv(e,n,s){var a=es(e);if(s={lane:a,action:s,hasEagerState:!1,eagerState:null,next:null},ah(e))uh(n,s);else if(s=Fd(e,n,s,a),s!==null){var f=$n();$r(s,e,a,f),ch(s,n,a)}}function fv(e,n,s){var a=es(e),f={lane:a,action:s,hasEagerState:!1,eagerState:null,next:null};if(ah(e))uh(n,f);else{var h=e.alternate;if(e.lanes===0&&(h===null||h.lanes===0)&&(h=n.lastRenderedReducer,h!==null))try{var w=n.lastRenderedState,k=h(w,s);if(f.hasEagerState=!0,f.eagerState=k,ut(k,w)){var b=n.interleaved;b===null?(f.next=f,uc(n)):(f.next=b.next,b.next=f),n.interleaved=f;return}}catch{}finally{}s=Fd(e,n,f,a),s!==null&&(f=$n(),$r(s,e,a,f),ch(s,n,a))}}function ah(e){var n=e.alternate;return e===Rt||n!==null&&n===Rt}function uh(e,n){io=ka=!0;var s=e.pending;s===null?n.next=n:(n.next=s.next,s.next=n),e.pending=n}function ch(e,n,s){if((s&4194240)!==0){var a=n.lanes;a&=e.pendingLanes,s|=a,n.lanes=s,Li(e,s)}}var ba={readContext:Dr,useCallback:zn,useContext:zn,useEffect:zn,useImperativeHandle:zn,useInsertionEffect:zn,useLayoutEffect:zn,useMemo:zn,useReducer:zn,useRef:zn,useState:zn,useDebugValue:zn,useDeferredValue:zn,useTransition:zn,useMutableSource:zn,useSyncExternalStore:zn,useId:zn,unstable_isNewReconciler:!1},dv={readContext:Dr,useCallback:function(e,n){return li().memoizedState=[e,n===void 0?null:n],e},useContext:Dr,useEffect:Zd,useImperativeHandle:function(e,n,s){return s=s!=null?s.concat([e]):null,Ra(4194308,4,nh.bind(null,n,e),s)},useLayoutEffect:function(e,n){return Ra(4194308,4,e,n)},useInsertionEffect:function(e,n){return Ra(4,2,e,n)},useMemo:function(e,n){var s=li();return n=n===void 0?null:n,e=e(),s.memoizedState=[e,n],e},useReducer:function(e,n,s){var a=li();return n=s!==void 0?s(n):n,a.memoizedState=a.baseState=n,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=cv.bind(null,Rt,e),[a.memoizedState,e]},useRef:function(e){var n=li();return e={current:e},n.memoizedState=e},useState:Xd,useDebugValue:_c,useDeferredValue:function(e){return li().memoizedState=e},useTransition:function(){var e=Xd(!1),n=e[0];return e=uv.bind(null,e[1]),li().memoizedState=e,[n,e]},useMutableSource:function(){},useSyncExternalStore:function(e,n,s){var a=Rt,f=li();if(xt){if(s===void 0)throw Error(r(407));s=s()}else{if(s=n(),hn===null)throw Error(r(349));(Rs&30)!==0||Gd(a,n,s)}f.memoizedState=s;var h={value:s,getSnapshot:n};return f.queue=h,Zd(Yd.bind(null,a,h,e),[e]),a.flags|=2048,oo(9,Kd.bind(null,a,h,s,n),void 0,null),s},useId:function(){var e=li(),n=hn.identifierPrefix;if(xt){var s=Si,a=wi;s=(a&~(1<<32-In(a)-1)).toString(32)+s,n=":"+n+"R"+s,s=so++,0<\/script>",e=e.removeChild(e.firstChild)):typeof a.is=="string"?e=w.createElement(s,{is:a.is}):(e=w.createElement(s),s==="select"&&(w=e,a.multiple?w.multiple=!0:a.size&&(w.size=a.size))):e=w.createElementNS(e,s),e[ii]=n,e[Jl]=a,Mh(e,n,!1,!1),n.stateNode=e;e:{switch(w=Zr(s,a),s){case"dialog":mt("cancel",e),mt("close",e),f=a;break;case"iframe":case"object":case"embed":mt("load",e),f=a;break;case"video":case"audio":for(f=0;fgl&&(n.flags|=128,a=!0,ao(h,!1),n.lanes=4194304)}else{if(!a)if(e=Ea(w),e!==null){if(n.flags|=128,a=!0,s=e.updateQueue,s!==null&&(n.updateQueue=s,n.flags|=4),ao(h,!0),h.tail===null&&h.tailMode==="hidden"&&!w.alternate&&!xt)return On(n),null}else 2*at()-h.renderingStartTime>gl&&s!==1073741824&&(n.flags|=128,a=!0,ao(h,!1),n.lanes=4194304);h.isBackwards?(w.sibling=n.child,n.child=w):(s=h.last,s!==null?s.sibling=w:n.child=w,h.last=w)}return h.tail!==null?(n=h.tail,h.rendering=n,h.tail=n.sibling,h.renderingStartTime=at(),n.sibling=null,s=kt.current,ht(kt,a?s&1|2:s&1),n):(On(n),null);case 22:case 23:return Gc(),a=n.memoizedState!==null,e!==null&&e.memoizedState!==null!==a&&(n.flags|=8192),a&&(n.mode&1)!==0?(yr&1073741824)!==0&&(On(n),n.subtreeFlags&6&&(n.flags|=8192)):On(n),null;case 24:return null;case 25:return null}throw Error(r(156,n.tag))}function Sv(e,n){switch(tc(n),n.tag){case 1:return tr(n.type)&&da(),e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 3:return dl(),gt(er),gt(Dn),pc(),e=n.flags,(e&65536)!==0&&(e&128)===0?(n.flags=e&-65537|128,n):null;case 5:return dc(n),null;case 13:if(gt(kt),e=n.memoizedState,e!==null&&e.dehydrated!==null){if(n.alternate===null)throw Error(r(340));al()}return e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 19:return gt(kt),null;case 4:return dl(),null;case 10:return oc(n.type._context),null;case 22:case 23:return Gc(),null;case 24:return null;default:return null}}var za=!1,Ln=!1,xv=typeof WeakSet=="function"?WeakSet:Set,Se=null;function pl(e,n){var s=e.ref;if(s!==null)if(typeof s=="function")try{s(null)}catch(a){Mt(e,n,a)}else s.current=null}function Lc(e,n,s){try{s()}catch(a){Mt(e,n,a)}}var Oh=!1;function _v(e,n){if(Gu=it,e=Bn(),Un(e)){if("selectionStart"in e)var s={start:e.selectionStart,end:e.selectionEnd};else e:{s=(s=e.ownerDocument)&&s.defaultView||window;var a=s.getSelection&&s.getSelection();if(a&&a.rangeCount!==0){s=a.anchorNode;var f=a.anchorOffset,h=a.focusNode;a=a.focusOffset;try{s.nodeType,h.nodeType}catch{s=null;break e}var w=0,k=-1,b=-1,B=0,re=0,ie=e,te=null;t:for(;;){for(var we;ie!==s||f!==0&&ie.nodeType!==3||(k=w+f),ie!==h||a!==0&&ie.nodeType!==3||(b=w+a),ie.nodeType===3&&(w+=ie.nodeValue.length),(we=ie.firstChild)!==null;)te=ie,ie=we;for(;;){if(ie===e)break t;if(te===s&&++B===f&&(k=w),te===h&&++re===a&&(b=w),(we=ie.nextSibling)!==null)break;ie=te,te=ie.parentNode}ie=we}s=k===-1||b===-1?null:{start:k,end:b}}else s=null}s=s||{start:0,end:0}}else s=null;for(Ku={focusedElem:e,selectionRange:s},it=!1,Se=n;Se!==null;)if(n=Se,e=n.child,(n.subtreeFlags&1028)!==0&&e!==null)e.return=n,Se=e;else for(;Se!==null;){n=Se;try{var _e=n.alternate;if((n.flags&1024)!==0)switch(n.tag){case 0:case 11:case 15:break;case 1:if(_e!==null){var Ce=_e.memoizedProps,Pt=_e.memoizedState,j=n.stateNode,O=j.getSnapshotBeforeUpdate(n.elementType===n.type?Ce:Br(n.type,Ce),Pt);j.__reactInternalSnapshotBeforeUpdate=O}break;case 3:var H=n.stateNode.containerInfo;H.nodeType===1?H.textContent="":H.nodeType===9&&H.documentElement&&H.removeChild(H.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(r(163))}}catch(ue){Mt(n,n.return,ue)}if(e=n.sibling,e!==null){e.return=n.return,Se=e;break}Se=n.return}return _e=Oh,Oh=!1,_e}function uo(e,n,s){var a=n.updateQueue;if(a=a!==null?a.lastEffect:null,a!==null){var f=a=a.next;do{if((f.tag&e)===e){var h=f.destroy;f.destroy=void 0,h!==void 0&&Lc(n,s,h)}f=f.next}while(f!==a)}}function Oa(e,n){if(n=n.updateQueue,n=n!==null?n.lastEffect:null,n!==null){var s=n=n.next;do{if((s.tag&e)===e){var a=s.create;s.destroy=a()}s=s.next}while(s!==n)}}function Pc(e){var n=e.ref;if(n!==null){var s=e.stateNode;switch(e.tag){case 5:e=s;break;default:e=s}typeof n=="function"?n(e):n.current=e}}function Lh(e){var n=e.alternate;n!==null&&(e.alternate=null,Lh(n)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(n=e.stateNode,n!==null&&(delete n[ii],delete n[Jl],delete n[Xu],delete n[iv],delete n[sv])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Ph(e){return e.tag===5||e.tag===3||e.tag===4}function Ah(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Ph(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Ac(e,n,s){var a=e.tag;if(a===5||a===6)e=e.stateNode,n?s.nodeType===8?s.parentNode.insertBefore(e,n):s.insertBefore(e,n):(s.nodeType===8?(n=s.parentNode,n.insertBefore(e,s)):(n=s,n.appendChild(e)),s=s._reactRootContainer,s!=null||n.onclick!==null||(n.onclick=ca));else if(a!==4&&(e=e.child,e!==null))for(Ac(e,n,s),e=e.sibling;e!==null;)Ac(e,n,s),e=e.sibling}function jc(e,n,s){var a=e.tag;if(a===5||a===6)e=e.stateNode,n?s.insertBefore(e,n):s.appendChild(e);else if(a!==4&&(e=e.child,e!==null))for(jc(e,n,s),e=e.sibling;e!==null;)jc(e,n,s),e=e.sibling}var _n=null,Ur=!1;function Xi(e,n,s){for(s=s.child;s!==null;)jh(e,n,s),s=s.sibling}function jh(e,n,s){if(qn&&typeof qn.onCommitFiberUnmount=="function")try{qn.onCommitFiberUnmount(Mi,s)}catch{}switch(s.tag){case 5:Ln||pl(s,n);case 6:var a=_n,f=Ur;_n=null,Xi(e,n,s),_n=a,Ur=f,_n!==null&&(Ur?(e=_n,s=s.stateNode,e.nodeType===8?e.parentNode.removeChild(s):e.removeChild(s)):_n.removeChild(s.stateNode));break;case 18:_n!==null&&(Ur?(e=_n,s=s.stateNode,e.nodeType===8?qu(e.parentNode,s):e.nodeType===1&&qu(e,s),Wi(e)):qu(_n,s.stateNode));break;case 4:a=_n,f=Ur,_n=s.stateNode.containerInfo,Ur=!0,Xi(e,n,s),_n=a,Ur=f;break;case 0:case 11:case 14:case 15:if(!Ln&&(a=s.updateQueue,a!==null&&(a=a.lastEffect,a!==null))){f=a=a.next;do{var h=f,w=h.destroy;h=h.tag,w!==void 0&&((h&2)!==0||(h&4)!==0)&&Lc(s,n,w),f=f.next}while(f!==a)}Xi(e,n,s);break;case 1:if(!Ln&&(pl(s,n),a=s.stateNode,typeof a.componentWillUnmount=="function"))try{a.props=s.memoizedProps,a.state=s.memoizedState,a.componentWillUnmount()}catch(k){Mt(s,n,k)}Xi(e,n,s);break;case 21:Xi(e,n,s);break;case 22:s.mode&1?(Ln=(a=Ln)||s.memoizedState!==null,Xi(e,n,s),Ln=a):Xi(e,n,s);break;default:Xi(e,n,s)}}function Ih(e){var n=e.updateQueue;if(n!==null){e.updateQueue=null;var s=e.stateNode;s===null&&(s=e.stateNode=new xv),n.forEach(function(a){var f=Dv.bind(null,e,a);s.has(a)||(s.add(a),a.then(f,f))})}}function Vr(e,n){var s=n.deletions;if(s!==null)for(var a=0;af&&(f=w),a&=~h}if(a=f,a=at()-a,a=(120>a?120:480>a?480:1080>a?1080:1920>a?1920:3e3>a?3e3:4320>a?4320:1960*Cv(a/1960))-a,10e?16:e,Zi===null)var a=!1;else{if(e=Zi,Zi=null,Ia=0,(Ye&6)!==0)throw Error(r(331));var f=Ye;for(Ye|=4,Se=e.current;Se!==null;){var h=Se,w=h.child;if((Se.flags&16)!==0){var k=h.deletions;if(k!==null){for(var b=0;bat()-Hc?Ts(e,0):Fc|=s),ir(e,n)}function Xh(e,n){n===0&&((e.mode&1)===0?n=1:(n=Di,Di<<=1,(Di&130023424)===0&&(Di=4194304)));var s=$n();e=xi(e,n),e!==null&&(Oi(e,n,s),ir(e,s))}function Mv(e){var n=e.memoizedState,s=0;n!==null&&(s=n.retryLane),Xh(e,s)}function Dv(e,n){var s=0;switch(e.tag){case 13:var a=e.stateNode,f=e.memoizedState;f!==null&&(s=f.retryLane);break;case 19:a=e.stateNode;break;default:throw Error(r(314))}a!==null&&a.delete(n),Xh(e,s)}var Jh;Jh=function(e,n,s){if(e!==null)if(e.memoizedProps!==n.pendingProps||er.current)nr=!0;else{if((e.lanes&s)===0&&(n.flags&128)===0)return nr=!1,yv(e,n,s);nr=(e.flags&131072)!==0}else nr=!1,xt&&(n.flags&1048576)!==0&&Dd(n,ga,n.index);switch(n.lanes=0,n.tag){case 2:var a=n.type;Da(e,n),e=n.pendingProps;var f=sl(n,Dn.current);fl(n,s),f=vc(null,n,a,e,f,s);var h=yc();return n.flags|=1,typeof f=="object"&&f!==null&&typeof f.render=="function"&&f.$$typeof===void 0?(n.tag=1,n.memoizedState=null,n.updateQueue=null,tr(a)?(h=!0,ha(n)):h=!1,n.memoizedState=f.state!==null&&f.state!==void 0?f.state:null,cc(n),f.updater=Ta,n.stateNode=f,f._reactInternals=n,Cc(n,a,e,s),n=bc(null,n,a,!0,h,s)):(n.tag=0,xt&&h&&ec(n),Vn(null,n,f,s),n=n.child),n;case 16:a=n.elementType;e:{switch(Da(e,n),e=n.pendingProps,f=a._init,a=f(a._payload),n.type=a,f=n.tag=Ov(a),e=Br(a,e),f){case 0:n=Nc(null,n,a,e,s);break e;case 1:n=Ch(null,n,a,e,s);break e;case 11:n=wh(null,n,a,e,s);break e;case 14:n=Sh(null,n,a,Br(a.type,e),s);break e}throw Error(r(306,a,""))}return n;case 0:return a=n.type,f=n.pendingProps,f=n.elementType===a?f:Br(a,f),Nc(e,n,a,f,s);case 1:return a=n.type,f=n.pendingProps,f=n.elementType===a?f:Br(a,f),Ch(e,n,a,f,s);case 3:e:{if(kh(n),e===null)throw Error(r(387));a=n.pendingProps,h=n.memoizedState,f=h.element,Hd(e,n),_a(n,a,null,s);var w=n.memoizedState;if(a=w.element,h.isDehydrated)if(h={element:a,isDehydrated:!1,cache:w.cache,pendingSuspenseBoundaries:w.pendingSuspenseBoundaries,transitions:w.transitions},n.updateQueue.baseState=h,n.memoizedState=h,n.flags&256){f=hl(Error(r(423)),n),n=Rh(e,n,a,s,f);break e}else if(a!==f){f=hl(Error(r(424)),n),n=Rh(e,n,a,s,f);break e}else for(vr=$i(n.stateNode.containerInfo.firstChild),gr=n,xt=!0,Wr=null,s=Id(n,null,a,s),n.child=s;s;)s.flags=s.flags&-3|4096,s=s.sibling;else{if(al(),a===f){n=Ei(e,n,s);break e}Vn(e,n,a,s)}n=n.child}return n;case 5:return Ud(n),e===null&&rc(n),a=n.type,f=n.pendingProps,h=e!==null?e.memoizedProps:null,w=f.children,Yu(a,f)?w=null:h!==null&&Yu(a,h)&&(n.flags|=32),Eh(e,n),Vn(e,n,w,s),n.child;case 6:return e===null&&rc(n),null;case 13:return Nh(e,n,s);case 4:return fc(n,n.stateNode.containerInfo),a=n.pendingProps,e===null?n.child=ul(n,null,a,s):Vn(e,n,a,s),n.child;case 11:return a=n.type,f=n.pendingProps,f=n.elementType===a?f:Br(a,f),wh(e,n,a,f,s);case 7:return Vn(e,n,n.pendingProps,s),n.child;case 8:return Vn(e,n,n.pendingProps.children,s),n.child;case 12:return Vn(e,n,n.pendingProps.children,s),n.child;case 10:e:{if(a=n.type._context,f=n.pendingProps,h=n.memoizedProps,w=f.value,ht(wa,a._currentValue),a._currentValue=w,h!==null)if(ut(h.value,w)){if(h.children===f.children&&!er.current){n=Ei(e,n,s);break e}}else for(h=n.child,h!==null&&(h.return=n);h!==null;){var k=h.dependencies;if(k!==null){w=h.child;for(var b=k.firstContext;b!==null;){if(b.context===a){if(h.tag===1){b=_i(-1,s&-s),b.tag=2;var B=h.updateQueue;if(B!==null){B=B.shared;var re=B.pending;re===null?b.next=b:(b.next=re.next,re.next=b),B.pending=b}}h.lanes|=s,b=h.alternate,b!==null&&(b.lanes|=s),ac(h.return,s,n),k.lanes|=s;break}b=b.next}}else if(h.tag===10)w=h.type===n.type?null:h.child;else if(h.tag===18){if(w=h.return,w===null)throw Error(r(341));w.lanes|=s,k=w.alternate,k!==null&&(k.lanes|=s),ac(w,s,n),w=h.sibling}else w=h.child;if(w!==null)w.return=h;else for(w=h;w!==null;){if(w===n){w=null;break}if(h=w.sibling,h!==null){h.return=w.return,w=h;break}w=w.return}h=w}Vn(e,n,f.children,s),n=n.child}return n;case 9:return f=n.type,a=n.pendingProps.children,fl(n,s),f=Dr(f),a=a(f),n.flags|=1,Vn(e,n,a,s),n.child;case 14:return a=n.type,f=Br(a,n.pendingProps),f=Br(a.type,f),Sh(e,n,a,f,s);case 15:return xh(e,n,n.type,n.pendingProps,s);case 17:return a=n.type,f=n.pendingProps,f=n.elementType===a?f:Br(a,f),Da(e,n),n.tag=1,tr(a)?(e=!0,ha(n)):e=!1,fl(n,s),dh(n,a,f),Cc(n,a,f,s),bc(null,n,a,!0,e,s);case 19:return Th(e,n,s);case 22:return _h(e,n,s)}throw Error(r(156,n.tag))};function Zh(e,n){return zt(e,n)}function zv(e,n,s,a){this.tag=e,this.key=s,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=n,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=a,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Lr(e,n,s,a){return new zv(e,n,s,a)}function Yc(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Ov(e){if(typeof e=="function")return Yc(e)?1:0;if(e!=null){if(e=e.$$typeof,e===xe)return 11;if(e===ye)return 14}return 2}function ns(e,n){var s=e.alternate;return s===null?(s=Lr(e.tag,n,e.key,e.mode),s.elementType=e.elementType,s.type=e.type,s.stateNode=e.stateNode,s.alternate=e,e.alternate=s):(s.pendingProps=n,s.type=e.type,s.flags=0,s.subtreeFlags=0,s.deletions=null),s.flags=e.flags&14680064,s.childLanes=e.childLanes,s.lanes=e.lanes,s.child=e.child,s.memoizedProps=e.memoizedProps,s.memoizedState=e.memoizedState,s.updateQueue=e.updateQueue,n=e.dependencies,s.dependencies=n===null?null:{lanes:n.lanes,firstContext:n.firstContext},s.sibling=e.sibling,s.index=e.index,s.ref=e.ref,s}function Ba(e,n,s,a,f,h){var w=2;if(a=e,typeof e=="function")Yc(e)&&(w=1);else if(typeof e=="string")w=5;else e:switch(e){case Y:return Ds(s.children,f,h,n);case Z:w=8,f|=8;break;case K:return e=Lr(12,s,n,f|2),e.elementType=K,e.lanes=h,e;case ce:return e=Lr(13,s,n,f),e.elementType=ce,e.lanes=h,e;case ge:return e=Lr(19,s,n,f),e.elementType=ge,e.lanes=h,e;case le:return Ua(s,f,h,n);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case fe:w=10;break e;case q:w=9;break e;case xe:w=11;break e;case ye:w=14;break e;case ke:w=16,a=null;break e}throw Error(r(130,e==null?e:typeof e,""))}return n=Lr(w,s,n,f),n.elementType=e,n.type=a,n.lanes=h,n}function Ds(e,n,s,a){return e=Lr(7,e,a,n),e.lanes=s,e}function Ua(e,n,s,a){return e=Lr(22,e,a,n),e.elementType=le,e.lanes=s,e.stateNode={isHidden:!1},e}function Qc(e,n,s){return e=Lr(6,e,null,n),e.lanes=s,e}function qc(e,n,s){return n=Lr(4,e.children!==null?e.children:[],e.key,n),n.lanes=s,n.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},n}function Lv(e,n,s,a,f){this.tag=n,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fl(0),this.expirationTimes=Fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fl(0),this.identifierPrefix=a,this.onRecoverableError=f,this.mutableSourceEagerHydrationData=null}function Xc(e,n,s,a,f,h,w,k,b){return e=new Lv(e,n,s,k,b),n===1?(n=1,h===!0&&(n|=8)):n=0,h=Lr(3,null,null,n),e.current=h,h.stateNode=e,h.memoizedState={element:a,isDehydrated:s,cache:null,transitions:null,pendingSuspenseBoundaries:null},cc(h),e}function Pv(e,n,s){var a=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(t){console.error(t)}}return l(),rf.exports=Yv(),rf.exports}var hp;function Qv(){if(hp)return qa;hp=1;var l=Dm();return qa.createRoot=l.createRoot,qa.hydrateRoot=l.hydrateRoot,qa}var qv=Qv();const Xv=Mm(qv);var Ps=Dm();const xu=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Ml(l){const t=Object.prototype.toString.call(l);return t==="[object Window]"||t==="[object global]"}function Wf(l){return"nodeType"in l}function Yn(l){var t,r;return l?Ml(l)?l:Wf(l)&&(t=(r=l.ownerDocument)==null?void 0:r.defaultView)!=null?t:window:window}function Bf(l){const{Document:t}=Yn(l);return l instanceof t}function Po(l){return Ml(l)?!1:l instanceof Yn(l).HTMLElement}function zm(l){return l instanceof Yn(l).SVGElement}function Dl(l){return l?Ml(l)?l.document:Wf(l)?Bf(l)?l:Po(l)||zm(l)?l.ownerDocument:document:document:document}const Ni=xu?P.useLayoutEffect:P.useEffect;function _u(l){const t=P.useRef(l);return Ni(()=>{t.current=l}),P.useCallback(function(){for(var r=arguments.length,i=new Array(r),o=0;o{l.current=setInterval(i,o)},[]),r=P.useCallback(()=>{l.current!==null&&(clearInterval(l.current),l.current=null)},[]);return[t,r]}function To(l,t){t===void 0&&(t=[l]);const r=P.useRef(l);return Ni(()=>{r.current!==l&&(r.current=l)},t),r}function Ao(l,t){const r=P.useRef();return P.useMemo(()=>{const i=l(r.current);return r.current=i,i},[...t])}function lu(l){const t=_u(l),r=P.useRef(null),i=P.useCallback(o=>{o!==r.current&&(t==null||t(o,r.current)),r.current=o},[]);return[r,i]}function ou(l){const t=P.useRef();return P.useEffect(()=>{t.current=l},[l]),t.current}let of={};function Eu(l,t){return P.useMemo(()=>{if(t)return t;const r=of[l]==null?0:of[l]+1;return of[l]=r,l+"-"+r},[l,t])}function Om(l){return function(t){for(var r=arguments.length,i=new Array(r>1?r-1:0),o=1;o{const d=Object.entries(c);for(const[p,g]of d){const y=u[p];y!=null&&(u[p]=y+l*g)}return u},{...t})}}const _l=Om(1),au=Om(-1);function Zv(l){return"clientX"in l&&"clientY"in l}function Uf(l){if(!l)return!1;const{KeyboardEvent:t}=Yn(l.target);return t&&l instanceof t}function ey(l){if(!l)return!1;const{TouchEvent:t}=Yn(l.target);return t&&l instanceof t}function uu(l){if(ey(l)){if(l.touches&&l.touches.length){const{clientX:t,clientY:r}=l.touches[0];return{x:t,y:r}}else if(l.changedTouches&&l.changedTouches.length){const{clientX:t,clientY:r}=l.changedTouches[0];return{x:t,y:r}}}return Zv(l)?{x:l.clientX,y:l.clientY}:null}const Mo=Object.freeze({Translate:{toString(l){if(!l)return;const{x:t,y:r}=l;return"translate3d("+(t?Math.round(t):0)+"px, "+(r?Math.round(r):0)+"px, 0)"}},Scale:{toString(l){if(!l)return;const{scaleX:t,scaleY:r}=l;return"scaleX("+t+") scaleY("+r+")"}},Transform:{toString(l){if(l)return[Mo.Translate.toString(l),Mo.Scale.toString(l)].join(" ")}},Transition:{toString(l){let{property:t,duration:r,easing:i}=l;return t+" "+r+"ms "+i}}}),pp="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function ty(l){return l.matches(pp)?l:l.querySelector(pp)}const ny={display:"none"};function ry(l){let{id:t,value:r}=l;return pt.createElement("div",{id:t,style:ny},r)}function iy(l){let{id:t,announcement:r,ariaLiveType:i="assertive"}=l;const o={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return pt.createElement("div",{id:t,style:o,role:"status","aria-live":i,"aria-atomic":!0},r)}function sy(){const[l,t]=P.useState("");return{announce:P.useCallback(i=>{i!=null&&t(i)},[]),announcement:l}}const Lm=P.createContext(null);function ly(l){const t=P.useContext(Lm);P.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(l)},[l,t])}function oy(){const[l]=P.useState(()=>new Set),t=P.useCallback(i=>(l.add(i),()=>l.delete(i)),[l]);return[P.useCallback(i=>{let{type:o,event:u}=i;l.forEach(c=>{var d;return(d=c[o])==null?void 0:d.call(c,u)})},[l]),t]}const ay={draggable:` + To pick up a draggable item, press the space bar. + While dragging, use the arrow keys to move the item. + Press space again to drop the item in its new position, or press escape to cancel. + `},uy={onDragStart(l){let{active:t}=l;return"Picked up draggable item "+t.id+"."},onDragOver(l){let{active:t,over:r}=l;return r?"Draggable item "+t.id+" was moved over droppable area "+r.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(l){let{active:t,over:r}=l;return r?"Draggable item "+t.id+" was dropped over droppable area "+r.id:"Draggable item "+t.id+" was dropped."},onDragCancel(l){let{active:t}=l;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function cy(l){let{announcements:t=uy,container:r,hiddenTextDescribedById:i,screenReaderInstructions:o=ay}=l;const{announce:u,announcement:c}=sy(),d=Eu("DndLiveRegion"),[p,g]=P.useState(!1);if(P.useEffect(()=>{g(!0)},[]),ly(P.useMemo(()=>({onDragStart(v){let{active:x}=v;u(t.onDragStart({active:x}))},onDragMove(v){let{active:x,over:T}=v;t.onDragMove&&u(t.onDragMove({active:x,over:T}))},onDragOver(v){let{active:x,over:T}=v;u(t.onDragOver({active:x,over:T}))},onDragEnd(v){let{active:x,over:T}=v;u(t.onDragEnd({active:x,over:T}))},onDragCancel(v){let{active:x,over:T}=v;u(t.onDragCancel({active:x,over:T}))}}),[u,t])),!p)return null;const y=pt.createElement(pt.Fragment,null,pt.createElement(ry,{id:i,value:o.draggable}),pt.createElement(iy,{id:d,announcement:c}));return r?Ps.createPortal(y,r):y}var tn;(function(l){l.DragStart="dragStart",l.DragMove="dragMove",l.DragEnd="dragEnd",l.DragCancel="dragCancel",l.DragOver="dragOver",l.RegisterDroppable="registerDroppable",l.SetDroppableDisabled="setDroppableDisabled",l.UnregisterDroppable="unregisterDroppable"})(tn||(tn={}));function cu(){}function fy(l,t){return P.useMemo(()=>({sensor:l,options:t??{}}),[l,t])}function dy(){for(var l=arguments.length,t=new Array(l),r=0;r[...t].filter(i=>i!=null),[...t])}const qr=Object.freeze({x:0,y:0});function hy(l,t){const r=uu(l);if(!r)return"0 0";const i={x:(r.x-t.left)/t.width*100,y:(r.y-t.top)/t.height*100};return i.x+"% "+i.y+"%"}function py(l,t){let{data:{value:r}}=l,{data:{value:i}}=t;return i-r}function my(l,t){if(!l||l.length===0)return null;const[r]=l;return r[t]}function gy(l,t){const r=Math.max(t.top,l.top),i=Math.max(t.left,l.left),o=Math.min(t.left+t.width,l.left+l.width),u=Math.min(t.top+t.height,l.top+l.height),c=o-i,d=u-r;if(i{let{collisionRect:t,droppableRects:r,droppableContainers:i}=l;const o=[];for(const u of i){const{id:c}=u,d=r.get(c);if(d){const p=gy(d,t);p>0&&o.push({id:c,data:{droppableContainer:u,value:p}})}}return o.sort(py)};function yy(l,t,r){return{...l,scaleX:t&&r?t.width/r.width:1,scaleY:t&&r?t.height/r.height:1}}function Pm(l,t){return l&&t?{x:l.left-t.left,y:l.top-t.top}:qr}function wy(l){return function(r){for(var i=arguments.length,o=new Array(i>1?i-1:0),u=1;u({...c,top:c.top+l*d.y,bottom:c.bottom+l*d.y,left:c.left+l*d.x,right:c.right+l*d.x}),{...r})}}const Sy=wy(1);function Am(l){if(l.startsWith("matrix3d(")){const t=l.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(l.startsWith("matrix(")){const t=l.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function xy(l,t,r){const i=Am(t);if(!i)return l;const{scaleX:o,scaleY:u,x:c,y:d}=i,p=l.left-c-(1-o)*parseFloat(r),g=l.top-d-(1-u)*parseFloat(r.slice(r.indexOf(" ")+1)),y=o?l.width/o:l.width,v=u?l.height/u:l.height;return{width:y,height:v,top:g,right:p+y,bottom:g+v,left:p}}const _y={ignoreTransform:!1};function jo(l,t){t===void 0&&(t=_y);let r=l.getBoundingClientRect();if(t.ignoreTransform){const{transform:g,transformOrigin:y}=Yn(l).getComputedStyle(l);g&&(r=xy(r,g,y))}const{top:i,left:o,width:u,height:c,bottom:d,right:p}=r;return{top:i,left:o,width:u,height:c,bottom:d,right:p}}function mp(l){return jo(l,{ignoreTransform:!0})}function Ey(l){const t=l.innerWidth,r=l.innerHeight;return{top:0,left:0,right:t,bottom:r,width:t,height:r}}function Cy(l,t){return t===void 0&&(t=Yn(l).getComputedStyle(l)),t.position==="fixed"}function ky(l,t){t===void 0&&(t=Yn(l).getComputedStyle(l));const r=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(o=>{const u=t[o];return typeof u=="string"?r.test(u):!1})}function Vf(l,t){const r=[];function i(o){if(t!=null&&r.length>=t||!o)return r;if(Bf(o)&&o.scrollingElement!=null&&!r.includes(o.scrollingElement))return r.push(o.scrollingElement),r;if(!Po(o)||zm(o)||r.includes(o))return r;const u=Yn(l).getComputedStyle(o);return o!==l&&ky(o,u)&&r.push(o),Cy(o,u)?r:i(o.parentNode)}return l?i(l):r}function jm(l){const[t]=Vf(l,1);return t??null}function af(l){return!xu||!l?null:Ml(l)?l:Wf(l)?Bf(l)||l===Dl(l).scrollingElement?window:Po(l)?l:null:null}function Im(l){return Ml(l)?l.scrollX:l.scrollLeft}function Fm(l){return Ml(l)?l.scrollY:l.scrollTop}function kf(l){return{x:Im(l),y:Fm(l)}}var mn;(function(l){l[l.Forward=1]="Forward",l[l.Backward=-1]="Backward"})(mn||(mn={}));function Hm(l){return!xu||!l?!1:l===document.scrollingElement}function Wm(l){const t={x:0,y:0},r=Hm(l)?{height:window.innerHeight,width:window.innerWidth}:{height:l.clientHeight,width:l.clientWidth},i={x:l.scrollWidth-r.width,y:l.scrollHeight-r.height},o=l.scrollTop<=t.y,u=l.scrollLeft<=t.x,c=l.scrollTop>=i.y,d=l.scrollLeft>=i.x;return{isTop:o,isLeft:u,isBottom:c,isRight:d,maxScroll:i,minScroll:t}}const Ry={x:.2,y:.2};function Ny(l,t,r,i,o){let{top:u,left:c,right:d,bottom:p}=r;i===void 0&&(i=10),o===void 0&&(o=Ry);const{isTop:g,isBottom:y,isLeft:v,isRight:x}=Wm(l),T={x:0,y:0},N={x:0,y:0},C={height:t.height*o.y,width:t.width*o.x};return!g&&u<=t.top+C.height?(T.y=mn.Backward,N.y=i*Math.abs((t.top+C.height-u)/C.height)):!y&&p>=t.bottom-C.height&&(T.y=mn.Forward,N.y=i*Math.abs((t.bottom-C.height-p)/C.height)),!x&&d>=t.right-C.width?(T.x=mn.Forward,N.x=i*Math.abs((t.right-C.width-d)/C.width)):!v&&c<=t.left+C.width&&(T.x=mn.Backward,N.x=i*Math.abs((t.left+C.width-c)/C.width)),{direction:T,speed:N}}function by(l){if(l===document.scrollingElement){const{innerWidth:u,innerHeight:c}=window;return{top:0,left:0,right:u,bottom:c,width:u,height:c}}const{top:t,left:r,right:i,bottom:o}=l.getBoundingClientRect();return{top:t,left:r,right:i,bottom:o,width:l.clientWidth,height:l.clientHeight}}function Bm(l){return l.reduce((t,r)=>_l(t,kf(r)),qr)}function Ty(l){return l.reduce((t,r)=>t+Im(r),0)}function My(l){return l.reduce((t,r)=>t+Fm(r),0)}function Um(l,t){if(t===void 0&&(t=jo),!l)return;const{top:r,left:i,bottom:o,right:u}=t(l);jm(l)&&(o<=0||u<=0||r>=window.innerHeight||i>=window.innerWidth)&&l.scrollIntoView({block:"center",inline:"center"})}const Dy=[["x",["left","right"],Ty],["y",["top","bottom"],My]];class $f{constructor(t,r){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const i=Vf(r),o=Bm(i);this.rect={...t},this.width=t.width,this.height=t.height;for(const[u,c,d]of Dy)for(const p of c)Object.defineProperty(this,p,{get:()=>{const g=d(i),y=o[u]-g;return this.rect[p]+y},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Eo{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(r=>{var i;return(i=this.target)==null?void 0:i.removeEventListener(...r)})},this.target=t}add(t,r,i){var o;(o=this.target)==null||o.addEventListener(t,r,i),this.listeners.push([t,r,i])}}function zy(l){const{EventTarget:t}=Yn(l);return l instanceof t?l:Dl(l)}function uf(l,t){const r=Math.abs(l.x),i=Math.abs(l.y);return typeof t=="number"?Math.sqrt(r**2+i**2)>t:"x"in t&&"y"in t?r>t.x&&i>t.y:"x"in t?r>t.x:"y"in t?i>t.y:!1}var Ar;(function(l){l.Click="click",l.DragStart="dragstart",l.Keydown="keydown",l.ContextMenu="contextmenu",l.Resize="resize",l.SelectionChange="selectionchange",l.VisibilityChange="visibilitychange"})(Ar||(Ar={}));function gp(l){l.preventDefault()}function Oy(l){l.stopPropagation()}var ct;(function(l){l.Space="Space",l.Down="ArrowDown",l.Right="ArrowRight",l.Left="ArrowLeft",l.Up="ArrowUp",l.Esc="Escape",l.Enter="Enter",l.Tab="Tab"})(ct||(ct={}));const Vm={start:[ct.Space,ct.Enter],cancel:[ct.Esc],end:[ct.Space,ct.Enter,ct.Tab]},Ly=(l,t)=>{let{currentCoordinates:r}=t;switch(l.code){case ct.Right:return{...r,x:r.x+25};case ct.Left:return{...r,x:r.x-25};case ct.Down:return{...r,y:r.y+25};case ct.Up:return{...r,y:r.y-25}}};class $m{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:r}}=t;this.props=t,this.listeners=new Eo(Dl(r)),this.windowListeners=new Eo(Yn(r)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(Ar.Resize,this.handleCancel),this.windowListeners.add(Ar.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(Ar.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:r}=this.props,i=t.node.current;i&&Um(i),r(qr)}handleKeyDown(t){if(Uf(t)){const{active:r,context:i,options:o}=this.props,{keyboardCodes:u=Vm,coordinateGetter:c=Ly,scrollBehavior:d="smooth"}=o,{code:p}=t;if(u.end.includes(p)){this.handleEnd(t);return}if(u.cancel.includes(p)){this.handleCancel(t);return}const{collisionRect:g}=i.current,y=g?{x:g.left,y:g.top}:qr;this.referenceCoordinates||(this.referenceCoordinates=y);const v=c(t,{active:r,context:i.current,currentCoordinates:y});if(v){const x=au(v,y),T={x:0,y:0},{scrollableAncestors:N}=i.current;for(const C of N){const L=t.code,{isTop:U,isRight:A,isLeft:V,isBottom:z,maxScroll:$,minScroll:G}=Wm(C),Y=by(C),Z={x:Math.min(L===ct.Right?Y.right-Y.width/2:Y.right,Math.max(L===ct.Right?Y.left:Y.left+Y.width/2,v.x)),y:Math.min(L===ct.Down?Y.bottom-Y.height/2:Y.bottom,Math.max(L===ct.Down?Y.top:Y.top+Y.height/2,v.y))},K=L===ct.Right&&!A||L===ct.Left&&!V,fe=L===ct.Down&&!z||L===ct.Up&&!U;if(K&&Z.x!==v.x){const q=C.scrollLeft+x.x,xe=L===ct.Right&&q<=$.x||L===ct.Left&&q>=G.x;if(xe&&!x.y){C.scrollTo({left:q,behavior:d});return}xe?T.x=C.scrollLeft-q:T.x=L===ct.Right?C.scrollLeft-$.x:C.scrollLeft-G.x,T.x&&C.scrollBy({left:-T.x,behavior:d});break}else if(fe&&Z.y!==v.y){const q=C.scrollTop+x.y,xe=L===ct.Down&&q<=$.y||L===ct.Up&&q>=G.y;if(xe&&!x.x){C.scrollTo({top:q,behavior:d});return}xe?T.y=C.scrollTop-q:T.y=L===ct.Down?C.scrollTop-$.y:C.scrollTop-G.y,T.y&&C.scrollBy({top:-T.y,behavior:d});break}}this.handleMove(t,_l(au(v,this.referenceCoordinates),T))}}}handleMove(t,r){const{onMove:i}=this.props;t.preventDefault(),i(r)}handleEnd(t){const{onEnd:r}=this.props;t.preventDefault(),this.detach(),r()}handleCancel(t){const{onCancel:r}=this.props;t.preventDefault(),this.detach(),r()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}$m.activators=[{eventName:"onKeyDown",handler:(l,t,r)=>{let{keyboardCodes:i=Vm,onActivation:o}=t,{active:u}=r;const{code:c}=l.nativeEvent;if(i.start.includes(c)){const d=u.activatorNode.current;return d&&l.target!==d?!1:(l.preventDefault(),o==null||o({event:l.nativeEvent}),!0)}return!1}}];function vp(l){return!!(l&&"distance"in l)}function yp(l){return!!(l&&"delay"in l)}class Gf{constructor(t,r,i){var o;i===void 0&&(i=zy(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=r;const{event:u}=t,{target:c}=u;this.props=t,this.events=r,this.document=Dl(c),this.documentListeners=new Eo(this.document),this.listeners=new Eo(i),this.windowListeners=new Eo(Yn(c)),this.initialCoordinates=(o=uu(u))!=null?o:qr,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:r,bypassActivationConstraint:i}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(Ar.Resize,this.handleCancel),this.windowListeners.add(Ar.DragStart,gp),this.windowListeners.add(Ar.VisibilityChange,this.handleCancel),this.windowListeners.add(Ar.ContextMenu,gp),this.documentListeners.add(Ar.Keydown,this.handleKeydown),r){if(i!=null&&i({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(yp(r)){this.timeoutId=setTimeout(this.handleStart,r.delay),this.handlePending(r);return}if(vp(r)){this.handlePending(r);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,r){const{active:i,onPending:o}=this.props;o(i,t,this.initialCoordinates,r)}handleStart(){const{initialCoordinates:t}=this,{onStart:r}=this.props;t&&(this.activated=!0,this.documentListeners.add(Ar.Click,Oy,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(Ar.SelectionChange,this.removeTextSelection),r(t))}handleMove(t){var r;const{activated:i,initialCoordinates:o,props:u}=this,{onMove:c,options:{activationConstraint:d}}=u;if(!o)return;const p=(r=uu(t))!=null?r:qr,g=au(o,p);if(!i&&d){if(vp(d)){if(d.tolerance!=null&&uf(g,d.tolerance))return this.handleCancel();if(uf(g,d.distance))return this.handleStart()}if(yp(d)&&uf(g,d.tolerance))return this.handleCancel();this.handlePending(d,g);return}t.cancelable&&t.preventDefault(),c(p)}handleEnd(){const{onAbort:t,onEnd:r}=this.props;this.detach(),this.activated||t(this.props.active),r()}handleCancel(){const{onAbort:t,onCancel:r}=this.props;this.detach(),this.activated||t(this.props.active),r()}handleKeydown(t){t.code===ct.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const Py={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Kf extends Gf{constructor(t){const{event:r}=t,i=Dl(r.target);super(t,Py,i)}}Kf.activators=[{eventName:"onPointerDown",handler:(l,t)=>{let{nativeEvent:r}=l,{onActivation:i}=t;return!r.isPrimary||r.button!==0?!1:(i==null||i({event:r}),!0)}}];const Ay={move:{name:"mousemove"},end:{name:"mouseup"}};var Rf;(function(l){l[l.RightClick=2]="RightClick"})(Rf||(Rf={}));class jy extends Gf{constructor(t){super(t,Ay,Dl(t.event.target))}}jy.activators=[{eventName:"onMouseDown",handler:(l,t)=>{let{nativeEvent:r}=l,{onActivation:i}=t;return r.button===Rf.RightClick?!1:(i==null||i({event:r}),!0)}}];const cf={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class Iy extends Gf{constructor(t){super(t,cf)}static setup(){return window.addEventListener(cf.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(cf.move.name,t)};function t(){}}}Iy.activators=[{eventName:"onTouchStart",handler:(l,t)=>{let{nativeEvent:r}=l,{onActivation:i}=t;const{touches:o}=r;return o.length>1?!1:(i==null||i({event:r}),!0)}}];var Co;(function(l){l[l.Pointer=0]="Pointer",l[l.DraggableRect=1]="DraggableRect"})(Co||(Co={}));var fu;(function(l){l[l.TreeOrder=0]="TreeOrder",l[l.ReversedTreeOrder=1]="ReversedTreeOrder"})(fu||(fu={}));function Fy(l){let{acceleration:t,activator:r=Co.Pointer,canScroll:i,draggingRect:o,enabled:u,interval:c=5,order:d=fu.TreeOrder,pointerCoordinates:p,scrollableAncestors:g,scrollableAncestorRects:y,delta:v,threshold:x}=l;const T=Wy({delta:v,disabled:!u}),[N,C]=Jv(),L=P.useRef({x:0,y:0}),U=P.useRef({x:0,y:0}),A=P.useMemo(()=>{switch(r){case Co.Pointer:return p?{top:p.y,bottom:p.y,left:p.x,right:p.x}:null;case Co.DraggableRect:return o}},[r,o,p]),V=P.useRef(null),z=P.useCallback(()=>{const G=V.current;if(!G)return;const Y=L.current.x*U.current.x,Z=L.current.y*U.current.y;G.scrollBy(Y,Z)},[]),$=P.useMemo(()=>d===fu.TreeOrder?[...g].reverse():g,[d,g]);P.useEffect(()=>{if(!u||!g.length||!A){C();return}for(const G of $){if((i==null?void 0:i(G))===!1)continue;const Y=g.indexOf(G),Z=y[Y];if(!Z)continue;const{direction:K,speed:fe}=Ny(G,Z,A,t,x);for(const q of["x","y"])T[q][K[q]]||(fe[q]=0,K[q]=0);if(fe.x>0||fe.y>0){C(),V.current=G,N(z,c),L.current=fe,U.current=K;return}}L.current={x:0,y:0},U.current={x:0,y:0},C()},[t,z,i,C,u,c,JSON.stringify(A),JSON.stringify(T),N,g,$,y,JSON.stringify(x)])}const Hy={x:{[mn.Backward]:!1,[mn.Forward]:!1},y:{[mn.Backward]:!1,[mn.Forward]:!1}};function Wy(l){let{delta:t,disabled:r}=l;const i=ou(t);return Ao(o=>{if(r||!i||!o)return Hy;const u={x:Math.sign(t.x-i.x),y:Math.sign(t.y-i.y)};return{x:{[mn.Backward]:o.x[mn.Backward]||u.x===-1,[mn.Forward]:o.x[mn.Forward]||u.x===1},y:{[mn.Backward]:o.y[mn.Backward]||u.y===-1,[mn.Forward]:o.y[mn.Forward]||u.y===1}}},[r,t,i])}function By(l,t){const r=t!=null?l.get(t):void 0,i=r?r.node.current:null;return Ao(o=>{var u;return t==null?null:(u=i??o)!=null?u:null},[i,t])}function Uy(l,t){return P.useMemo(()=>l.reduce((r,i)=>{const{sensor:o}=i,u=o.activators.map(c=>({eventName:c.eventName,handler:t(c.handler,i)}));return[...r,...u]},[]),[l,t])}var Do;(function(l){l[l.Always=0]="Always",l[l.BeforeDragging=1]="BeforeDragging",l[l.WhileDragging=2]="WhileDragging"})(Do||(Do={}));var Nf;(function(l){l.Optimized="optimized"})(Nf||(Nf={}));const wp=new Map;function Vy(l,t){let{dragging:r,dependencies:i,config:o}=t;const[u,c]=P.useState(null),{frequency:d,measure:p,strategy:g}=o,y=P.useRef(l),v=L(),x=To(v),T=P.useCallback(function(U){U===void 0&&(U=[]),!x.current&&c(A=>A===null?U:A.concat(U.filter(V=>!A.includes(V))))},[x]),N=P.useRef(null),C=Ao(U=>{if(v&&!r)return wp;if(!U||U===wp||y.current!==l||u!=null){const A=new Map;for(let V of l){if(!V)continue;if(u&&u.length>0&&!u.includes(V.id)&&V.rect.current){A.set(V.id,V.rect.current);continue}const z=V.node.current,$=z?new $f(p(z),z):null;V.rect.current=$,$&&A.set(V.id,$)}return A}return U},[l,u,r,v,p]);return P.useEffect(()=>{y.current=l},[l]),P.useEffect(()=>{v||T()},[r,v]),P.useEffect(()=>{u&&u.length>0&&c(null)},[JSON.stringify(u)]),P.useEffect(()=>{v||typeof d!="number"||N.current!==null||(N.current=setTimeout(()=>{T(),N.current=null},d))},[d,v,T,...i]),{droppableRects:C,measureDroppableContainers:T,measuringScheduled:u!=null};function L(){switch(g){case Do.Always:return!1;case Do.BeforeDragging:return r;default:return!r}}}function Yf(l,t){return Ao(r=>l?r||(typeof t=="function"?t(l):l):null,[t,l])}function $y(l,t){return Yf(l,t)}function Gy(l){let{callback:t,disabled:r}=l;const i=_u(t),o=P.useMemo(()=>{if(r||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:u}=window;return new u(i)},[i,r]);return P.useEffect(()=>()=>o==null?void 0:o.disconnect(),[o]),o}function Cu(l){let{callback:t,disabled:r}=l;const i=_u(t),o=P.useMemo(()=>{if(r||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:u}=window;return new u(i)},[r]);return P.useEffect(()=>()=>o==null?void 0:o.disconnect(),[o]),o}function Ky(l){return new $f(jo(l),l)}function Sp(l,t,r){t===void 0&&(t=Ky);const[i,o]=P.useState(null);function u(){o(p=>{if(!l)return null;if(l.isConnected===!1){var g;return(g=p??r)!=null?g:null}const y=t(l);return JSON.stringify(p)===JSON.stringify(y)?p:y})}const c=Gy({callback(p){if(l)for(const g of p){const{type:y,target:v}=g;if(y==="childList"&&v instanceof HTMLElement&&v.contains(l)){u();break}}}}),d=Cu({callback:u});return Ni(()=>{u(),l?(d==null||d.observe(l),c==null||c.observe(document.body,{childList:!0,subtree:!0})):(d==null||d.disconnect(),c==null||c.disconnect())},[l]),i}function Yy(l){const t=Yf(l);return Pm(l,t)}const xp=[];function Qy(l){const t=P.useRef(l),r=Ao(i=>l?i&&i!==xp&&l&&t.current&&l.parentNode===t.current.parentNode?i:Vf(l):xp,[l]);return P.useEffect(()=>{t.current=l},[l]),r}function qy(l){const[t,r]=P.useState(null),i=P.useRef(l),o=P.useCallback(u=>{const c=af(u.target);c&&r(d=>d?(d.set(c,kf(c)),new Map(d)):null)},[]);return P.useEffect(()=>{const u=i.current;if(l!==u){c(u);const d=l.map(p=>{const g=af(p);return g?(g.addEventListener("scroll",o,{passive:!0}),[g,kf(g)]):null}).filter(p=>p!=null);r(d.length?new Map(d):null),i.current=l}return()=>{c(l),c(u)};function c(d){d.forEach(p=>{const g=af(p);g==null||g.removeEventListener("scroll",o)})}},[o,l]),P.useMemo(()=>l.length?t?Array.from(t.values()).reduce((u,c)=>_l(u,c),qr):Bm(l):qr,[l,t])}function _p(l,t){t===void 0&&(t=[]);const r=P.useRef(null);return P.useEffect(()=>{r.current=null},t),P.useEffect(()=>{const i=l!==qr;i&&!r.current&&(r.current=l),!i&&r.current&&(r.current=null)},[l]),r.current?au(l,r.current):qr}function Xy(l){P.useEffect(()=>{if(!xu)return;const t=l.map(r=>{let{sensor:i}=r;return i.setup==null?void 0:i.setup()});return()=>{for(const r of t)r==null||r()}},l.map(t=>{let{sensor:r}=t;return r}))}function Jy(l,t){return P.useMemo(()=>l.reduce((r,i)=>{let{eventName:o,handler:u}=i;return r[o]=c=>{u(c,t)},r},{}),[l,t])}function Gm(l){return P.useMemo(()=>l?Ey(l):null,[l])}const Ep=[];function Zy(l,t){t===void 0&&(t=jo);const[r]=l,i=Gm(r?Yn(r):null),[o,u]=P.useState(Ep);function c(){u(()=>l.length?l.map(p=>Hm(p)?i:new $f(t(p),p)):Ep)}const d=Cu({callback:c});return Ni(()=>{d==null||d.disconnect(),c(),l.forEach(p=>d==null?void 0:d.observe(p))},[l]),o}function Km(l){if(!l)return null;if(l.children.length>1)return l;const t=l.children[0];return Po(t)?t:l}function e0(l){let{measure:t}=l;const[r,i]=P.useState(null),o=P.useCallback(g=>{for(const{target:y}of g)if(Po(y)){i(v=>{const x=t(y);return v?{...v,width:x.width,height:x.height}:x});break}},[t]),u=Cu({callback:o}),c=P.useCallback(g=>{const y=Km(g);u==null||u.disconnect(),y&&(u==null||u.observe(y)),i(y?t(y):null)},[t,u]),[d,p]=lu(c);return P.useMemo(()=>({nodeRef:d,rect:r,setRef:p}),[r,d,p])}const t0=[{sensor:Kf,options:{}},{sensor:$m,options:{}}],n0={current:{}},eu={draggable:{measure:mp},droppable:{measure:mp,strategy:Do.WhileDragging,frequency:Nf.Optimized},dragOverlay:{measure:jo}};class ko extends Map{get(t){var r;return t!=null&&(r=super.get(t))!=null?r:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:r}=t;return!r})}getNodeFor(t){var r,i;return(r=(i=this.get(t))==null?void 0:i.node.current)!=null?r:void 0}}const r0={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new ko,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:cu},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:eu,measureDroppableContainers:cu,windowRect:null,measuringScheduled:!1},Ym={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:cu,draggableNodes:new Map,over:null,measureDroppableContainers:cu},Io=P.createContext(Ym),Qm=P.createContext(r0);function i0(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new ko}}}function s0(l,t){switch(t.type){case tn.DragStart:return{...l,draggable:{...l.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case tn.DragMove:return l.draggable.active==null?l:{...l,draggable:{...l.draggable,translate:{x:t.coordinates.x-l.draggable.initialCoordinates.x,y:t.coordinates.y-l.draggable.initialCoordinates.y}}};case tn.DragEnd:case tn.DragCancel:return{...l,draggable:{...l.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case tn.RegisterDroppable:{const{element:r}=t,{id:i}=r,o=new ko(l.droppable.containers);return o.set(i,r),{...l,droppable:{...l.droppable,containers:o}}}case tn.SetDroppableDisabled:{const{id:r,key:i,disabled:o}=t,u=l.droppable.containers.get(r);if(!u||i!==u.key)return l;const c=new ko(l.droppable.containers);return c.set(r,{...u,disabled:o}),{...l,droppable:{...l.droppable,containers:c}}}case tn.UnregisterDroppable:{const{id:r,key:i}=t,o=l.droppable.containers.get(r);if(!o||i!==o.key)return l;const u=new ko(l.droppable.containers);return u.delete(r),{...l,droppable:{...l.droppable,containers:u}}}default:return l}}function l0(l){let{disabled:t}=l;const{active:r,activatorEvent:i,draggableNodes:o}=P.useContext(Io),u=ou(i),c=ou(r==null?void 0:r.id);return P.useEffect(()=>{if(!t&&!i&&u&&c!=null){if(!Uf(u)||document.activeElement===u.target)return;const d=o.get(c);if(!d)return;const{activatorNode:p,node:g}=d;if(!p.current&&!g.current)return;requestAnimationFrame(()=>{for(const y of[p.current,g.current]){if(!y)continue;const v=ty(y);if(v){v.focus();break}}})}},[i,t,o,c,u]),null}function qm(l,t){let{transform:r,...i}=t;return l!=null&&l.length?l.reduce((o,u)=>u({transform:o,...i}),r):r}function o0(l){return P.useMemo(()=>({draggable:{...eu.draggable,...l==null?void 0:l.draggable},droppable:{...eu.droppable,...l==null?void 0:l.droppable},dragOverlay:{...eu.dragOverlay,...l==null?void 0:l.dragOverlay}}),[l==null?void 0:l.draggable,l==null?void 0:l.droppable,l==null?void 0:l.dragOverlay])}function a0(l){let{activeNode:t,measure:r,initialRect:i,config:o=!0}=l;const u=P.useRef(!1),{x:c,y:d}=typeof o=="boolean"?{x:o,y:o}:o;Ni(()=>{if(!c&&!d||!t){u.current=!1;return}if(u.current||!i)return;const g=t==null?void 0:t.node.current;if(!g||g.isConnected===!1)return;const y=r(g),v=Pm(y,i);if(c||(v.x=0),d||(v.y=0),u.current=!0,Math.abs(v.x)>0||Math.abs(v.y)>0){const x=jm(g);x&&x.scrollBy({top:v.y,left:v.x})}},[t,c,d,i,r])}const ku=P.createContext({...qr,scaleX:1,scaleY:1});var ss;(function(l){l[l.Uninitialized=0]="Uninitialized",l[l.Initializing=1]="Initializing",l[l.Initialized=2]="Initialized"})(ss||(ss={}));const u0=P.memo(function(t){var r,i,o,u;let{id:c,accessibility:d,autoScroll:p=!0,children:g,sensors:y=t0,collisionDetection:v=vy,measuring:x,modifiers:T,...N}=t;const C=P.useReducer(s0,void 0,i0),[L,U]=C,[A,V]=oy(),[z,$]=P.useState(ss.Uninitialized),G=z===ss.Initialized,{draggable:{active:Y,nodes:Z,translate:K},droppable:{containers:fe}}=L,q=Y!=null?Z.get(Y):null,xe=P.useRef({initial:null,translated:null}),ce=P.useMemo(()=>{var ot;return Y!=null?{id:Y,data:(ot=q==null?void 0:q.data)!=null?ot:n0,rect:xe}:null},[Y,q]),ge=P.useRef(null),[ye,ke]=P.useState(null),[le,oe]=P.useState(null),ae=To(N,Object.values(N)),J=Eu("DndDescribedBy",c),M=P.useMemo(()=>fe.getEnabled(),[fe]),W=o0(x),{droppableRects:X,measureDroppableContainers:ee,measuringScheduled:be}=Vy(M,{dragging:G,dependencies:[K.x,K.y],config:W.droppable}),he=By(Z,Y),Ee=P.useMemo(()=>le?uu(le):null,[le]),Ie=Dt(),Fe=$y(he,W.draggable.measure);a0({activeNode:Y!=null?Z.get(Y):null,config:Ie.layoutShiftCompensation,initialRect:Fe,measure:W.draggable.measure});const Oe=Sp(he,W.draggable.measure,Fe),Gt=Sp(he?he.parentElement:null),At=P.useRef({activatorEvent:null,active:null,activeNode:he,collisionRect:null,collisions:null,droppableRects:X,draggableNodes:Z,draggingNode:null,draggingNodeRect:null,droppableContainers:fe,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),jt=fe.getNodeFor((r=At.current.over)==null?void 0:r.id),It=e0({measure:W.dragOverlay.measure}),Qn=(i=It.nodeRef.current)!=null?i:he,kn=G?(o=It.rect)!=null?o:Oe:null,Er=!!(It.nodeRef.current&&It.rect),Xr=Yy(Er?null:Oe),An=Gm(Qn?Yn(Qn):null),et=Qy(G?jt??he:null),rn=Zy(et),sn=qm(T,{transform:{x:K.x-Xr.x,y:K.y-Xr.y,scaleX:1,scaleY:1},activatorEvent:le,active:ce,activeNodeRect:Oe,containerNodeRect:Gt,draggingNodeRect:kn,over:At.current.over,overlayNodeRect:It.rect,scrollableAncestors:et,scrollableAncestorRects:rn,windowRect:An}),lr=Ee?_l(Ee,K):null,Pe=qy(et),pe=_p(Pe),Je=_p(Pe,[Oe]),tt=_l(sn,pe),ln=kn?Sy(kn,sn):null,Rn=ce&&ln?v({active:ce,collisionRect:ln,droppableRects:X,droppableContainers:M,pointerCoordinates:lr}):null,Kt=my(Rn,"id"),[Nt,on]=P.useState(null),gn=Er?sn:_l(sn,Je),Yt=yy(gn,(u=Nt==null?void 0:Nt.rect)!=null?u:null,Oe),vn=P.useRef(null),Jr=P.useCallback((ot,Qt)=>{let{sensor:an,options:ur}=Qt;if(ge.current==null)return;const yn=Z.get(ge.current);if(!yn)return;const un=ot.nativeEvent,Nn=new an({active:ge.current,activeNode:yn,event:un,options:ur,context:At,onAbort(Be){if(!Z.get(Be))return;const{onDragAbort:Et}=ae.current,cn={id:Be};Et==null||Et(cn),A({type:"onDragAbort",event:cn})},onPending(Be,_t,Et,cn){if(!Z.get(Be))return;const{onDragPending:Sn}=ae.current,Ft={id:Be,constraint:_t,initialCoordinates:Et,offset:cn};Sn==null||Sn(Ft),A({type:"onDragPending",event:Ft})},onStart(Be){const _t=ge.current;if(_t==null)return;const Et=Z.get(_t);if(!Et)return;const{onDragStart:cn}=ae.current,yt={activatorEvent:un,active:{id:_t,data:Et.data,rect:xe}};Ps.unstable_batchedUpdates(()=>{cn==null||cn(yt),$(ss.Initializing),U({type:tn.DragStart,initialCoordinates:Be,active:_t}),A({type:"onDragStart",event:yt}),ke(vn.current),oe(un)})},onMove(Be){U({type:tn.DragMove,coordinates:Be})},onEnd:wn(tn.DragEnd),onCancel:wn(tn.DragCancel)});vn.current=Nn;function wn(Be){return async function(){const{active:Et,collisions:cn,over:yt,scrollAdjustedTranslate:Sn}=At.current;let Ft=null;if(Et&&Sn){const{cancelDrop:Cr}=ae.current;Ft={activatorEvent:un,active:Et,collisions:cn,delta:Sn,over:yt},Be===tn.DragEnd&&typeof Cr=="function"&&await Promise.resolve(Cr(Ft))&&(Be=tn.DragCancel)}ge.current=null,Ps.unstable_batchedUpdates(()=>{U({type:Be}),$(ss.Uninitialized),on(null),ke(null),oe(null),vn.current=null;const Cr=Be===tn.DragEnd?"onDragEnd":"onDragCancel";if(Ft){const bi=ae.current[Cr];bi==null||bi(Ft),A({type:Cr,event:Ft})}})}}},[Z]),Zr=P.useCallback((ot,Qt)=>(an,ur)=>{const yn=an.nativeEvent,un=Z.get(ur);if(ge.current!==null||!un||yn.dndKit||yn.defaultPrevented)return;const Nn={active:un};ot(an,Qt.options,Nn)===!0&&(yn.dndKit={capturedBy:Qt.sensor},ge.current=ur,Jr(an,Qt))},[Z,Jr]),or=Uy(y,Zr);Xy(y),Ni(()=>{Oe&&z===ss.Initializing&&$(ss.Initialized)},[Oe,z]),P.useEffect(()=>{const{onDragMove:ot}=ae.current,{active:Qt,activatorEvent:an,collisions:ur,over:yn}=At.current;if(!Qt||!an)return;const un={active:Qt,activatorEvent:an,collisions:ur,delta:{x:tt.x,y:tt.y},over:yn};Ps.unstable_batchedUpdates(()=>{ot==null||ot(un),A({type:"onDragMove",event:un})})},[tt.x,tt.y]),P.useEffect(()=>{const{active:ot,activatorEvent:Qt,collisions:an,droppableContainers:ur,scrollAdjustedTranslate:yn}=At.current;if(!ot||ge.current==null||!Qt||!yn)return;const{onDragOver:un}=ae.current,Nn=ur.get(Kt),wn=Nn&&Nn.rect.current?{id:Nn.id,rect:Nn.rect.current,data:Nn.data,disabled:Nn.disabled}:null,Be={active:ot,activatorEvent:Qt,collisions:an,delta:{x:yn.x,y:yn.y},over:wn};Ps.unstable_batchedUpdates(()=>{on(wn),un==null||un(Be),A({type:"onDragOver",event:Be})})},[Kt]),Ni(()=>{At.current={activatorEvent:le,active:ce,activeNode:he,collisionRect:ln,collisions:Rn,droppableRects:X,draggableNodes:Z,draggingNode:Qn,draggingNodeRect:kn,droppableContainers:fe,over:Nt,scrollableAncestors:et,scrollAdjustedTranslate:tt},xe.current={initial:kn,translated:ln}},[ce,he,Rn,ln,Z,Qn,kn,X,fe,Nt,et,tt]),Fy({...Ie,delta:K,draggingRect:ln,pointerCoordinates:lr,scrollableAncestors:et,scrollableAncestorRects:rn});const ar=P.useMemo(()=>({active:ce,activeNode:he,activeNodeRect:Oe,activatorEvent:le,collisions:Rn,containerNodeRect:Gt,dragOverlay:It,draggableNodes:Z,droppableContainers:fe,droppableRects:X,over:Nt,measureDroppableContainers:ee,scrollableAncestors:et,scrollableAncestorRects:rn,measuringConfiguration:W,measuringScheduled:be,windowRect:An}),[ce,he,Oe,le,Rn,Gt,It,Z,fe,X,Nt,ee,et,rn,W,be,An]),ei=P.useMemo(()=>({activatorEvent:le,activators:or,active:ce,activeNodeRect:Oe,ariaDescribedById:{draggable:J},dispatch:U,draggableNodes:Z,over:Nt,measureDroppableContainers:ee}),[le,or,ce,Oe,U,J,Z,Nt,ee]);return pt.createElement(Lm.Provider,{value:V},pt.createElement(Io.Provider,{value:ei},pt.createElement(Qm.Provider,{value:ar},pt.createElement(ku.Provider,{value:Yt},g)),pt.createElement(l0,{disabled:(d==null?void 0:d.restoreFocus)===!1})),pt.createElement(cy,{...d,hiddenTextDescribedById:J}));function Dt(){const ot=(ye==null?void 0:ye.autoScrollEnabled)===!1,Qt=typeof p=="object"?p.enabled===!1:p===!1,an=G&&!ot&&!Qt;return typeof p=="object"?{...p,enabled:an}:{enabled:an}}}),c0=P.createContext(null),Cp="button",f0="Draggable";function d0(l){let{id:t,data:r,disabled:i=!1,attributes:o}=l;const u=Eu(f0),{activators:c,activatorEvent:d,active:p,activeNodeRect:g,ariaDescribedById:y,draggableNodes:v,over:x}=P.useContext(Io),{role:T=Cp,roleDescription:N="draggable",tabIndex:C=0}=o??{},L=(p==null?void 0:p.id)===t,U=P.useContext(L?ku:c0),[A,V]=lu(),[z,$]=lu(),G=Jy(c,t),Y=To(r);Ni(()=>(v.set(t,{id:t,key:u,node:A,activatorNode:z,data:Y}),()=>{const K=v.get(t);K&&K.key===u&&v.delete(t)}),[v,t]);const Z=P.useMemo(()=>({role:T,tabIndex:C,"aria-disabled":i,"aria-pressed":L&&T===Cp?!0:void 0,"aria-roledescription":N,"aria-describedby":y.draggable}),[i,T,C,L,N,y.draggable]);return{active:p,activatorEvent:d,activeNodeRect:g,attributes:Z,isDragging:L,listeners:i?void 0:G,node:A,over:x,setNodeRef:V,setActivatorNodeRef:$,transform:U}}function h0(){return P.useContext(Qm)}const p0="Droppable",m0={timeout:25};function g0(l){let{data:t,disabled:r=!1,id:i,resizeObserverConfig:o}=l;const u=Eu(p0),{active:c,dispatch:d,over:p,measureDroppableContainers:g}=P.useContext(Io),y=P.useRef({disabled:r}),v=P.useRef(!1),x=P.useRef(null),T=P.useRef(null),{disabled:N,updateMeasurementsFor:C,timeout:L}={...m0,...o},U=To(C??i),A=P.useCallback(()=>{if(!v.current){v.current=!0;return}T.current!=null&&clearTimeout(T.current),T.current=setTimeout(()=>{g(Array.isArray(U.current)?U.current:[U.current]),T.current=null},L)},[L]),V=Cu({callback:A,disabled:N||!c}),z=P.useCallback((Z,K)=>{V&&(K&&(V.unobserve(K),v.current=!1),Z&&V.observe(Z))},[V]),[$,G]=lu(z),Y=To(t);return P.useEffect(()=>{!V||!$.current||(V.disconnect(),v.current=!1,V.observe($.current))},[$,V]),P.useEffect(()=>(d({type:tn.RegisterDroppable,element:{id:i,key:u,disabled:r,node:$,rect:x,data:Y}}),()=>d({type:tn.UnregisterDroppable,key:u,id:i})),[i]),P.useEffect(()=>{r!==y.current.disabled&&(d({type:tn.SetDroppableDisabled,id:i,key:u,disabled:r}),y.current.disabled=r)},[i,u,r,d]),{active:c,rect:x,isOver:(p==null?void 0:p.id)===i,node:$,over:p,setNodeRef:G}}function v0(l){let{animation:t,children:r}=l;const[i,o]=P.useState(null),[u,c]=P.useState(null),d=ou(r);return!r&&!i&&d&&o(d),Ni(()=>{if(!u)return;const p=i==null?void 0:i.key,g=i==null?void 0:i.props.id;if(p==null||g==null){o(null);return}Promise.resolve(t(g,u)).then(()=>{o(null)})},[t,i,u]),pt.createElement(pt.Fragment,null,r,i?P.cloneElement(i,{ref:c}):null)}const y0={x:0,y:0,scaleX:1,scaleY:1};function w0(l){let{children:t}=l;return pt.createElement(Io.Provider,{value:Ym},pt.createElement(ku.Provider,{value:y0},t))}const S0={position:"fixed",touchAction:"none"},x0=l=>Uf(l)?"transform 250ms ease":void 0,_0=P.forwardRef((l,t)=>{let{as:r,activatorEvent:i,adjustScale:o,children:u,className:c,rect:d,style:p,transform:g,transition:y=x0}=l;if(!d)return null;const v=o?g:{...g,scaleX:1,scaleY:1},x={...S0,width:d.width,height:d.height,top:d.top,left:d.left,transform:Mo.Transform.toString(v),transformOrigin:o&&i?hy(i,d):void 0,transition:typeof y=="function"?y(i):y,...p};return pt.createElement(r,{className:c,style:x,ref:t},u)}),E0=l=>t=>{let{active:r,dragOverlay:i}=t;const o={},{styles:u,className:c}=l;if(u!=null&&u.active)for(const[d,p]of Object.entries(u.active))p!==void 0&&(o[d]=r.node.style.getPropertyValue(d),r.node.style.setProperty(d,p));if(u!=null&&u.dragOverlay)for(const[d,p]of Object.entries(u.dragOverlay))p!==void 0&&i.node.style.setProperty(d,p);return c!=null&&c.active&&r.node.classList.add(c.active),c!=null&&c.dragOverlay&&i.node.classList.add(c.dragOverlay),function(){for(const[p,g]of Object.entries(o))r.node.style.setProperty(p,g);c!=null&&c.active&&r.node.classList.remove(c.active)}},C0=l=>{let{transform:{initial:t,final:r}}=l;return[{transform:Mo.Transform.toString(t)},{transform:Mo.Transform.toString(r)}]},k0={duration:250,easing:"ease",keyframes:C0,sideEffects:E0({styles:{active:{opacity:"0"}}})};function R0(l){let{config:t,draggableNodes:r,droppableContainers:i,measuringConfiguration:o}=l;return _u((u,c)=>{if(t===null)return;const d=r.get(u);if(!d)return;const p=d.node.current;if(!p)return;const g=Km(c);if(!g)return;const{transform:y}=Yn(c).getComputedStyle(c),v=Am(y);if(!v)return;const x=typeof t=="function"?t:N0(t);return Um(p,o.draggable.measure),x({active:{id:u,data:d.data,node:p,rect:o.draggable.measure(p)},draggableNodes:r,dragOverlay:{node:c,rect:o.dragOverlay.measure(g)},droppableContainers:i,measuringConfiguration:o,transform:v})})}function N0(l){const{duration:t,easing:r,sideEffects:i,keyframes:o}={...k0,...l};return u=>{let{active:c,dragOverlay:d,transform:p,...g}=u;if(!t)return;const y={x:d.rect.left-c.rect.left,y:d.rect.top-c.rect.top},v={scaleX:p.scaleX!==1?c.rect.width*p.scaleX/d.rect.width:1,scaleY:p.scaleY!==1?c.rect.height*p.scaleY/d.rect.height:1},x={x:p.x-y.x,y:p.y-y.y,...v},T=o({...g,active:c,dragOverlay:d,transform:{initial:p,final:x}}),[N]=T,C=T[T.length-1];if(JSON.stringify(N)===JSON.stringify(C))return;const L=i==null?void 0:i({active:c,dragOverlay:d,...g}),U=d.node.animate(T,{duration:t,easing:r,fill:"forwards"});return new Promise(A=>{U.onfinish=()=>{L==null||L(),A()}})}}let kp=0;function b0(l){return P.useMemo(()=>{if(l!=null)return kp++,kp},[l])}const T0=pt.memo(l=>{let{adjustScale:t=!1,children:r,dropAnimation:i,style:o,transition:u,modifiers:c,wrapperElement:d="div",className:p,zIndex:g=999}=l;const{activatorEvent:y,active:v,activeNodeRect:x,containerNodeRect:T,draggableNodes:N,droppableContainers:C,dragOverlay:L,over:U,measuringConfiguration:A,scrollableAncestors:V,scrollableAncestorRects:z,windowRect:$}=h0(),G=P.useContext(ku),Y=b0(v==null?void 0:v.id),Z=qm(c,{activatorEvent:y,active:v,activeNodeRect:x,containerNodeRect:T,draggingNodeRect:L.rect,over:U,overlayNodeRect:L.rect,scrollableAncestors:V,scrollableAncestorRects:z,transform:G,windowRect:$}),K=Yf(x),fe=R0({config:i,draggableNodes:N,droppableContainers:C,measuringConfiguration:A}),q=K?L.setRef:void 0;return pt.createElement(w0,null,pt.createElement(v0,{animation:fe},v&&Y?pt.createElement(_0,{key:Y,id:v.id,ref:q,as:d,activatorEvent:y,adjustScale:t,className:p,transition:u,rect:K,style:{zIndex:g,...o},transform:Z},r):null))}),Rp=l=>{let t;const r=new Set,i=(g,y)=>{const v=typeof g=="function"?g(t):g;if(!Object.is(v,t)){const x=t;t=y??(typeof v!="object"||v===null)?v:Object.assign({},t,v),r.forEach(T=>T(t,x))}},o=()=>t,d={setState:i,getState:o,getInitialState:()=>p,subscribe:g=>(r.add(g),()=>r.delete(g))},p=t=l(i,o,d);return d},M0=(l=>l?Rp(l):Rp),D0=l=>l;function z0(l,t=D0){const r=pt.useSyncExternalStore(l.subscribe,pt.useCallback(()=>t(l.getState()),[l,t]),pt.useCallback(()=>t(l.getInitialState()),[l,t]));return pt.useDebugValue(r),r}const Np=l=>{const t=M0(l),r=i=>z0(t,i);return Object.assign(r,t),r},Xm=(l=>l?Np(l):Np),Jm="damiao.monitor.plotConfigs";function O0(){try{return JSON.parse(localStorage.getItem(Jm)||"{}")}catch{return{}}}function L0(l){try{localStorage.setItem(Jm,JSON.stringify(l))}catch{}}const We=Xm((l,t)=>({connected:!1,status:null,signals:[],pairs:[],motors:[],motorTypes:[],plotConfigs:O0(),mode:"monitor",controlMotors:[],currentMotorId:null,registerTable:{},setConnected:r=>l({connected:r}),setStatus:r=>l({status:r}),setMeta:(r,i)=>l({signals:r,pairs:i}),setMotors:r=>l({motors:r}),setMotorTypes:r=>l({motorTypes:r}),setMode:r=>l({mode:r}),setControlMotors:r=>l({controlMotors:r}),setCurrentMotor:r=>l({currentMotorId:r}),setRegisterTable:r=>l({registerTable:r}),ensurePlot:r=>l(i=>i.plotConfigs[r]?i:{plotConfigs:{...i.plotConfigs,[r]:{signals:[],duration:10}}}),setPlotConfig:(r,i)=>l(o=>({plotConfigs:{...o.plotConfigs,[r]:{...o.plotConfigs[r]||{signals:[],duration:10},...i}}})),addSignalToPlot:(r,i)=>l(o=>{const u=o.plotConfigs[r]||{signals:[],duration:10};return u.signals.includes(i)?o:{plotConfigs:{...o.plotConfigs,[r]:{...u,signals:[...u.signals,i]}}}}),removeSignalFromPlot:(r,i)=>l(o=>{const u=o.plotConfigs[r];return u?{plotConfigs:{...o.plotConfigs,[r]:{...u,signals:u.signals.filter(c=>c!==i)}}}:o}),dropPlot:r=>l(i=>{const o={...i.plotConfigs};return delete o[r],{plotConfigs:o}})}));We.subscribe(l=>L0(l.plotConfigs));const Qf="damiao.monitor.widgets.v3";function P0(){try{const l=localStorage.getItem(Qf);if(!l)return null;const t=JSON.parse(l);return Array.isArray(t)&&t.length?t:null}catch{return null}}function ff(l){try{localStorage.setItem(Qf,JSON.stringify(l))}catch{}}const bp=[{id:"connection-1",kind:"connection",x:0,y:0,w:3,h:4},{id:"control-1",kind:"control",x:0,y:4,w:3,h:8},{id:"plot-1",kind:"plot",x:3,y:0,w:6,h:6},{id:"cards-1",kind:"cards",x:9,y:0,w:3,h:6},{id:"table-1",kind:"table",x:3,y:6,w:6,h:6},{id:"registers-1",kind:"registers",x:9,y:6,w:3,h:6}];let Tp=1;const Ro=Xm((l,t)=>({widgets:P0()||bp,addWidget:r=>{Tp+=1;const i=`${r}-${Date.now().toString(36)}-${Tp}`,o=t().widgets.reduce((d,p)=>Math.max(d,p.y+p.h),0),u={id:i,kind:r,x:0,y:o,w:6,h:5},c=[...t().widgets,u];return ff(c),l({widgets:c}),i},removeWidget:r=>{const i=t().widgets.filter(o=>o.id!==r);ff(i),l({widgets:i})},updateGeom:r=>{const i=new Map(r.map(u=>[u.id,u])),o=t().widgets.map(u=>{const c=i.get(u.id);return c?{...u,x:c.x,y:c.y,w:c.w,h:c.h}:u});ff(o),l({widgets:o})},resetWidgets:()=>{try{localStorage.removeItem(Qf),localStorage.removeItem("damiao.monitor.plotConfigs")}catch{}l({widgets:bp.map(r=>({...r}))})}})),A0=!0,nn="u-",j0="uplot",I0=nn+"hz",F0=nn+"vt",H0=nn+"title",W0=nn+"wrap",B0=nn+"under",U0=nn+"over",V0=nn+"axis",Ls=nn+"off",$0=nn+"select",G0=nn+"cursor-x",K0=nn+"cursor-y",Y0=nn+"cursor-pt",Q0=nn+"legend",q0=nn+"live",X0=nn+"inline",J0=nn+"series",Z0=nn+"marker",Mp=nn+"label",ew=nn+"value",So="width",xo="height",vo="top",Dp="bottom",yl="left",df="right",qf="#000",zp=qf+"0",hf="mousemove",Op="mousedown",pf="mouseup",Lp="mouseenter",Pp="mouseleave",Ap="dblclick",tw="resize",nw="scroll",jp="change",du="dppxchange",Xf="--",zl=typeof window<"u",bf=zl?document:null,El=zl?window:null,rw=zl?navigator:null;let Ze,Xa;function Tf(){let l=devicePixelRatio;Ze!=l&&(Ze=l,Xa&&Df(jp,Xa,Tf),Xa=matchMedia(`(min-resolution: ${Ze-.001}dppx) and (max-resolution: ${Ze+.001}dppx)`),As(jp,Xa,Tf),El.dispatchEvent(new CustomEvent(du)))}function Sr(l,t){if(t!=null){let r=l.classList;!r.contains(t)&&r.add(t)}}function Mf(l,t){let r=l.classList;r.contains(t)&&r.remove(t)}function vt(l,t,r){l.style[t]=r+"px"}function Gr(l,t,r,i){let o=bf.createElement(l);return t!=null&&Sr(o,t),r!=null&&r.insertBefore(o,i),o}function Pr(l,t){return Gr("div",l,t)}const Ip=new WeakMap;function ai(l,t,r,i,o){let u="translate("+t+"px,"+r+"px)",c=Ip.get(l);u!=c&&(l.style.transform=u,Ip.set(l,u),t<0||r<0||t>i||r>o?Sr(l,Ls):Mf(l,Ls))}const Fp=new WeakMap;function Hp(l,t,r){let i=t+r,o=Fp.get(l);i!=o&&(Fp.set(l,i),l.style.background=t,l.style.borderColor=r)}const Wp=new WeakMap;function Bp(l,t,r,i){let o=t+""+r,u=Wp.get(l);o!=u&&(Wp.set(l,o),l.style.height=r+"px",l.style.width=t+"px",l.style.marginLeft=i?-t/2+"px":0,l.style.marginTop=i?-r/2+"px":0)}const Jf={passive:!0},iw={...Jf,capture:!0};function As(l,t,r,i){t.addEventListener(l,r,i?iw:Jf)}function Df(l,t,r,i){t.removeEventListener(l,r,Jf)}zl&&Tf();function Kr(l,t,r,i){let o;r=r||0,i=i||t.length-1;let u=i<=2147483647;for(;i-r>1;)o=u?r+i>>1:xr((r+i)/2),t[o]{let u=-1,c=-1;for(let d=i;d<=o;d++)if(l(r[d])){u=d;break}for(let d=o;d>=i;d--)if(l(r[d])){c=d;break}return[u,c]}}const eg=l=>l!=null,tg=l=>l!=null&&l>0,Ru=Zm(eg),sw=Zm(tg);function lw(l,t,r,i=0,o=!1){let u=o?sw:Ru,c=o?tg:eg;[t,r]=u(l,t,r);let d=l[t],p=l[t];if(t>-1)if(i==1)d=l[t],p=l[r];else if(i==-1)d=l[r],p=l[t];else for(let g=t;g<=r;g++){let y=l[g];c(y)&&(yp&&(p=y))}return[d??ft,p??-ft]}function Nu(l,t,r,i){let o=$p(l),u=$p(t);l==t&&(o==-1?(l*=r,t/=r):(l/=r,t*=r));let c=r==10?ki:ng,d=o==1?xr:jr,p=u==1?jr:xr,g=d(c(en(l))),y=p(c(en(t))),v=kl(r,g),x=kl(r,y);return r==10&&(g<0&&(v=dt(v,-g)),y<0&&(x=dt(x,-y))),i||r==2?(l=v*o,t=x*u):(l=lg(l,v),t=bu(t,x)),[l,t]}function Zf(l,t,r,i){let o=Nu(l,t,r,i);return l==0&&(o[0]=0),t==0&&(o[1]=0),o}const ed=.1,Up={mode:3,pad:ed},No={pad:0,soft:null,mode:0},ow={min:No,max:No};function hu(l,t,r,i){return Tu(r)?Vp(l,t,r):(No.pad=r,No.soft=i?0:null,No.mode=i?3:0,Vp(l,t,ow))}function Xe(l,t){return l??t}function aw(l,t,r){for(t=Xe(t,0),r=Xe(r,l.length-1);t<=r;){if(l[t]!=null)return!0;t++}return!1}function Vp(l,t,r){let i=r.min,o=r.max,u=Xe(i.pad,0),c=Xe(o.pad,0),d=Xe(i.hard,-ft),p=Xe(o.hard,ft),g=Xe(i.soft,ft),y=Xe(o.soft,-ft),v=Xe(i.mode,0),x=Xe(o.mode,0),T=t-l,N=ki(T),C=Kn(en(l),en(t)),L=ki(C),U=en(L-N);(T<1e-24||U>10)&&(T=0,(l==0||t==0)&&(T=1e-24,v==2&&g!=ft&&(u=0),x==2&&y!=-ft&&(c=0)));let A=T||C||1e3,V=ki(A),z=kl(10,xr(V)),$=A*(T==0?l==0?.1:1:u),G=dt(lg(l-$,z/10),24),Y=l>=g&&(v==1||v==3&&G<=g||v==2&&G>=g)?g:ft,Z=Kn(d,G=Y?Y:Yr(Y,G)),K=A*(T==0?t==0?.1:1:c),fe=dt(bu(t+K,z/10),24),q=t<=y&&(x==1||x==3&&fe>=y||x==2&&fe<=y)?y:-ft,xe=Yr(p,fe>q&&t<=q?q:Kn(q,fe));return Z==xe&&Z==0&&(xe=100),[Z,xe]}const uw=new Intl.NumberFormat(zl?rw.language:"en-US"),td=l=>uw.format(l),_r=Math,tu=_r.PI,en=_r.abs,xr=_r.floor,Zt=_r.round,jr=_r.ceil,Yr=_r.min,Kn=_r.max,kl=_r.pow,$p=_r.sign,ki=_r.log10,ng=_r.log2,cw=(l,t=1)=>_r.sinh(l)*t,mf=(l,t=1)=>_r.asinh(l/t),ft=1/0;function Gp(l){return(ki((l^l>>31)-(l>>31))|0)+1}function zf(l,t,r){return Yr(Kn(l,t),r)}function rg(l){return typeof l=="function"}function $e(l){return rg(l)?l:()=>l}const fw=()=>{},ig=l=>l,sg=(l,t)=>t,dw=l=>null,Kp=l=>!0,Yp=(l,t)=>l==t,hw=/\.\d*?(?=9{6,}|0{6,})/gm,Is=l=>{if(ag(l)||os.has(l))return l;const t=`${l}`,r=t.match(hw);if(r==null)return l;let i=r[0].length-1;if(t.indexOf("e-")!=-1){let[o,u]=t.split("e");return+`${Is(o)}e${u}`}return dt(l,i)};function zs(l,t){return Is(dt(Is(l/t))*t)}function bu(l,t){return Is(jr(Is(l/t))*t)}function lg(l,t){return Is(xr(Is(l/t))*t)}function dt(l,t=0){if(ag(l))return l;let r=10**t,i=l*r*(1+Number.EPSILON);return Zt(i)/r}const os=new Map;function og(l){return((""+l).split(".")[1]||"").length}function zo(l,t,r,i){let o=[],u=i.map(og);for(let c=t;c=0?0:d)+(c>=u[g]?0:u[g]),x=l==10?y:dt(y,v);o.push(x),os.set(x,v)}}return o}const bo={},nd=[],Rl=[null,null],ls=Array.isArray,ag=Number.isInteger,pw=l=>l===void 0;function Qp(l){return typeof l=="string"}function Tu(l){let t=!1;if(l!=null){let r=l.constructor;t=r==null||r==Object}return t}function mw(l){return l!=null&&typeof l=="object"}const gw=Object.getPrototypeOf(Uint8Array),ug="__proto__";function Nl(l,t=Tu){let r;if(ls(l)){let i=l.find(o=>o!=null);if(ls(i)||t(i)){r=Array(l.length);for(let o=0;ou){for(o=c-1;o>=0&&l[o]==null;)l[o--]=null;for(o=c+1;oc-d)],o=i[0].length,u=new Map;for(let c=0;c"u"?l=>Promise.resolve().then(l):queueMicrotask;function Ew(l){let t=l[0],r=t.length,i=Array(r);for(let u=0;ut[u]-t[c]);let o=[];for(let u=0;u=i&&l[o]==null;)o--;if(o<=i)return!0;const u=Kn(1,xr((o-i+1)/t));for(let c=l[i],d=i+u;d<=o;d+=u){const p=l[d];if(p!=null){if(p<=c)return!1;c=p}}return!0}const cg=["January","February","March","April","May","June","July","August","September","October","November","December"],fg=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function dg(l){return l.slice(0,3)}const Rw=fg.map(dg),Nw=cg.map(dg),bw={MMMM:cg,MMM:Nw,WWWW:fg,WWW:Rw};function yo(l){return(l<10?"0":"")+l}function Tw(l){return(l<10?"00":l<100?"0":"")+l}const Mw={YYYY:l=>l.getFullYear(),YY:l=>(l.getFullYear()+"").slice(2),MMMM:(l,t)=>t.MMMM[l.getMonth()],MMM:(l,t)=>t.MMM[l.getMonth()],MM:l=>yo(l.getMonth()+1),M:l=>l.getMonth()+1,DD:l=>yo(l.getDate()),D:l=>l.getDate(),WWWW:(l,t)=>t.WWWW[l.getDay()],WWW:(l,t)=>t.WWW[l.getDay()],HH:l=>yo(l.getHours()),H:l=>l.getHours(),h:l=>{let t=l.getHours();return t==0?12:t>12?t-12:t},AA:l=>l.getHours()>=12?"PM":"AM",aa:l=>l.getHours()>=12?"pm":"am",a:l=>l.getHours()>=12?"p":"a",mm:l=>yo(l.getMinutes()),m:l=>l.getMinutes(),ss:l=>yo(l.getSeconds()),s:l=>l.getSeconds(),fff:l=>Tw(l.getMilliseconds())};function rd(l,t){t=t||bw;let r=[],i=/\{([a-z]+)\}|[^{]+/gi,o;for(;o=i.exec(l);)r.push(o[0][0]=="{"?Mw[o[1]]:o[0]);return u=>{let c="";for(let d=0;dl%1==0,pu=[1,2,2.5,5],Ow=zo(10,-32,0,pu),pg=zo(10,0,32,pu),Lw=pg.filter(hg),Os=Ow.concat(pg),id=` +`,mg="{YYYY}",qp=id+mg,gg="{M}/{D}",_o=id+gg,Ja=_o+"/{YY}",vg="{aa}",Pw="{h}:{mm}",Sl=Pw+vg,Xp=id+Sl,Jp=":{ss}",rt=null;function yg(l){let t=l*1e3,r=t*60,i=r*60,o=i*24,u=o*30,c=o*365,p=(l==1?zo(10,0,3,pu).filter(hg):zo(10,-3,0,pu)).concat([t,t*5,t*10,t*15,t*30,r,r*5,r*10,r*15,r*30,i,i*2,i*3,i*4,i*6,i*8,i*12,o,o*2,o*3,o*4,o*5,o*6,o*7,o*8,o*9,o*10,o*15,u,u*2,u*3,u*4,u*6,c,c*2,c*5,c*10,c*25,c*50,c*100]);const g=[[c,mg,rt,rt,rt,rt,rt,rt,1],[o*28,"{MMM}",qp,rt,rt,rt,rt,rt,1],[o,gg,qp,rt,rt,rt,rt,rt,1],[i,"{h}"+vg,Ja,rt,_o,rt,rt,rt,1],[r,Sl,Ja,rt,_o,rt,rt,rt,1],[t,Jp,Ja+" "+Sl,rt,_o+" "+Sl,rt,Xp,rt,1],[l,Jp+".{fff}",Ja+" "+Sl,rt,_o+" "+Sl,rt,Xp,rt,1]];function y(v){return(x,T,N,C,L,U)=>{let A=[],V=L>=c,z=L>=u&&L=o?o:L,fe=xr(N)-xr(G),q=Z+fe+bu(G-Z,K);A.push(q);let xe=v(q),ce=xe.getHours()+xe.getMinutes()/r+xe.getSeconds()/i,ge=L/i,ye=x.axes[T]._space,ke=U/ye;for(;q=dt(q+L,l==1?0:3),!(q>C);)if(ge>1){let le=xr(dt(ce+ge,6))%24,J=v(q).getHours()-le;J>1&&(J=-1),q-=J*i,ce=(ce+ge)%24;let M=A[A.length-1];dt((q-M)/L,3)*ke>=.7&&A.push(q)}else A.push(q)}return A}}return[p,g,y]}const[Aw,jw,Iw]=yg(1),[Fw,Hw,Ww]=yg(.001);zo(2,-53,53,[1]);function Zp(l,t){return l.map(r=>r.map((i,o)=>o==0||o==8||i==null?i:t(o==1||r[8]==0?i:r[1]+i)))}function em(l,t){return(r,i,o,u,c)=>{let d=t.find(N=>c>=N[0])||t[t.length-1],p,g,y,v,x,T;return i.map(N=>{let C=l(N),L=C.getFullYear(),U=C.getMonth(),A=C.getDate(),V=C.getHours(),z=C.getMinutes(),$=C.getSeconds(),G=L!=p&&d[2]||U!=g&&d[3]||A!=y&&d[4]||V!=v&&d[5]||z!=x&&d[6]||$!=T&&d[7]||d[1];return p=L,g=U,y=A,v=V,x=z,T=$,G(C)})}}function Bw(l,t){let r=rd(t);return(i,o,u,c,d)=>o.map(p=>r(l(p)))}function gf(l,t,r){return new Date(l,t,r)}function tm(l,t){return t(l)}const Uw="{YYYY}-{MM}-{DD} {h}:{mm}{aa}";function nm(l,t){return(r,i,o,u)=>u==null?Xf:t(l(i))}function Vw(l,t){let r=l.series[t];return r.width?r.stroke(l,t):r.points.width?r.points.stroke(l,t):null}function $w(l,t){return l.series[t].fill(l,t)}const Gw={show:!0,live:!0,isolate:!1,mount:fw,markers:{show:!0,width:2,stroke:Vw,fill:$w,dash:"solid"},idx:null,idxs:null,values:[]};function Kw(l,t){let r=l.cursor.points,i=Pr(),o=r.size(l,t);vt(i,So,o),vt(i,xo,o);let u=o/-2;vt(i,"marginLeft",u),vt(i,"marginTop",u);let c=r.width(l,t,o);return c&&vt(i,"borderWidth",c),i}function Yw(l,t){let r=l.series[t].points;return r._fill||r._stroke}function Qw(l,t){let r=l.series[t].points;return r._stroke||r._fill}function qw(l,t){return l.series[t].points.size}const vf=[0,0];function Xw(l,t,r){return vf[0]=t,vf[1]=r,vf}function Za(l,t,r,i=!0){return o=>{o.button==0&&(!i||o.target==t)&&r(o)}}function yf(l,t,r,i=!0){return o=>{(!i||o.target==t)&&r(o)}}const Jw={show:!0,x:!0,y:!0,lock:!1,move:Xw,points:{one:!1,show:Kw,size:qw,width:0,stroke:Qw,fill:Yw},bind:{mousedown:Za,mouseup:Za,click:Za,dblclick:Za,mousemove:yf,mouseleave:yf,mouseenter:yf},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,click:(l,t)=>{t.stopPropagation(),t.stopImmediatePropagation()},_x:!1,_y:!1},focus:{dist:(l,t,r,i,o)=>i-o,prox:-1,bias:0},hover:{skip:[void 0],prox:null,bias:0},left:-10,top:-10,idx:null,dataIdx:null,idxs:null,event:null},wg={show:!0,stroke:"rgba(0,0,0,0.07)",width:2},sd=$t({},wg,{filter:sg}),Sg=$t({},sd,{size:10}),xg=$t({},wg,{show:!1}),ld='12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',_g="bold "+ld,Eg=1.5,rm={show:!0,scale:"x",stroke:qf,space:50,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:_g,side:2,grid:sd,ticks:Sg,border:xg,font:ld,lineGap:Eg,rotate:0},Zw="Value",e1="Time",im={show:!0,scale:"x",auto:!1,sorted:1,min:ft,max:-ft,idxs:[]};function t1(l,t,r,i,o){return t.map(u=>u==null?"":td(u))}function n1(l,t,r,i,o,u,c){let d=[],p=os.get(o)||0;r=c?r:dt(bu(r,o),p);for(let g=r;g<=i;g=dt(g+o,p))d.push(Object.is(g,-0)?0:g);return d}function Of(l,t,r,i,o,u,c){const d=[],p=l.scales[l.axes[t].scale].log,g=p==10?ki:ng,y=xr(g(r));o=kl(p,y),p==10&&(o=Os[Kr(o,Os)]);let v=r,x=o*p;p==10&&(x=Os[Kr(x,Os)]);do d.push(v),v=v+o,p==10&&!os.has(v)&&(v=dt(v,os.get(o))),v>=x&&(o=v,x=o*p,p==10&&(x=Os[Kr(x,Os)]));while(v<=i);return d}function r1(l,t,r,i,o,u,c){let p=l.scales[l.axes[t].scale].asinh,g=i>p?Of(l,t,Kn(p,r),i,o):[p],y=i>=0&&r<=0?[0]:[];return(r<-p?Of(l,t,Kn(p,-i),-r,o):[p]).reverse().map(x=>-x).concat(y,g)}const Cg=/./,i1=/[12357]/,s1=/[125]/,sm=/1/,Lf=(l,t,r,i)=>l.map((o,u)=>t==4&&o==0||u%i==0&&r.test(o.toExponential()[o<0?1:0])?o:null);function l1(l,t,r,i,o){let u=l.axes[r],c=u.scale,d=l.scales[c],p=l.valToPos,g=u._space,y=p(10,c),v=p(9,c)-y>=g?Cg:p(7,c)-y>=g?i1:p(5,c)-y>=g?s1:sm;if(v==sm){let x=en(p(1,c)-y);if(xo,am={show:!0,auto:!0,sorted:0,gaps:kg,alpha:1,facets:[$t({},om,{scale:"x"}),$t({},om,{scale:"y"})]},um={scale:"y",auto:!0,sorted:0,show:!0,spanGaps:!1,gaps:kg,alpha:1,points:{show:c1,filter:null},values:null,min:ft,max:-ft,idxs:[],path:null,clip:null};function f1(l,t,r,i,o){return r/10}const Rg={time:A0,auto:!0,distr:1,log:10,asinh:1,min:null,max:null,dir:1,ori:0},d1=$t({},Rg,{time:!1,ori:1}),cm={};function Ng(l,t){let r=cm[l];return r||(r={key:l,plots:[],sub(i){r.plots.push(i)},unsub(i){r.plots=r.plots.filter(o=>o!=i)},pub(i,o,u,c,d,p,g){for(let y=0;y{let U=c.pxRound;const A=g.dir*(g.ori==0?1:-1),V=g.ori==0?Ol:Ll;let z,$;A==1?(z=r,$=i):(z=i,$=r);let G=U(v(d[z],g,C,T)),Y=U(x(p[z],y,L,N)),Z=U(v(d[$],g,C,T)),K=U(x(u==1?y.max:y.min,y,L,N)),fe=new Path2D(o);return V(fe,Z,K),V(fe,G,K),V(fe,G,Y),fe})}function Mu(l,t,r,i,o,u){let c=null;if(l.length>0){c=new Path2D;const d=t==0?Ou:ud;let p=r;for(let v=0;vx[0]){let T=x[0]-p;T>0&&d(c,p,i,T,i+u),p=x[1]}}let g=r+o-p,y=10;g>0&&d(c,p,i-y/2,g,i+u+y)}return c}function p1(l,t,r){let i=l[l.length-1];i&&i[0]==t?i[1]=r:l.push([t,r])}function ad(l,t,r,i,o,u,c){let d=[],p=l.length;for(let g=o==1?r:i;g>=r&&g<=i;g+=o)if(t[g]===null){let v=g,x=g;if(o==1)for(;++g<=i&&t[g]===null;)x=g;else for(;--g>=r&&t[g]===null;)x=g;let T=u(l[v]),N=x==v?T:u(l[x]),C=v-o;T=c<=0&&C>=0&&C=0&&U>=0&&U=T&&d.push([T,N])}return d}function fm(l){return l==0?ig:l==1?Zt:t=>zs(t,l)}function bg(l){let t=l==0?Du:zu,r=l==0?(o,u,c,d,p,g)=>{o.arcTo(u,c,d,p,g)}:(o,u,c,d,p,g)=>{o.arcTo(c,u,p,d,g)},i=l==0?(o,u,c,d,p)=>{o.rect(u,c,d,p)}:(o,u,c,d,p)=>{o.rect(c,u,p,d)};return(o,u,c,d,p,g=0,y=0)=>{g==0&&y==0?i(o,u,c,d,p):(g=Yr(g,d/2,p/2),y=Yr(y,d/2,p/2),t(o,u+g,c),r(o,u+d,c,u+d,c+p,g),r(o,u+d,c+p,u,c+p,y),r(o,u,c+p,u,c,y),r(o,u,c,u+d,c,g),o.closePath())}}const Du=(l,t,r)=>{l.moveTo(t,r)},zu=(l,t,r)=>{l.moveTo(r,t)},Ol=(l,t,r)=>{l.lineTo(t,r)},Ll=(l,t,r)=>{l.lineTo(r,t)},Ou=bg(0),ud=bg(1),Tg=(l,t,r,i,o,u)=>{l.arc(t,r,i,o,u)},Mg=(l,t,r,i,o,u)=>{l.arc(r,t,i,o,u)},Dg=(l,t,r,i,o,u,c)=>{l.bezierCurveTo(t,r,i,o,u,c)},zg=(l,t,r,i,o,u,c)=>{l.bezierCurveTo(r,t,o,i,c,u)};function Og(l){return(t,r,i,o,u)=>Fs(t,r,(c,d,p,g,y,v,x,T,N,C,L)=>{let{pxRound:U,points:A}=c,V,z;g.ori==0?(V=Du,z=Tg):(V=zu,z=Mg);const $=dt(A.width*Ze,3);let G=(A.size-A.width)/2*Ze,Y=dt(G*2,3),Z=new Path2D,K=new Path2D,{left:fe,top:q,width:xe,height:ce}=t.bbox;Ou(K,fe-Y,q-Y,xe+Y*2,ce+Y*2);const ge=ye=>{if(p[ye]!=null){let ke=U(v(d[ye],g,C,T)),le=U(x(p[ye],y,L,N));V(Z,ke+G,le),z(Z,ke,le,G,0,tu*2)}};if(u)u.forEach(ge);else for(let ye=i;ye<=o;ye++)ge(ye);return{stroke:$>0?Z:null,fill:Z,clip:K,flags:bl|Pf}})}function Lg(l){return(t,r,i,o,u,c)=>{i!=o&&(u!=i&&c!=i&&l(t,r,i),u!=o&&c!=o&&l(t,r,o),l(t,r,c))}}const m1=Lg(Ol),g1=Lg(Ll);function Pg(l){const t=Xe(l==null?void 0:l.alignGaps,0);return(r,i,o,u)=>Fs(r,i,(c,d,p,g,y,v,x,T,N,C,L)=>{[o,u]=Ru(p,o,u);let U=c.pxRound,A=ce=>U(v(ce,g,C,T)),V=ce=>U(x(ce,y,L,N)),z,$;g.ori==0?(z=Ol,$=m1):(z=Ll,$=g1);const G=g.dir*(g.ori==0?1:-1),Y={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:bl},Z=Y.stroke;let K=!1;if(u-o>=C*4){let ce=X=>r.posToVal(X,g.key,!0),ge=null,ye=null,ke,le,oe,ae=A(d[G==1?o:u]),J=A(d[o]),M=A(d[u]),W=ce(G==1?J+1:M-1);for(let X=G==1?o:u;X>=o&&X<=u;X+=G){let ee=d[X],he=(G==1?eeW)?ae:A(ee),Ee=p[X];he==ae?Ee!=null?(le=Ee,ge==null?(z(Z,he,V(le)),ke=ge=ye=le):leye&&(ye=le)):Ee===null&&(K=!0):(ge!=null&&$(Z,ae,V(ge),V(ye),V(ke),V(le)),Ee!=null?(le=Ee,z(Z,he,V(le)),ge=ye=ke=le):(ge=ye=null,Ee===null&&(K=!0)),ae=he,W=ce(ae+G))}ge!=null&&ge!=ye&&oe!=ae&&$(Z,ae,V(ge),V(ye),V(ke),V(le))}else for(let ce=G==1?o:u;ce>=o&&ce<=u;ce+=G){let ge=p[ce];ge===null?K=!0:ge!=null&&z(Z,A(d[ce]),V(ge))}let[q,xe]=od(r,i);if(c.fill!=null||q!=0){let ce=Y.fill=new Path2D(Z),ge=c.fillTo(r,i,c.min,c.max,q),ye=V(ge),ke=A(d[o]),le=A(d[u]);G==-1&&([le,ke]=[ke,le]),z(ce,le,ye),z(ce,ke,ye)}if(!c.spanGaps){let ce=[];K&&ce.push(...ad(d,p,o,u,G,A,t)),Y.gaps=ce=c.gaps(r,i,o,u,ce),Y.clip=Mu(ce,g.ori,T,N,C,L)}return xe!=0&&(Y.band=xe==2?[Ri(r,i,o,u,Z,-1),Ri(r,i,o,u,Z,1)]:Ri(r,i,o,u,Z,xe)),Y})}function v1(l){const t=Xe(l.align,1),r=Xe(l.ascDesc,!1),i=Xe(l.alignGaps,0),o=Xe(l.extend,!1);return(u,c,d,p)=>Fs(u,c,(g,y,v,x,T,N,C,L,U,A,V)=>{[d,p]=Ru(v,d,p);let z=g.pxRound,{left:$,width:G}=u.bbox,Y=J=>z(N(J,x,A,L)),Z=J=>z(C(J,T,V,U)),K=x.ori==0?Ol:Ll;const fe={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:bl},q=fe.stroke,xe=x.dir*(x.ori==0?1:-1);let ce=Z(v[xe==1?d:p]),ge=Y(y[xe==1?d:p]),ye=ge,ke=ge;o&&t==-1&&(ke=$,K(q,ke,ce)),K(q,ge,ce);for(let J=xe==1?d:p;J>=d&&J<=p;J+=xe){let M=v[J];if(M==null)continue;let W=Y(y[J]),X=Z(M);t==1?K(q,W,ce):K(q,ye,X),K(q,W,X),ce=X,ye=W}let le=ye;o&&t==1&&(le=$+G,K(q,le,ce));let[oe,ae]=od(u,c);if(g.fill!=null||oe!=0){let J=fe.fill=new Path2D(q),M=g.fillTo(u,c,g.min,g.max,oe),W=Z(M);K(J,le,W),K(J,ke,W)}if(!g.spanGaps){let J=[];J.push(...ad(y,v,d,p,xe,Y,i));let M=g.width*Ze/2,W=r||t==1?M:-M,X=r||t==-1?-M:M;J.forEach(ee=>{ee[0]+=W,ee[1]+=X}),fe.gaps=J=g.gaps(u,c,d,p,J),fe.clip=Mu(J,x.ori,L,U,A,V)}return ae!=0&&(fe.band=ae==2?[Ri(u,c,d,p,q,-1),Ri(u,c,d,p,q,1)]:Ri(u,c,d,p,q,ae)),fe})}function dm(l,t,r,i,o,u,c=ft){if(l.length>1){let d=null;for(let p=0,g=1/0;p{}),{fill:v,stroke:x}=g;return(T,N,C,L)=>Fs(T,N,(U,A,V,z,$,G,Y,Z,K,fe,q)=>{let xe=U.pxRound,ce=r,ge=i*Ze,ye=d*Ze,ke=p*Ze,le,oe;z.ori==0?[le,oe]=u(T,N):[oe,le]=u(T,N);const ae=z.dir*(z.ori==0?1:-1);let J=z.ori==0?Ou:ud,M=z.ori==0?y:(pe,Je,tt,ln,Rn,Kt,Nt)=>{y(pe,Je,tt,Rn,ln,Nt,Kt)},W=Xe(T.bands,nd).find(pe=>pe.series[0]==N),X=W!=null?W.dir:0,ee=U.fillTo(T,N,U.min,U.max,X),be=xe(Y(ee,$,q,K)),he,Ee,Ie,Fe=fe,Oe=xe(U.width*Ze),Gt=!1,At=null,jt=null,It=null,Qn=null;v!=null&&(Oe==0||x!=null)&&(Gt=!0,At=v.values(T,N,C,L),jt=new Map,new Set(At).forEach(pe=>{pe!=null&&jt.set(pe,new Path2D)}),Oe>0&&(It=x.values(T,N,C,L),Qn=new Map,new Set(It).forEach(pe=>{pe!=null&&Qn.set(pe,new Path2D)})));let{x0:kn,size:Er}=g;if(kn!=null&&Er!=null){ce=1,A=kn.values(T,N,C,L),kn.unit==2&&(A=A.map(tt=>T.posToVal(Z+tt*fe,z.key,!0)));let pe=Er.values(T,N,C,L);Er.unit==2?Ee=pe[0]*fe:Ee=G(pe[0],z,fe,Z)-G(0,z,fe,Z),Fe=dm(A,V,G,z,fe,Z,Fe),Ie=Fe-Ee+ge}else Fe=dm(A,V,G,z,fe,Z,Fe),Ie=Fe*c+ge,Ee=Fe-Ie;Ie<1&&(Ie=0),Oe>=Ee/2&&(Oe=0),Ie<5&&(xe=ig);let Xr=Ie>0,An=Fe-Ie-(Xr?Oe:0);Ee=xe(zf(An,ke,ye)),he=(ce==0?Ee/2:ce==ae?0:Ee)-ce*ae*((ce==0?ge/2:0)+(Xr?Oe/2:0));const et={stroke:null,fill:null,clip:null,band:null,gaps:null,flags:0},rn=Gt?null:new Path2D;let sn=null;if(W!=null)sn=T.data[W.series[1]];else{let{y0:pe,y1:Je}=g;pe!=null&&Je!=null&&(V=Je.values(T,N,C,L),sn=pe.values(T,N,C,L))}let lr=le*Ee,Pe=oe*Ee;for(let pe=ae==1?C:L;pe>=C&&pe<=L;pe+=ae){let Je=V[pe];if(Je==null)continue;if(sn!=null){let Yt=sn[pe]??0;if(Je-Yt==0)continue;be=Y(Yt,$,q,K)}let tt=z.distr!=2||g!=null?A[pe]:pe,ln=G(tt,z,fe,Z),Rn=Y(Xe(Je,ee),$,q,K),Kt=xe(ln-he),Nt=xe(Kn(Rn,be)),on=xe(Yr(Rn,be)),gn=Nt-on;if(Je!=null){let Yt=Je<0?Pe:lr,vn=Je<0?lr:Pe;Gt?(Oe>0&&It[pe]!=null&&J(Qn.get(It[pe]),Kt,on+xr(Oe/2),Ee,Kn(0,gn-Oe),Yt,vn),At[pe]!=null&&J(jt.get(At[pe]),Kt,on+xr(Oe/2),Ee,Kn(0,gn-Oe),Yt,vn)):J(rn,Kt,on+xr(Oe/2),Ee,Kn(0,gn-Oe),Yt,vn),M(T,N,pe,Kt-Oe/2,on,Ee+Oe,gn)}}return Oe>0?et.stroke=Gt?Qn:rn:Gt||(et._fill=U.width==0?U._fill:U._stroke??U._fill,et.width=0),et.fill=Gt?jt:rn,et})}function w1(l,t){const r=Xe(t==null?void 0:t.alignGaps,0);return(i,o,u,c)=>Fs(i,o,(d,p,g,y,v,x,T,N,C,L,U)=>{[u,c]=Ru(g,u,c);let A=d.pxRound,V=le=>A(x(le,y,L,N)),z=le=>A(T(le,v,U,C)),$,G,Y;y.ori==0?($=Du,Y=Ol,G=Dg):($=zu,Y=Ll,G=zg);const Z=y.dir*(y.ori==0?1:-1);let K=V(p[Z==1?u:c]),fe=K,q=[],xe=[];for(let le=Z==1?u:c;le>=u&&le<=c;le+=Z)if(g[le]!=null){let ae=p[le],J=V(ae);q.push(fe=J),xe.push(z(g[le]))}const ce={stroke:l(q,xe,$,Y,G,A),fill:null,clip:null,band:null,gaps:null,flags:bl},ge=ce.stroke;let[ye,ke]=od(i,o);if(d.fill!=null||ye!=0){let le=ce.fill=new Path2D(ge),oe=d.fillTo(i,o,d.min,d.max,ye),ae=z(oe);Y(le,fe,ae),Y(le,K,ae)}if(!d.spanGaps){let le=[];le.push(...ad(p,g,u,c,Z,V,r)),ce.gaps=le=d.gaps(i,o,u,c,le),ce.clip=Mu(le,y.ori,N,C,L,U)}return ke!=0&&(ce.band=ke==2?[Ri(i,o,u,c,ge,-1),Ri(i,o,u,c,ge,1)]:Ri(i,o,u,c,ge,ke)),ce})}function S1(l){return w1(x1,l)}function x1(l,t,r,i,o,u){const c=l.length;if(c<2)return null;const d=new Path2D;if(r(d,l[0],t[0]),c==2)i(d,l[1],t[1]);else{let p=Array(c),g=Array(c-1),y=Array(c-1),v=Array(c-1);for(let x=0;x0!=g[x]>0?p[x]=0:(p[x]=3*(v[x-1]+v[x])/((2*v[x]+v[x-1])/g[x-1]+(v[x]+2*v[x-1])/g[x]),isFinite(p[x])||(p[x]=0));p[c-1]=g[c-2];for(let x=0;x{Pn.pxRatio=Ze}));const _1=Pg(),E1=Og();function pm(l,t,r,i){return(i?[l[0],l[1]].concat(l.slice(2)):[l[0]].concat(l.slice(1))).map((u,c)=>jf(u,c,t,r))}function C1(l,t){return l.map((r,i)=>i==0?{}:$t({},t,r))}function jf(l,t,r,i){return $t({},t==0?r:i,l)}function Ag(l,t,r){return t==null?Rl:[t,r]}const k1=Ag;function R1(l,t,r){return t==null?Rl:hu(t,r,ed,!0)}function jg(l,t,r,i){return t==null?Rl:Nu(t,r,l.scales[i].log,!1)}const N1=jg;function Ig(l,t,r,i){return t==null?Rl:Zf(t,r,l.scales[i].log,!1)}const b1=Ig;function T1(l,t,r,i,o){let u=Kn(Gp(l),Gp(t)),c=t-l,d=Kr(o/i*c,r);do{let p=r[d],g=i*p/c;if(g>=o&&u+(p<5?os.get(p):0)<=17)return[p,g]}while(++d(t=Zt((r=+o)*Ze))+"px"),[l,t,r]}function M1(l){l.show&&[l.font,l.labelFont].forEach(t=>{let r=dt(t[2]*Ze,1);t[0]=t[0].replace(/[0-9.]+px/,r+"px"),t[1]=r})}function Pn(l,t,r){const i={mode:Xe(l.mode,1)},o=i.mode;function u(m,S,_,E){let D=S.valToPct(m);return E+_*(S.dir==-1?1-D:D)}function c(m,S,_,E){let D=S.valToPct(m);return E+_*(S.dir==-1?D:1-D)}function d(m,S,_,E){return S.ori==0?u(m,S,_,E):c(m,S,_,E)}i.valToPosH=u,i.valToPosV=c;let p=!1;i.status=0;const g=i.root=Pr(j0);if(l.id!=null&&(g.id=l.id),Sr(g,l.class),l.title){let m=Pr(H0,g);m.textContent=l.title}const y=Gr("canvas"),v=i.ctx=y.getContext("2d"),x=Pr(W0,g);As("click",x,m=>{m.target===N&&(Qe!=hi||it!=Fi)&&qt.click(i,m)},!0);const T=i.under=Pr(B0,x);x.appendChild(y);const N=i.over=Pr(U0,x);l=Nl(l);const C=+Xe(l.pxAlign,1),L=fm(C);(l.plugins||[]).forEach(m=>{m.opts&&(l=m.opts(i,l)||l)});const U=l.ms||.001,A=i.series=o==1?pm(l.series||[],im,um,!1):C1(l.series||[null],am),V=i.axes=pm(l.axes||[],rm,lm,!0),z=i.scales={},$=i.bands=l.bands||[];$.forEach(m=>{m.fill=$e(m.fill||null),m.dir=Xe(m.dir,-1)});const G=o==2?A[1].facets[0].scale:A[0].scale,Y={axes:$o,series:Iu},Z=(l.drawOrder||["axes","series"]).map(m=>Y[m]);function K(m){const S=m.distr==3?_=>ki(_>0?_:m.clamp(i,_,m.min,m.max,m.key)):m.distr==4?_=>mf(_,m.asinh):m.distr==100?_=>m.fwd(_):_=>_;return _=>{let E=S(_),{_min:D,_max:I}=m,Q=I-D;return(E-D)/Q}}function fe(m){let S=z[m];if(S==null){let _=(l.scales||bo)[m]||bo;if(_.from!=null){fe(_.from);let E=$t({},z[_.from],_,{key:m});E.valToPct=K(E),z[m]=E}else{S=z[m]=$t({},m==G?Rg:d1,_),S.key=m;let E=S.time,D=S.range,I=ls(D);if((m!=G||o==2&&!E)&&(I&&(D[0]==null||D[1]==null)&&(D={min:D[0]==null?Up:{mode:1,hard:D[0],soft:D[0]},max:D[1]==null?Up:{mode:1,hard:D[1],soft:D[1]}},I=!1),!I&&Tu(D))){let Q=D;D=(ne,se,de)=>se==null?Rl:hu(se,de,Q)}S.range=$e(D||(E?k1:m==G?S.distr==3?N1:S.distr==4?b1:Ag:S.distr==3?jg:S.distr==4?Ig:R1)),S.auto=$e(I?!1:S.auto),S.clamp=$e(S.clamp||f1),S._min=S._max=null,S.valToPct=K(S)}}}fe("x"),fe("y"),o==1&&A.forEach(m=>{fe(m.scale)}),V.forEach(m=>{fe(m.scale)});for(let m in l.scales)fe(m);const q=z[G],xe=q.distr;let ce,ge;q.ori==0?(Sr(g,I0),ce=u,ge=c):(Sr(g,F0),ce=c,ge=u);const ye={};for(let m in z){let S=z[m];(S.min!=null||S.max!=null)&&(ye[m]={min:S.min,max:S.max},S.min=S.max=null)}const ke=l.tzDate||(m=>new Date(Zt(m/U))),le=l.fmtDate||rd,oe=U==1?Iw(ke):Ww(ke),ae=em(ke,Zp(U==1?jw:Hw,le)),J=nm(ke,tm(Uw,le)),M=[],W=i.legend=$t({},Gw,l.legend),X=i.cursor=$t({},Jw,{drag:{y:o==2}},l.cursor),ee=W.show,be=X.show,he=W.markers;W.idxs=M,he.width=$e(he.width),he.dash=$e(he.dash),he.stroke=$e(he.stroke),he.fill=$e(he.fill);let Ee,Ie,Fe,Oe=[],Gt=[],At,jt=!1,It={};if(W.live){const m=A[1]?A[1].values:null;jt=m!=null,At=jt?m(i,1,0):{_:0};for(let S in At)It[S]=Xf}if(ee)if(Ee=Gr("table",Q0,g),Fe=Gr("tbody",null,Ee),W.mount(i,Ee),jt){Ie=Gr("thead",null,Ee,Fe);let m=Gr("tr",null,Ie);Gr("th",null,m);for(var Qn in At)Gr("th",Mp,m).textContent=Qn}else Sr(Ee,X0),W.live&&Sr(Ee,q0);const kn={show:!0},Er={show:!1};function Xr(m,S){if(S==0&&(jt||!W.live||o==2))return Rl;let _=[],E=Gr("tr",J0,Fe,Fe.childNodes[S]);Sr(E,m.class),m.show||Sr(E,Ls);let D=Gr("th",null,E);if(he.show){let ne=Pr(Z0,D);if(S>0){let se=he.width(i,S);se&&(ne.style.border=se+"px "+he.dash(i,S)+" "+he.stroke(i,S)),ne.style.background=he.fill(i,S)}}let I=Pr(Mp,D);m.label instanceof HTMLElement?I.appendChild(m.label):I.textContent=m.label,S>0&&(he.show||(I.style.color=m.width>0?he.stroke(i,S):he.fill(i,S)),et("click",D,ne=>{if(X._lock)return;wn(ne);let se=A.indexOf(m);if((ne.ctrlKey||ne.metaKey)!=W.isolate){let de=A.some((me,ve)=>ve>0&&ve!=se&&me.show);A.forEach((me,ve)=>{ve>0&&hr(ve,de?ve==se?kn:Er:kn,!0,Tt.setSeries)})}else hr(se,{show:!m.show},!0,Tt.setSeries)},!1),Et&&et(Lp,D,ne=>{X._lock||(wn(ne),hr(A.indexOf(m),Bi,!0,Tt.setSeries))},!1));for(var Q in At){let ne=Gr("td",ew,E);ne.textContent="--",_.push(ne)}return[E,_]}const An=new Map;function et(m,S,_,E=!0){const D=An.get(S)||{},I=X.bind[m](i,S,_,E);I&&(As(m,S,D[m]=I),An.set(S,D))}function rn(m,S,_){const E=An.get(S)||{};for(let D in E)(m==null||D==m)&&(Df(D,S,E[D]),delete E[D]);m==null&&An.delete(S)}let sn=0,lr=0,Pe=0,pe=0,Je=0,tt=0,ln=Je,Rn=tt,Kt=Pe,Nt=pe,on=0,gn=0,Yt=0,vn=0;i.bbox={};let Jr=!1,Zr=!1,or=!1,ar=!1,ei=!1,Dt=!1;function ot(m,S,_){(_||m!=i.width||S!=i.height)&&Qt(m,S),di(!1),or=!0,Zr=!0,Fn()}function Qt(m,S){i.width=sn=Pe=m,i.height=lr=pe=S,Je=tt=0,un(),Nn();let _=i.bbox;on=_.left=zs(Je*Ze,.5),gn=_.top=zs(tt*Ze,.5),Yt=_.width=zs(Pe*Ze,.5),vn=_.height=zs(pe*Ze,.5)}const an=3;function ur(){let m=!1,S=0;for(;!m;){S++;let _=Wl(S),E=Vo(S);m=S==an||_&&E,m||(Qt(i.width,i.height),Zr=!0)}}function yn({width:m,height:S}){ot(m,S)}i.setSize=yn;function un(){let m=!1,S=!1,_=!1,E=!1;V.forEach((D,I)=>{if(D.show&&D._show){let{side:Q,_size:ne}=D,se=Q%2,de=D.label!=null?D.labelSize:0,me=ne+de;me>0&&(se?(Pe-=me,Q==3?(Je+=me,E=!0):_=!0):(pe-=me,Q==0?(tt+=me,m=!0):S=!0))}}),jn[0]=m,jn[1]=_,jn[2]=S,jn[3]=E,Pe-=Ir[1]+Ir[3],Je+=Ir[3],pe-=Ir[2]+Ir[0],tt+=Ir[0]}function Nn(){let m=Je+Pe,S=tt+pe,_=Je,E=tt;function D(I,Q){switch(I){case 1:return m+=Q,m-Q;case 2:return S+=Q,S-Q;case 3:return _-=Q,_+Q;case 0:return E-=Q,E+Q}}V.forEach((I,Q)=>{if(I.show&&I._show){let ne=I.side;I._pos=D(ne,I._size),I.label!=null&&(I._lpos=D(ne,I.labelSize))}})}if(X.dataIdx==null){let m=X.hover,S=m.skip=new Set(m.skip??[]);S.add(void 0);let _=m.prox=$e(m.prox),E=m.bias??(m.bias=0);X.dataIdx=(D,I,Q,ne)=>{if(I==0)return Q;let se=Q,de=_(D,I,Q,ne)??ft,me=de>=0&&de0;)S.has(Ve[Re])||(He=Re);if(E==0||E==1)for(Re=Q;Me==null&&Re++de&&(se=null);return se}}const wn=m=>{X.event=m};X.idxs=M,X._lock=!1;let Be=X.points;Be.show=$e(Be.show),Be.size=$e(Be.size),Be.stroke=$e(Be.stroke),Be.width=$e(Be.width),Be.fill=$e(Be.fill);const _t=i.focus=$t({},l.focus||{alpha:.3},X.focus),Et=_t.prox>=0,cn=Et&&Be.one;let yt=[],Sn=[],Ft=[];function Cr(m,S){let _=Be.show(i,S);if(_ instanceof HTMLElement)return Sr(_,Y0),Sr(_,m.class),ai(_,-10,-10,Pe,pe),N.insertBefore(_,yt[S]),_}function bi(m,S){if(o==1||S>0){let _=o==1&&z[m.scale].time,E=m.value;m.value=_?Qp(E)?nm(ke,tm(E,le)):E||J:E||a1,m.label=m.label||(_?e1:Zw)}if(cn||S>0){m.width=m.width==null?1:m.width,m.paths=m.paths||_1||dw,m.fillTo=$e(m.fillTo||h1),m.pxAlign=+Xe(m.pxAlign,C),m.pxRound=fm(m.pxAlign),m.stroke=$e(m.stroke||null),m.fill=$e(m.fill||null),m._stroke=m._fill=m._paths=m._focus=null;let _=u1(Kn(1,m.width),1),E=m.points=$t({},{size:_,width:Kn(1,_*.2),stroke:m.stroke,space:_*2,paths:E1,_stroke:null,_fill:null},m.points);E.show=$e(E.show),E.filter=$e(E.filter),E.fill=$e(E.fill),E.stroke=$e(E.stroke),E.paths=$e(E.paths),E.pxAlign=m.pxAlign}if(ee){let _=Xr(m,S);Oe.splice(S,0,_[0]),Gt.splice(S,0,_[1]),W.values.push(null)}if(be){M.splice(S,0,null);let _=null;cn?S==0&&(_=Cr(m,S)):S>0&&(_=Cr(m,S)),yt.splice(S,0,_),Sn.splice(S,0,0),Ft.splice(S,0,0)}Wt("addSeries",S)}function Pu(m,S){S=S??A.length,m=o==1?jf(m,S,im,um):jf(m,S,{},am),A.splice(S,0,m),bi(A[S],S)}i.addSeries=Pu;function Au(m){if(A.splice(m,1),ee){W.values.splice(m,1),Gt.splice(m,1);let S=Oe.splice(m,1)[0];rn(null,S.firstChild),S.remove()}be&&(M.splice(m,1),yt.splice(m,1)[0].remove(),Sn.splice(m,1),Ft.splice(m,1)),Wt("delSeries",m)}i.delSeries=Au;const jn=[!1,!1,!1,!1];function Fo(m,S){if(m._show=m.show,m.show){let _=m.side%2,E=z[m.scale];E==null&&(m.scale=_?A[1].scale:G,E=z[m.scale]);let D=E.time;m.size=$e(m.size),m.space=$e(m.space),m.rotate=$e(m.rotate),ls(m.incrs)&&m.incrs.forEach(Q=>{!os.has(Q)&&os.set(Q,og(Q))}),m.incrs=$e(m.incrs||(E.distr==2?Lw:D?U==1?Aw:Fw:Os)),m.splits=$e(m.splits||(D&&E.distr==1?oe:E.distr==3?Of:E.distr==4?r1:n1)),m.stroke=$e(m.stroke),m.grid.stroke=$e(m.grid.stroke),m.ticks.stroke=$e(m.ticks.stroke),m.border.stroke=$e(m.border.stroke);let I=m.values;m.values=ls(I)&&!ls(I[0])?$e(I):D?ls(I)?em(ke,Zp(I,le)):Qp(I)?Bw(ke,I):I||ae:I||t1,m.filter=$e(m.filter||(E.distr>=3&&E.log==10?l1:E.distr==3&&E.log==2?o1:sg)),m.font=mm(m.font),m.labelFont=mm(m.labelFont),m._size=m.size(i,null,S,0),m._space=m._rotate=m._incrs=m._found=m._splits=m._values=null,m._size>0&&(jn[S]=!0,m._el=Pr(V0,x))}}function Ti(m,S,_,E){let[D,I,Q,ne]=_,se=S%2,de=0;return se==0&&(ne||I)&&(de=S==0&&!D||S==2&&!Q?Zt(rm.size/3):0),se==1&&(D||Q)&&(de=S==1&&!I||S==3&&!ne?Zt(lm.size/2):0),de}const Ho=i.padding=(l.padding||[Ti,Ti,Ti,Ti]).map(m=>$e(Xe(m,Ti))),Ir=i._padding=Ho.map((m,S)=>m(i,S,jn,0));let Ht,zt=null,Ot=null;const Hs=o==1?A[0].idxs:null;let cr=null,at=!1;function Wo(m,S){if(t=m??[],i.data=i._data=t,o==2){Ht=0;for(let _=1;_=0,Dt=!0,Fn()}}i.setData=Wo;function as(){at=!0;let m,S;o==1&&(Ht>0?(zt=Hs[0]=0,Ot=Hs[1]=Ht-1,m=t[0][zt],S=t[0][Ot],xe==2?(m=zt,S=Ot):m==S&&(xe==3?[m,S]=Nu(m,m,q.log,!1):xe==4?[m,S]=Zf(m,m,q.log,!1):q.time?S=m+Zt(86400/U):[m,S]=hu(m,S,ed,!0))):(zt=Hs[0]=m=null,Ot=Hs[1]=S=null)),dr(G,m,S)}let us,Fr,Pl,Ws,Mi,qn,Al,In,jl,bn;function Bo(m,S,_,E,D,I){m??(m=zp),_??(_=nd),E??(E="butt"),D??(D=zp),I??(I="round"),m!=us&&(v.strokeStyle=us=m),D!=Fr&&(v.fillStyle=Fr=D),S!=Pl&&(v.lineWidth=Pl=S),I!=Mi&&(v.lineJoin=Mi=I),E!=qn&&(v.lineCap=qn=E),_!=Ws&&v.setLineDash(Ws=_)}function cs(m,S,_,E){S!=Fr&&(v.fillStyle=Fr=S),m!=Al&&(v.font=Al=m),_!=In&&(v.textAlign=In=_),E!=jl&&(v.textBaseline=jl=E)}function Di(m,S,_,E,D=0){if(E.length>0&&m.auto(i,at)&&(S==null||S.min==null)){let I=Xe(zt,0),Q=Xe(Ot,E.length-1),ne=_.min==null?lw(E,I,Q,D,m.distr==3):[_.min,_.max];m.min=Yr(m.min,_.min=ne[0]),m.max=Kn(m.max,_.max=ne[1])}}const zi={min:null,max:null};function Bs(){for(let E in z){let D=z[E];ye[E]==null&&(D.min==null||ye[G]!=null&&D.auto(i,at))&&(ye[E]=zi)}for(let E in z){let D=z[E];ye[E]==null&&D.from!=null&&ye[D.from]!=null&&(ye[E]=zi)}ye[G]!=null&&di(!0);let m={};for(let E in ye){let D=ye[E];if(D!=null){let I=m[E]=Nl(z[E],mw);if(D.min!=null)$t(I,D);else if(E!=G||o==2)if(Ht==0&&I.from==null){let Q=I.range(i,null,null,E);I.min=Q[0],I.max=Q[1]}else I.min=ft,I.max=-ft}}if(Ht>0){A.forEach((E,D)=>{if(o==1){let I=E.scale,Q=ye[I];if(Q==null)return;let ne=m[I];if(D==0){let se=ne.range(i,ne.min,ne.max,I);ne.min=se[0],ne.max=se[1],zt=Kr(ne.min,t[0]),Ot=Kr(ne.max,t[0]),Ot-zt>1&&(t[0][zt]ne.max&&Ot--),E.min=cr[zt],E.max=cr[Ot]}else E.show&&E.auto&&Di(ne,Q,E,t[D],E.sorted);E.idxs[0]=zt,E.idxs[1]=Ot}else if(D>0&&E.show&&E.auto){let[I,Q]=E.facets,ne=I.scale,se=Q.scale,[de,me]=t[D],ve=m[ne],Ae=m[se];ve!=null&&Di(ve,ye[ne],I,de,I.sorted),Ae!=null&&Di(Ae,ye[se],Q,me,Q.sorted),E.min=Q.min,E.max=Q.max}});for(let E in m){let D=m[E],I=ye[E];if(D.from==null&&(I==null||I.min==null)){let Q=D.range(i,D.min==ft?null:D.min,D.max==-ft?null:D.max,E);D.min=Q[0],D.max=Q[1]}}}for(let E in m){let D=m[E];if(D.from!=null){let I=m[D.from];if(I.min==null)D.min=D.max=null;else{let Q=D.range(i,I.min,I.max,E);D.min=Q[0],D.max=Q[1]}}}let S={},_=!1;for(let E in m){let D=m[E],I=z[E];if(I.min!=D.min||I.max!=D.max){I.min=D.min,I.max=D.max;let Q=I.distr;I._min=Q==3?ki(I.min):Q==4?mf(I.min,I.asinh):Q==100?I.fwd(I.min):I.min,I._max=Q==3?ki(I.max):Q==4?mf(I.max,I.asinh):Q==100?I.fwd(I.max):I.max,S[E]=_=!0}}if(_){A.forEach((E,D)=>{o==2?D>0&&S.y&&(E._paths=null):S[E.scale]&&(E._paths=null)});for(let E in S)or=!0,Wt("setScale",E);be&&X.left>=0&&(ar=Dt=!0)}for(let E in ye)ye[E]=null}function ju(m){let S=zf(zt-1,0,Ht-1),_=zf(Ot+1,0,Ht-1);for(;m[S]==null&&S>0;)S--;for(;m[_]==null&&_0){let m=A.some(S=>S._focus)&&bn!=_t.alpha;m&&(v.globalAlpha=bn=_t.alpha),A.forEach((S,_)=>{if(_>0&&S.show&&(Us(_,!1),Us(_,!0),S._paths==null)){let E=bn;bn!=S.alpha&&(v.globalAlpha=bn=S.alpha);let D=o==2?[0,t[_][0].length-1]:ju(t[_]);S._paths=S.paths(i,_,D[0],D[1]),bn!=E&&(v.globalAlpha=bn=E)}}),A.forEach((S,_)=>{if(_>0&&S.show){let E=bn;bn!=S.alpha&&(v.globalAlpha=bn=S.alpha),S._paths!=null&&Il(_,!1);{let D=S._paths!=null?S._paths.gaps:null,I=S.points.show(i,_,zt,Ot,D),Q=S.points.filter(i,_,I,D);(I||Q)&&(S.points._paths=S.points.paths(i,_,zt,Ot,Q),Il(_,!0))}bn!=E&&(v.globalAlpha=bn=E),Wt("drawSeries",_)}}),m&&(v.globalAlpha=bn=1)}}function Us(m,S){let _=S?A[m].points:A[m];_._stroke=_.stroke(i,m),_._fill=_.fill(i,m)}function Il(m,S){let _=S?A[m].points:A[m],{stroke:E,fill:D,clip:I,flags:Q,_stroke:ne=_._stroke,_fill:se=_._fill,_width:de=_.width}=_._paths;de=dt(de*Ze,3);let me=null,ve=de%2/2;S&&se==null&&(se=de>0?"#fff":ne);let Ae=_.pxAlign==1&&ve>0;if(Ae&&v.translate(ve,ve),!S){let Ke=on-de/2,Ve=gn-de/2,He=Yt+de,Me=vn+de;me=new Path2D,me.rect(Ke,Ve,He,Me)}S?Hl(ne,de,_.dash,_.cap,se,E,D,Q,I):Fl(m,ne,de,_.dash,_.cap,se,E,D,Q,me,I),Ae&&v.translate(-ve,-ve)}function Fl(m,S,_,E,D,I,Q,ne,se,de,me){let ve=!1;se!=0&&$.forEach((Ae,Ke)=>{if(Ae.series[0]==m){let Ve=A[Ae.series[1]],He=t[Ae.series[1]],Me=(Ve._paths||bo).band;ls(Me)&&(Me=Ae.dir==1?Me[0]:Me[1]);let Re,lt=null;Ve.show&&Me&&aw(He,zt,Ot)?(lt=Ae.fill(i,Ke)||I,Re=Ve._paths.clip):Me=null,Hl(S,_,E,D,lt,Q,ne,se,de,me,Re,Me),ve=!0}}),ve||Hl(S,_,E,D,I,Q,ne,se,de,me)}const Oi=bl|Pf;function Hl(m,S,_,E,D,I,Q,ne,se,de,me,ve){Bo(m,S,_,E,D),(se||de||ve)&&(v.save(),se&&v.clip(se),de&&v.clip(de)),ve?(ne&Oi)==Oi?(v.clip(ve),me&&v.clip(me),Ge(D,Q),Li(m,I,S)):ne&Pf?(Ge(D,Q),v.clip(ve),Li(m,I,S)):ne&bl&&(v.save(),v.clip(ve),me&&v.clip(me),Ge(D,Q),v.restore(),Li(m,I,S)):(Ge(D,Q),Li(m,I,S)),(se||de||ve)&&v.restore()}function Li(m,S,_){_>0&&(S instanceof Map?S.forEach((E,D)=>{v.strokeStyle=us=D,v.stroke(E)}):S!=null&&m&&v.stroke(S))}function Ge(m,S){S instanceof Map?S.forEach((_,E)=>{v.fillStyle=Fr=E,v.fill(_)}):S!=null&&m&&v.fill(S)}function Uo(m,S,_,E){let D=V[m],I;if(E<=0)I=[0,0];else{let Q=D._space=D.space(i,m,S,_,E),ne=D._incrs=D.incrs(i,m,S,_,E,Q);I=T1(S,_,ne,E,Q)}return D._found=I}function Vs(m,S,_,E,D,I,Q,ne,se,de){let me=Q%2/2;C==1&&v.translate(me,me),Bo(ne,Q,se,de,ne),v.beginPath();let ve,Ae,Ke,Ve,He=D+(E==0||E==3?-I:I);_==0?(Ae=D,Ve=He):(ve=D,Ke=He);for(let Me=0;Me{if(!_.show)return;let D=z[_.scale];if(D.min==null){_._show&&(S=!1,_._show=!1,di(!1));return}else _._show||(S=!1,_._show=!0,di(!1));let I=_.side,Q=I%2,{min:ne,max:se}=D,[de,me]=Uo(E,ne,se,Q==0?Pe:pe);if(me==0)return;let ve=D.distr==2,Ae=_._splits=_.splits(i,E,ne,se,de,me,ve),Ke=D.distr==2?Ae.map(Re=>cr[Re]):Ae,Ve=D.distr==2?cr[Ae[1]]-cr[Ae[0]]:de,He=_._values=_.values(i,_.filter(i,Ke,E,me,Ve),E,me,Ve);_._rotate=I==2?_.rotate(i,He,E,me):0;let Me=_._size;_._size=jr(_.size(i,He,E,m)),Me!=null&&_._size!=Me&&(S=!1)}),S}function Vo(m){let S=!0;return Ho.forEach((_,E)=>{let D=_(i,E,jn,m);D!=Ir[E]&&(S=!1),Ir[E]=D}),S}function $o(){for(let m=0;mcr[xn]):Ke,He=me.distr==2?cr[Ke[1]]-cr[Ke[0]]:se,Me=S.ticks,Re=S.border,lt=Me.show?Me.size:0,wt=Zt(lt*Ze),Bt=Zt((S.alignTo==2?S._size-lt-S.gap:S.gap)*Ze),nt=S._rotate*-tu/180,St=L(S._pos*Ze),Wn=(wt+Bt)*ne,ut=St+Wn;I=E==0?ut:0,D=E==1?ut:0;let fn=S.font[0],Zn=S.align==1?yl:S.align==2?df:nt>0?yl:nt<0?df:E==0?"center":_==3?df:yl,mr=nt||E==1?"middle":_==2?vo:Dp;cs(fn,Q,Zn,mr);let Mn=S.font[1]*S.lineGap,Bn=Ke.map(xn=>L(d(xn,me,ve,Ae))),Un=S._values;for(let xn=0;xn{_>0&&(S._paths=null,m&&(o==1?(S.min=null,S.max=null):S.facets.forEach(E=>{E.min=null,E.max=null})))})}let Pi=!1,Ai=!1,Xn=[];function ti(){Ai=!1;for(let m=0;m0&&queueMicrotask(ti)}i.batch=fs;function ji(){if(Jr&&(Bs(),Jr=!1),or&&(ur(),or=!1),Zr){if(vt(T,yl,Je),vt(T,vo,tt),vt(T,So,Pe),vt(T,xo,pe),vt(N,yl,Je),vt(N,vo,tt),vt(N,So,Pe),vt(N,xo,pe),vt(x,So,sn),vt(x,xo,lr),y.width=Zt(sn*Ze),y.height=Zt(lr*Ze),V.forEach(({_el:m,_show:S,_size:_,_pos:E,side:D})=>{if(m!=null)if(S){let I=D===3||D===0?_:0,Q=D%2==1;vt(m,Q?"left":"top",E-I),vt(m,Q?"width":"height",_),vt(m,Q?"top":"left",Q?tt:Je),vt(m,Q?"height":"width",Q?pe:Pe),Mf(m,Ls)}else Sr(m,Ls)}),us=Fr=Pl=Mi=qn=Al=In=jl=Ws=null,bn=1,ys(!0),Je!=ln||tt!=Rn||Pe!=Kt||pe!=Nt){di(!1);let m=Pe/Kt,S=pe/Nt;if(be&&!ar&&X.left>=0){X.left*=m,X.top*=S,Rr&&ai(Rr,Zt(X.left),0,Pe,pe),Ii&&ai(Ii,0,Zt(X.top),Pe,pe);for(let _=0;_=0&&st.width>0){st.left*=m,st.width*=m,st.top*=S,st.height*=S;for(let _ in Kl)vt(pi,_,st[_])}ln=Je,Rn=tt,Kt=Pe,Nt=pe}Wt("setSize"),Zr=!1}sn>0&&lr>0&&(v.clearRect(0,0,y.width,y.height),Wt("drawClear"),Z.forEach(m=>m()),Wt("draw")),st.show&&ei&&(fr(st),ei=!1),be&&ar&&(mi(null,!0,!1),ar=!1),W.show&&W.live&&Dt&&(vs(),Dt=!1),p||(p=!0,i.status=1,Wt("ready")),at=!1,Pi=!1}i.redraw=(m,S)=>{or=S||!1,m!==!1?dr(G,q.min,q.max):Fn()};function kr(m,S){let _=z[m];if(_.from==null){if(Ht==0){let E=_.range(i,S.min,S.max,m);S.min=E[0],S.max=E[1]}if(S.min>S.max){let E=S.min;S.min=S.max,S.max=E}if(Ht>1&&S.min!=null&&S.max!=null&&S.max-S.min<1e-16)return;m==G&&_.distr==2&&Ht>0&&(S.min=Kr(S.min,t[0]),S.max=Kr(S.max,t[0]),S.min==S.max&&S.max++),ye[m]=S,Jr=!0,Fn()}}i.setScale=kr;let Bl,$s,Rr,Ii,Ul,ds,hi,Fi,Hi,Wi,Qe,it,ni=!1;const qt=X.drag;let bt=qt.x,Ct=qt.y;be&&(X.x&&(Bl=Pr(G0,N)),X.y&&($s=Pr(K0,N)),q.ori==0?(Rr=Bl,Ii=$s):(Rr=$s,Ii=Bl),Qe=X.left,it=X.top);const st=i.select=$t({show:!0,over:!0,left:0,width:0,top:0,height:0},l.select),pi=st.show?Pr($0,st.over?N:T):null;function fr(m,S){if(st.show){for(let _ in m)st[_]=m[_],_ in Kl&&vt(pi,_,m[_]);S!==!1&&Wt("setSelect")}}i.setSelect=fr;function Vl(m){if(A[m].show)ee&&Mf(Oe[m],Ls);else if(ee&&Sr(Oe[m],Ls),be){let _=cn?yt[0]:yt[m];_!=null&&ai(_,-10,-10,Pe,pe)}}function dr(m,S,_){kr(m,{min:S,max:_})}function hr(m,S,_,E){S.focus!=null&&$l(m),S.show!=null&&A.forEach((D,I)=>{I>0&&(m==I||m==null)&&(D.show=S.show,Vl(I),o==2?(dr(D.facets[0].scale,null,null),dr(D.facets[1].scale,null,null)):dr(D.scale,null,null),Fn())}),_!==!1&&Wt("setSeries",m,S),E&&ws("setSeries",i,m,S)}i.setSeries=hr;function Gs(m,S){$t($[m],S)}function Ks(m,S){m.fill=$e(m.fill||null),m.dir=Xe(m.dir,-1),S=S??$.length,$.splice(S,0,m)}function Go(m){m==null?$.length=0:$.splice(m,1)}i.addBand=Ks,i.setBand=Gs,i.delBand=Go;function Hn(m,S){A[m].alpha=S,be&&yt[m]!=null&&(yt[m].style.opacity=S),ee&&Oe[m]&&(Oe[m].style.opacity=S)}let Tn,Nr,pr;const Bi={focus:!0};function $l(m){if(m!=pr){let S=m==null,_=_t.alpha!=1;A.forEach((E,D)=>{if(o==1||D>0){let I=S||D==0||D==m;E._focus=S?null:I,_&&Hn(D,I?1:_t.alpha)}}),pr=m,_&&Fn()}}ee&&Et&&et(Pp,Ee,m=>{X._lock||(wn(m),pr!=null&&hr(null,Bi,!0,Tt.setSeries))});function Jn(m,S,_){let E=z[S];_&&(m=m/Ze-(E.ori==1?tt:Je));let D=Pe;E.ori==1&&(D=pe,m=D-m),E.dir==-1&&(m=D-m);let I=E._min,Q=E._max,ne=m/D,se=I+(Q-I)*ne,de=E.distr;return de==3?kl(10,se):de==4?cw(se,E.asinh):de==100?E.bwd(se):se}function hs(m,S){let _=Jn(m,G,S);return Kr(_,t[0],zt,Ot)}i.valToIdx=m=>Kr(m,t[0]),i.posToIdx=hs,i.posToVal=Jn,i.valToPos=(m,S,_)=>z[S].ori==0?u(m,z[S],_?Yt:Pe,_?on:0):c(m,z[S],_?vn:pe,_?gn:0),i.setCursor=(m,S,_)=>{Qe=m.left,it=m.top,mi(null,S,_)};function ps(m,S){vt(pi,yl,st.left=m),vt(pi,So,st.width=S)}function Gl(m,S){vt(pi,vo,st.top=m),vt(pi,xo,st.height=S)}let ms=q.ori==0?ps:Gl,gs=q.ori==1?ps:Gl;function Fu(){if(ee&&W.live)for(let m=o==2?1:0;m{M[E]=_}):pw(m.idx)||M.fill(m.idx),W.idx=M[0]),ee&&W.live){for(let _=0;_0||o==1&&!jt)&&Hu(_,M[_]);Fu()}Dt=!1,S!==!1&&Wt("setLegend")}i.setLegend=vs;function Hu(m,S){let _=A[m],E=m==0&&xe==2?cr:t[m],D;jt?D=_.values(i,m,S)??It:(D=_.value(i,S==null?null:E[S],m,S),D=D==null?It:{_:D}),W.values[m]=D}function mi(m,S,_){Hi=Qe,Wi=it,[Qe,it]=X.move(i,Qe,it),X.left=Qe,X.top=it,be&&(Rr&&ai(Rr,Zt(Qe),0,Pe,pe),Ii&&ai(Ii,0,Zt(it),Pe,pe));let E,D=zt>Ot;Tn=ft,Nr=null;let I=q.ori==0?Pe:pe,Q=q.ori==1?Pe:pe;if(Qe<0||Ht==0||D){E=X.idx=null;for(let ne=0;ne0&<.show){let Wn=nt==null?-10:nt==E?de:ce(o==1?t[0][nt]:t[Re][0][nt],q,I,0),ut=St==null?-10:ge(St,o==1?z[lt.scale]:z[lt.facets[1].scale],Q,0);if(Et&&St!=null){let fn=q.ori==1?Qe:it,Zn=en(_t.dist(i,Re,nt,ut,fn));if(Zn=0?1:-1,Un=Mn>=0?1:-1;Un==Bn&&(Un==1?mr==1?St>=Mn:St<=Mn:mr==1?St<=Mn:St>=Mn)&&(Tn=Zn,Nr=Re)}else Tn=Zn,Nr=Re}}if(Dt||cn){let fn,Zn;q.ori==0?(fn=Wn,Zn=ut):(fn=ut,Zn=Wn);let mr,Mn,Bn,Un,br,xn,Ut=!0,Hr=Be.bbox;if(Hr!=null){Ut=!1;let Lt=Hr(i,Re);Bn=Lt.left,Un=Lt.top,mr=Lt.width,Mn=Lt.height}else Bn=fn,Un=Zn,mr=Mn=Be.size(i,Re);if(xn=Be.fill(i,Re),br=Be.stroke(i,Re),cn)Re==Nr&&Tn<=_t.prox&&(me=Bn,ve=Un,Ae=mr,Ke=Mn,Ve=Ut,He=xn,Me=br);else{let Lt=yt[Re];Lt!=null&&(Sn[Re]=Bn,Ft[Re]=Un,Bp(Lt,mr,Mn,Ut),Hp(Lt,xn,br),ai(Lt,jr(Bn),jr(Un),Pe,pe))}}}}if(cn){let Re=_t.prox,lt=pr==null?Tn<=Re:Tn>Re||Nr!=pr;if(Dt||lt){let wt=yt[0];wt!=null&&(Sn[0]=me,Ft[0]=ve,Bp(wt,Ae,Ke,Ve),Hp(wt,He,Me),ai(wt,jr(me),jr(ve),Pe,pe))}}}if(st.show&&ni)if(m!=null){let[ne,se]=Tt.scales,[de,me]=Tt.match,[ve,Ae]=m.cursor.sync.scales,Ke=m.cursor.drag;if(bt=Ke._x,Ct=Ke._y,bt||Ct){let{left:Ve,top:He,width:Me,height:Re}=m.select,lt=m.scales[ve].ori,wt=m.posToVal,Bt,nt,St,Wn,ut,fn=ne!=null&&de(ne,ve),Zn=se!=null&&me(se,Ae);fn&&bt?(lt==0?(Bt=Ve,nt=Me):(Bt=He,nt=Re),St=z[ne],Wn=ce(wt(Bt,ve),St,I,0),ut=ce(wt(Bt+nt,ve),St,I,0),ms(Yr(Wn,ut),en(ut-Wn))):ms(0,I),Zn&&Ct?(lt==1?(Bt=Ve,nt=Me):(Bt=He,nt=Re),St=z[se],Wn=ge(wt(Bt,Ae),St,Q,0),ut=ge(wt(Bt+nt,Ae),St,Q,0),gs(Yr(Wn,ut),en(ut-Wn))):gs(0,Q)}else Yl()}else{let ne=en(Hi-Ul),se=en(Wi-ds);if(q.ori==1){let Ae=ne;ne=se,se=Ae}bt=qt.x&&ne>=qt.dist,Ct=qt.y&&se>=qt.dist;let de=qt.uni;de!=null?bt&&Ct&&(bt=ne>=de,Ct=se>=de,!bt&&!Ct&&(se>ne?Ct=!0:bt=!0)):qt.x&&qt.y&&(bt||Ct)&&(bt=Ct=!0);let me,ve;bt&&(q.ori==0?(me=hi,ve=Qe):(me=Fi,ve=it),ms(Yr(me,ve),en(ve-me)),Ct||gs(0,Q)),Ct&&(q.ori==1?(me=hi,ve=Qe):(me=Fi,ve=it),gs(Yr(me,ve),en(ve-me)),bt||ms(0,I)),!bt&&!Ct&&(ms(0,0),gs(0,0))}if(qt._x=bt,qt._y=Ct,m==null){if(_){if(Xs!=null){let[ne,se]=Tt.scales;Tt.values[0]=ne!=null?Jn(q.ori==0?Qe:it,ne):null,Tt.values[1]=se!=null?Jn(q.ori==1?Qe:it,se):null}ws(hf,i,Qe,it,Pe,pe,E)}if(Et){let ne=_&&Tt.setSeries,se=_t.prox;pr==null?Tn<=se&&hr(Nr,Bi,!0,ne):Tn>se?hr(null,Bi,!0,ne):Nr!=pr&&hr(Nr,Bi,!0,ne)}}Dt&&(W.idx=E,vs()),S!==!1&&Wt("setCursor")}let ri=null;Object.defineProperty(i,"rect",{get(){return ri==null&&ys(!1),ri}});function ys(m=!1){m?ri=null:(ri=N.getBoundingClientRect(),Wt("syncRect",ri))}function Ko(m,S,_,E,D,I,Q){X._lock||ni&&m!=null&&m.movementX==0&&m.movementY==0||(Ys(m,S,_,E,D,I,Q,!1,m!=null),m!=null?mi(null,!0,!0):mi(S,!0,!1))}function Ys(m,S,_,E,D,I,Q,ne,se){if(ri==null&&ys(!1),wn(m),m!=null)_=m.clientX-ri.left,E=m.clientY-ri.top;else{if(_<0||E<0){Qe=-10,it=-10;return}let[de,me]=Tt.scales,ve=S.cursor.sync,[Ae,Ke]=ve.values,[Ve,He]=ve.scales,[Me,Re]=Tt.match,lt=S.axes[0].side%2==1,wt=q.ori==0?Pe:pe,Bt=q.ori==1?Pe:pe,nt=lt?I:D,St=lt?D:I,Wn=lt?E:_,ut=lt?_:E;if(Ve!=null?_=Me(de,Ve)?d(Ae,z[de],wt,0):-10:_=wt*(Wn/nt),He!=null?E=Re(me,He)?d(Ke,z[me],Bt,0):-10:E=Bt*(ut/St),q.ori==1){let fn=_;_=E,E=fn}}se&&(S==null||S.cursor.event.type==hf)&&((_<=1||_>=Pe-1)&&(_=zs(_,Pe)),(E<=1||E>=pe-1)&&(E=zs(E,pe))),ne?(Ul=_,ds=E,[hi,Fi]=X.move(i,_,E)):(Qe=_,it=E)}const Kl={width:0,height:0,left:0,top:0};function Yl(){fr(Kl,!1)}let Yo,Qo,Qs,qo;function Xo(m,S,_,E,D,I,Q){ni=!0,bt=Ct=qt._x=qt._y=!1,Ys(m,S,_,E,D,I,Q,!0,!1),m!=null&&(et(pf,bf,Jo,!1),ws(Op,i,hi,Fi,Pe,pe,null));let{left:ne,top:se,width:de,height:me}=st;Yo=ne,Qo=se,Qs=de,qo=me}function Jo(m,S,_,E,D,I,Q){ni=qt._x=qt._y=!1,Ys(m,S,_,E,D,I,Q,!1,!0);let{left:ne,top:se,width:de,height:me}=st,ve=de>0||me>0,Ae=Yo!=ne||Qo!=se||Qs!=de||qo!=me;if(ve&&Ae&&fr(st),qt.setScale&&ve&&Ae){let Ke=ne,Ve=de,He=se,Me=me;if(q.ori==1&&(Ke=se,Ve=me,He=ne,Me=de),bt&&dr(G,Jn(Ke,G),Jn(Ke+Ve,G)),Ct)for(let Re in z){let lt=z[Re];Re!=G&<.from==null&<.min!=ft&&dr(Re,Jn(He+Me,Re),Jn(He,Re))}Yl()}else X.lock&&(X._lock=!X._lock,mi(S,!0,m!=null));m!=null&&(rn(pf,bf),ws(pf,i,Qe,it,Pe,pe,null))}function Zo(m,S,_,E,D,I,Q){if(X._lock)return;wn(m);let ne=ni;if(ni){let se=!0,de=!0,me=10,ve,Ae;q.ori==0?(ve=bt,Ae=Ct):(ve=Ct,Ae=bt),ve&&Ae&&(se=Qe<=me||Qe>=Pe-me,de=it<=me||it>=pe-me),ve&&se&&(Qe=Qe{let D=Tt.match[2];_=D(i,S,_),_!=-1&&hr(_,E,!0,!1)},be&&(et(Op,N,Xo),et(hf,N,Ko),et(Lp,N,m=>{wn(m),ys(!1)}),et(Pp,N,Zo),et(Ap,N,ea),Af.add(i),i.syncRect=ys);const qs=i.hooks=l.hooks||{};function Wt(m,S,_){Ai?Xn.push([m,S,_]):m in qs&&qs[m].forEach(E=>{E.call(null,i,S,_)})}(l.plugins||[]).forEach(m=>{for(let S in m.hooks)qs[S]=(qs[S]||[]).concat(m.hooks[S])});const na=(m,S,_)=>_,Tt=$t({key:null,setSeries:!1,filters:{pub:Kp,sub:Kp},scales:[G,A[1]?A[1].scale:null],match:[Yp,Yp,na],values:[null,null]},X.sync);Tt.match.length==2&&Tt.match.push(na),X.sync=Tt;const Xs=Tt.key,gi=Ng(Xs);function ws(m,S,_,E,D,I,Q){Tt.filters.pub(m,S,_,E,D,I,Q)&&gi.pub(m,S,_,E,D,I,Q)}gi.sub(i);function ra(m,S,_,E,D,I,Q){Tt.filters.sub(m,S,_,E,D,I,Q)&&Ui[m](null,S,_,E,D,I,Q)}i.pub=ra;function ia(){gi.unsub(i),Af.delete(i),An.clear(),Df(du,El,ta),g.remove(),Ee==null||Ee.remove(),Wt("destroy")}i.destroy=ia;function Js(){Wt("init",l,t),Wo(t||l.data,!1),ye[G]?kr(G,ye[G]):as(),ei=st.show&&(st.width>0||st.height>0),ar=Dt=!0,ot(l.width,l.height)}return A.forEach(bi),V.forEach(Fo),r?r instanceof HTMLElement?(r.appendChild(g),Js()):r(i,Js):Js(),i}Pn.assign=$t;Pn.fmtNum=td;Pn.rangeNum=hu;Pn.rangeLog=Nu;Pn.rangeAsinh=Zf;Pn.orient=Fs;Pn.pxRatio=Ze;Pn.join=xw;Pn.fmtDate=rd,Pn.tzDate=zw;Pn.sync=Ng;{Pn.addGap=p1,Pn.clipGaps=Mu;let l=Pn.paths={points:Og};l.linear=Pg,l.stepped=v1,l.bars=y1,l.spline=S1}const D1=6e3;class z1{constructor(t=D1){mo(this,"t");mo(this,"v");mo(this,"len",0);mo(this,"head",0);this.t=new Float64Array(t),this.v=new Float64Array(t)}push(t,r){const i=this.t.length;this.t[this.head]=t,this.v[this.head]=r,this.head=(this.head+1)%i,this.len=t&&(u[d]=this.t[g],c[d]=this.v[g],d++)}return{t:u.subarray(0,d),v:c.subarray(0,d)}}last(){if(this.len===0)return null;const t=this.t.length;return this.v[(this.head-1+t)%t]}}const If=new Map;function O1(l){let t=If.get(l);return t||(t=new z1,If.set(l,t)),t}function Fg(l,t){const r=O1(l);for(const[i,o]of t)r.push(i,o)}function Hg(l,t=-1/0){const r=If.get(l);return r?r.read(t):{t:new Float64Array(0),v:new Float64Array(0)}}const Cl=new Map;let nu=[];function Wg(){nu.forEach(l=>l())}function L1(l){Cl.set(l,(Cl.get(l)||0)+1),Wg()}function P1(l){const t=(Cl.get(l)||0)-1;t<=0?Cl.delete(l):Cl.set(l,t),Wg()}function A1(){return Array.from(Cl.keys())}function j1(l){return nu.push(l),()=>{nu=nu.filter(t=>t!==l)}}const gm=3e3;let xl=[],ru=[];function I1(l){l.length&&(xl=xl.concat(l),xl.length>gm&&(xl=xl.slice(-gm)),ru.forEach(t=>t()))}function F1(){return xl}function H1(l){return ru.push(l),()=>{ru=ru.filter(t=>t!==l)}}let iu=0,su=[];function vm(l){iu+=l?1:-1,iu<0&&(iu=0),su.forEach(t=>t())}function W1(){return iu>0}function B1(l){return su.push(l),()=>{su=su.filter(t=>t!==l)}}let js=null,wf=null;function U1(){return`${location.protocol==="https:"?"wss":"ws"}://${location.host}/api/monitor/stream`}function ym(){js&&js.readyState===WebSocket.OPEN&&js.send(JSON.stringify({type:"subscribe",signals:A1()}))}function wm(){js&&js.readyState===WebSocket.OPEN&&js.send(JSON.stringify({type:"raw",enabled:W1()}))}function Bg(){const l=new WebSocket(U1());js=l,l.onopen=()=>{We.getState().setConnected(!0),ym(),wm()},l.onclose=()=>{We.getState().setConnected(!1),wf==null&&(wf=window.setTimeout(()=>{wf=null,Bg()},1e3))},l.onerror=()=>l.close(),l.onmessage=r=>{let i;try{i=JSON.parse(r.data)}catch{return}const o=We.getState();switch(i.type){case"meta":o.setMeta(i.signals,i.pairs),o.setMotors(i.motors);break;case"motors":o.setMotors(i.motors),i.status&&o.setStatus(i.status);break;case"samples":for(const[u,c]of Object.entries(i.data))Fg(u,c);break;case"raw":I1(i.frames);break}};let t=null;j1(()=>{t==null&&(t=window.setTimeout(()=>{t=null,ym()},80))}),B1(wm)}async function V1(l,t=600){return l.length?(await fetch(`/api/monitor/snapshot?signals=${l.join(",")}&n=${t}`)).json():{}}async function $1(){try{return(await(await fetch("/api/monitor/motor-types")).json()).types||[]}catch{return[]}}async function G1(l,t){await fetch("/api/monitor/motor-type",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({motorId:l,motorType:t})})}const K1={pos:"#58a6ff",vel:"#3fb950",torque:"#d29922",kp:"#bc8cff",kd:"#f778ba",vel_limit:"#56d4dd",torque_limit:"#e3b341",t_mos:"#ff7b72",t_rotor:"#ffa657",status_code:"#8b949e"};function Ug(l){return K1[l]||"#8b949e"}function Y1(l){const t=Ug(l.field);return l.source==="cmd"?X1(t,.15):t}function Q1(l,t){const r=l.replace("#",""),i=parseInt(r.slice(0,2),16),o=parseInt(r.slice(2,4),16),u=parseInt(r.slice(4,6),16);return`rgba(${i},${o},${u},${t})`}function Sm(l){const t=Ug(l.field);return l.source==="cmd"?{stroke:Q1(t,.45),width:1.25}:{stroke:t,width:1.85}}function Ff(l){const t=l.split(":");return t.length>=3?`${t[1]} ${t[2]}`:l}function q1(l){return l.includes(":cmd.")}const xm=["pos","vel","torque","kp","kd","t_mos","t_rotor"];function X1(l,t){const r=l.replace("#",""),i=Math.min(255,Math.round(parseInt(r.slice(0,2),16)+255*t)),o=Math.min(255,Math.round(parseInt(r.slice(2,4),16)+255*t)),u=Math.min(255,Math.round(parseInt(r.slice(4,6),16)+255*t));return`rgb(${i},${o},${u})`}function Tl(l,t=3){return l==null||Number.isNaN(l)?"—":l.toFixed(t)}const _m=2e3;function J1(l,t){const r=l.map(c=>Hg(c,t)),i=new Set;for(const c of r)for(let d=0;dc-d);if(o.length>_m){const c=Math.ceil(o.length/_m);o=o.filter((d,p)=>p%c===0)}const u=[o];for(const c of r){const d=new Array(o.length).fill(null);let p=0,g=null;for(let y=0;yC.ensurePlot),r=We(C=>C.removeSignalFromPlot),i=We(C=>C.setPlotConfig),o=We(C=>C.plotConfigs[l]),u=We(C=>C.signals);P.useEffect(()=>{t(l)},[l,t]);const c=(o==null?void 0:o.signals)??[],d=(o==null?void 0:o.duration)??10,p=c.join("|"),{setNodeRef:g,isOver:y}=g0({id:`plot:${l}`,data:{panelId:l}}),v=P.useRef(null),x=P.useRef(null),T=P.useRef(0);P.useEffect(()=>{if(!v.current)return;const C=v.current,L=new Map(u.map($=>[$.id,$])),U=[{label:"t"},...c.map($=>{const G=L.get($),Y=G?Sm(G):{stroke:"#8b949e",width:1.5};return{label:Ff($),stroke:Y.stroke,width:Y.width,points:{show:!1}}})],A={width:C.clientWidth||400,height:C.clientHeight||220,legend:{show:!1},series:U,cursor:{y:!1,points:{show:!0}},scales:{x:{time:!1}},axes:[{stroke:"#8b949e",grid:{stroke:"rgba(139,148,158,0.12)"},ticks:{stroke:"rgba(139,148,158,0.2)"},values:($,G)=>G.map(Y=>(Y-T.current).toFixed(1)+"s")},{stroke:"#8b949e",grid:{stroke:"rgba(139,148,158,0.12)"},ticks:{stroke:"rgba(139,148,158,0.2)"}}]},V=new Pn(A,[[],...c.map(()=>[])],C);x.current=V;const z=new ResizeObserver(()=>{V.setSize({width:C.clientWidth,height:C.clientHeight})});return z.observe(C),()=>{z.disconnect(),V.destroy(),x.current=null}},[p,u.length]),P.useEffect(()=>{if(!c.length)return;c.forEach(L1);let C=!1;return V1(c,1200).then(L=>{if(!C)for(const[U,A]of Object.entries(L))Fg(U,A)}),()=>{C=!0,c.forEach(P1)}},[p]),P.useEffect(()=>{let C=0;const L=()=>{const U=x.current;if(U&&c.length){let A=0;for(const z of c){const $=Hg(z);$.t.length&&(A=Math.max(A,$.t[$.t.length-1]))}T.current=A;const V=J1(c,A-d);U.setData(V,!1),U.setScale("x",{min:A-d,max:A})}C=requestAnimationFrame(L)};return C=requestAnimationFrame(L),()=>cancelAnimationFrame(C)},[p,d]);const N=P.useMemo(()=>new Map(u.map(C=>[C.id,C])),[u]);return R.jsxs("div",{className:"panel plot-panel",ref:g,children:[R.jsxs("div",{className:"plot-toolbar",children:[R.jsx("span",{className:"muted",children:"window"}),R.jsx("select",{value:d,onChange:C=>i(l,{duration:Number(C.target.value)}),children:[5,10,20,30,60].map(C=>R.jsxs("option",{value:C,children:[C,"s"]},C))}),R.jsx("div",{className:"legend",children:c.map(C=>{const L=N.get(C),U=L?Sm(L):{stroke:"#555"};return R.jsxs("span",{className:"legend-chip",children:[R.jsx("span",{className:"legend-swatch",style:{background:U.stroke,opacity:q1(C)?.9:1}}),Ff(C),R.jsx("button",{className:"legend-x",onClick:()=>r(l,C),children:"×"})]},C)})})]}),R.jsx("div",{className:"plot-host"+(y?" drop-over":""),ref:v,children:c.length===0&&R.jsx("div",{className:"drop-hint",children:"Drag signals here to plot — drop cmd onto fb to overlay"})})]})}const Sf=[["pos","cmd p"],["vel","cmd v"],["kp","kp"],["kd","kd"],["torque","cmd τ"]],xf=[["pos","act p"],["vel","act v"],["torque","act τ"],["t_mos","Tmos"],["t_rotor","Trot"]];function eS(){const l=We(t=>t.motors);return R.jsx("div",{className:"panel table-panel",children:R.jsxs("table",{className:"motor-table",children:[R.jsx("thead",{children:R.jsxs("tr",{children:[R.jsx("th",{children:"Motor"}),R.jsx("th",{children:"Mode"}),R.jsx("th",{children:"Status"}),Sf.map(([t,r])=>R.jsx("th",{className:"cmd-col",children:r},"c"+t)),xf.map(([t,r])=>R.jsx("th",{children:r},"f"+t))]})}),R.jsxs("tbody",{children:[l.length===0&&R.jsx("tr",{children:R.jsx("td",{colSpan:3+Sf.length+xf.length,className:"muted center",children:"Waiting for traffic…"})}),l.map(t=>R.jsxs("tr",{children:[R.jsxs("td",{className:"mono",children:["m",t.motorId]}),R.jsx("td",{className:"muted",children:t.mode||"—"}),R.jsx("td",{children:R.jsx("span",{className:"status-pill "+(t.status==="ENABLED"?"ok":t.status==="DISABLED"?"off":"warn"),children:t.status||"—"})}),Sf.map(([r])=>R.jsx("td",{className:"mono cmd-col",children:Tl(t.cmd[r],r==="kp"?0:3)},"c"+r)),xf.map(([r])=>R.jsx("td",{className:"mono",children:Tl(t.fb[r],r.startsWith("t_")?1:3)},"f"+r))]},`${t.bus}:${t.motorId}`))]})]})})}function _f({label:l,cmd:t,act:r,unit:i,digits:o=2}){return R.jsxs("div",{className:"metric",children:[R.jsxs("div",{className:"metric-label",children:[l," ",R.jsx("span",{className:"muted",children:i})]}),R.jsxs("div",{className:"metric-values",children:[R.jsx("span",{className:"metric-act",children:Tl(r,o)}),t!==void 0&&R.jsxs("span",{className:"metric-cmd",children:["⌖ ",Tl(t,o)]})]})]})}function tS(){const l=We(r=>r.motors),t=We(r=>r.motorTypes);return R.jsxs("div",{className:"panel cards-panel",children:[l.length===0&&R.jsx("div",{className:"muted center pad",children:"Waiting for traffic…"}),R.jsx("div",{className:"cards-grid",children:l.map(r=>R.jsxs("div",{className:"motor-card",children:[R.jsxs("div",{className:"motor-card-head",children:[R.jsxs("span",{className:"mono strong",children:["Motor ",r.motorId]}),R.jsx("span",{className:"status-pill "+(r.status==="ENABLED"?"ok":r.status==="DISABLED"?"off":"warn"),children:r.status||"—"})]}),R.jsxs("div",{className:"motor-card-sub",children:[R.jsx("span",{className:"muted",children:r.mode||"—"}),t.length>0&&R.jsxs("select",{className:"type-select",defaultValue:"",onChange:i=>i.target.value&&G1(r.motorId,i.target.value),title:"Override motor type used to scale this motor's values",children:[R.jsx("option",{value:"",children:"set type…"}),t.map(i=>R.jsx("option",{value:i,children:i},i))]})]}),R.jsx(_f,{label:"Position",unit:"rad",cmd:r.cmd.pos,act:r.fb.pos,digits:3}),R.jsx(_f,{label:"Velocity",unit:"rad/s",cmd:r.cmd.vel,act:r.fb.vel,digits:2}),R.jsx(_f,{label:"Torque",unit:"Nm",cmd:r.cmd.torque,act:r.fb.torque,digits:2}),R.jsxs("div",{className:"temp-row",children:[R.jsxs("span",{children:["MOS ",Tl(r.fb.t_mos,1),"°"]}),R.jsxs("span",{children:["Rotor ",Tl(r.fb.t_rotor,1),"°"]})]})]},`${r.bus}:${r.motorId}`))})]})}function nS(l,t,r){const i=new Array(l);return new Proxy(i,{get(o,u,c){if(typeof u=="string"){const d=u.charCodeAt(0);if(d>=48&&d<=57){const p=+u;if(Number.isInteger(p)&&p>=0&&pi[y]!==g))&&(i=d,o=t(...d),r!=null&&r.onChange&&!(u&&r.skipInitialOnChange)&&r.onChange(o),u=!1),o}return c.updateDeps=d=>{i=d},c}function Em(l,t){if(l===void 0)throw new Error("Unexpected undefined");return l}const rS=(l,t)=>Math.abs(l-t)<1.01,iS=(l,t,r)=>{let i;return function(...o){l.clearTimeout(i),i=l.setTimeout(()=>t.apply(this,o),r)}};let wo;const Ef=()=>{if(wo!==void 0)return wo;if(typeof navigator>"u")return wo=!1;if(/iP(hone|od|ad)/.test(navigator.userAgent))return wo=!0;const l=navigator.maxTouchPoints;return wo=navigator.platform==="MacIntel"&&l!==void 0&&l>0},Cm=l=>{const{offsetWidth:t,offsetHeight:r}=l;return{width:t,height:r}},sS=l=>l,lS=l=>{const t=Math.max(l.startIndex-l.overscan,0),i=Math.min(l.endIndex+l.overscan,l.count-1)-t+1,o=new Array(i);for(let u=0;u{const r=l.scrollElement;if(!r)return;const i=l.targetWindow;if(!i)return;const o=c=>{const{width:d,height:p}=c;t({width:Math.round(d),height:Math.round(p)})};if(o(Cm(r)),!i.ResizeObserver)return()=>{};const u=new i.ResizeObserver(c=>{const d=()=>{const p=c[0];if(p!=null&&p.borderBoxSize){const g=p.borderBoxSize[0];if(g){o({width:g.inlineSize,height:g.blockSize});return}}o(Cm(r))};l.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(d):d()});return u.observe(r,{box:"border-box"}),()=>{u.unobserve(r)}},mu={passive:!0},aS=typeof window>"u"?!0:"onscrollend"in window,uS=(l,t,r)=>{const i=l.scrollElement;if(!i)return;const o=l.targetWindow;if(!o)return;const u=l.options.useScrollendEvent&&aS;let c=0;const d=u?null:iS(o,()=>t(c,!1),l.options.isScrollingResetDelay),p=v=>()=>{c=r(i),d==null||d(),t(c,v)},g=p(!0),y=p(!1);return i.addEventListener("scroll",g,mu),u&&i.addEventListener("scrollend",y,mu),()=>{i.removeEventListener("scroll",g),u&&i.removeEventListener("scrollend",y)}},cS=(l,t)=>uS(l,t,r=>{const{horizontal:i,isRtl:o}=l.options;return i?r.scrollLeft*(o&&-1||1):r.scrollTop}),fS=(l,t,r)=>{if(r.options.useCachedMeasurements){const i=r.indexFromElement(l),o=r.options.getItemKey(i);return r.itemSizeCache.get(o)??r.options.estimateSize(i)}if(t!=null&&t.borderBoxSize){const i=t.borderBoxSize[0];if(i)return Math.round(i[r.options.horizontal?"inlineSize":"blockSize"])}if(!t){const i=r.indexFromElement(l),o=r.options.getItemKey(i),u=r.itemSizeCache.get(o);if(u!==void 0)return u}return l[r.options.horizontal?"offsetWidth":"offsetHeight"]},dS=(l,{adjustments:t=0,behavior:r},i)=>{var o,u;(u=(o=i.scrollElement)==null?void 0:o.scrollTo)==null||u.call(o,{[i.options.horizontal?"left":"top"]:l+t,behavior:r})},hS=dS;class pS{constructor(t){this.unsubs=[],this.scrollElement=null,this.targetWindow=null,this.isScrolling=!1,this.scrollState=null,this.measurementsCache=[],this._flatMeasurements=null,this.itemSizeCache=new Map,this.itemSizeCacheVersion=0,this.laneAssignments=new Map,this.pendingMin=null,this.prevLanes=void 0,this.lanesChangedFlag=!1,this.lanesSettling=!1,this.pendingScrollAnchor=null,this.scrollRect=null,this.scrollOffset=null,this.scrollDirection=null,this.scrollAdjustments=0,this._iosDeferredAdjustment=0,this._iosTouching=!1,this._iosJustTouchEnded=!1,this._iosTouchEndTimerId=null,this._intendedScrollOffset=null,this.elementsCache=new Map,this.now=()=>{var r,i,o;return((o=(i=(r=this.targetWindow)==null?void 0:r.performance)==null?void 0:i.now)==null?void 0:o.call(i))??Date.now()},this.observer=(()=>{let r=null;const i=()=>r||(!this.targetWindow||!this.targetWindow.ResizeObserver?null:r=new this.targetWindow.ResizeObserver(o=>{o.forEach(u=>{const c=()=>{const d=u.target,p=this.indexFromElement(d);if(!d.isConnected){this.observer.unobserve(d);for(const[g,y]of this.elementsCache)if(y===d){this.elementsCache.delete(g);break}return}this.shouldMeasureDuringScroll(p)&&this.resizeItem(p,this.options.measureElement(d,u,this))};this.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(c):c()})}));return{disconnect:()=>{var o;(o=i())==null||o.disconnect(),r=null},observe:o=>{var u;return(u=i())==null?void 0:u.observe(o,{box:"border-box"})},unobserve:o=>{var u;return(u=i())==null?void 0:u.unobserve(o)}}})(),this.range=null,this.setOptions=r=>{var i,o;const u={debug:!1,initialOffset:0,overscan:1,paddingStart:0,paddingEnd:0,scrollPaddingStart:0,scrollPaddingEnd:0,horizontal:!1,getItemKey:sS,rangeExtractor:lS,onChange:()=>{},measureElement:fS,initialRect:{width:0,height:0},scrollMargin:0,gap:0,indexAttribute:"data-index",initialMeasurementsCache:[],lanes:1,anchorTo:"start",followOnAppend:!1,scrollEndThreshold:1,isScrollingResetDelay:150,enabled:!0,isRtl:!1,useScrollendEvent:!1,useAnimationFrameWithResizeObserver:!1,laneAssignmentMode:"estimate",useCachedMeasurements:!1};for(const x in r){const T=r[x];T!==void 0&&(u[x]=T)}const c=this.options;let d=null,p=null,g=!1;if(c!==void 0&&c.enabled&&u.enabled&&u.anchorTo==="end"&&this.scrollElement!==null){const x=c.count,T=u.count,N=this.getMeasurements(),C=x>0?((i=N[0])==null?void 0:i.key)??c.getItemKey(0):null,L=x>0?((o=N[x-1])==null?void 0:o.key)??c.getItemKey(x-1):null;if(T!==x||x>0&&T>0&&(u.getItemKey(0)!==C||u.getItemKey(T-1)!==L)){g=!0;const V=x>0?this.getVirtualItemForOffset(this.getScrollOffset())??N[0]:null;V&&(d=[V.key,this.getScrollOffset()-V.start]);const z=u.followOnAppend===!0?"auto":u.followOnAppend||null;z&&T>x&&this.isAtEnd(c.scrollEndThreshold)&&(x===0||u.getItemKey(T-1)!==L)&&(p=z)}}this.options=u,g&&(this.pendingMin=0,this.itemSizeCacheVersion++);let y=!1,v=0;if(d&&this.scrollOffset!==null){const[x,T]=d,N=this.getMeasurements(),{count:C,getItemKey:L}=this.options;let U=0;for(;U{var i,o;(o=(i=this.options).onChange)==null||o.call(i,this,r)},this.maybeNotify=wl(()=>(this.calculateRange(),[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]),r=>{this.notify(r)},{key:!1,debug:()=>this.options.debug,initialDeps:[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]}),this.cleanup=()=>{this.unsubs.filter(Boolean).forEach(r=>r()),this.unsubs=[],this.observer.disconnect(),this.rafId!=null&&this.targetWindow&&(this.targetWindow.cancelAnimationFrame(this.rafId),this.rafId=null),this.scrollState=null,this.scrollElement=null,this.targetWindow=null},this._didMount=()=>()=>{this.cleanup()},this._willUpdate=()=>{var r;const i=this.options.enabled?this.options.getScrollElement():null;if(this.scrollElement!==i){if(this.cleanup(),!i){this.maybeNotify();return}if(this.scrollElement=i,this.scrollElement&&"ownerDocument"in this.scrollElement?this.targetWindow=this.scrollElement.ownerDocument.defaultView:this.targetWindow=((r=this.scrollElement)==null?void 0:r.window)??null,this.elementsCache.forEach(u=>{this.observer.observe(u)}),this.unsubs.push(this.options.observeElementRect(this,u=>{this.scrollRect=u,this.maybeNotify()})),this.unsubs.push(this.options.observeElementOffset(this,(u,c)=>{this._intendedScrollOffset!==null&&Math.abs(u-this._intendedScrollOffset)<1.5&&(u=this._intendedScrollOffset),this._intendedScrollOffset=null,this.scrollAdjustments=0,this.scrollDirection=c?this.getScrollOffset(){this._iosTouching=!0,this._iosJustTouchEnded=!1,this._iosTouchEndTimerId!==null&&this.targetWindow!=null&&(this.targetWindow.clearTimeout(this._iosTouchEndTimerId),this._iosTouchEndTimerId=null)},d=()=>{this._iosTouching=!1,!(!Ef()||this.targetWindow==null)&&(this._iosJustTouchEnded=!0,this._iosTouchEndTimerId=this.targetWindow.setTimeout(()=>{this._iosJustTouchEnded=!1,this._iosTouchEndTimerId=null,this._flushIosDeferredIfReady()},150))};u.addEventListener("touchstart",c,mu),u.addEventListener("touchend",d,mu),this.unsubs.push(()=>{u.removeEventListener("touchstart",c),u.removeEventListener("touchend",d),this._iosTouchEndTimerId!==null&&this.targetWindow!=null&&(this.targetWindow.clearTimeout(this._iosTouchEndTimerId),this._iosTouchEndTimerId=null)})}this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0})}const o=this.pendingScrollAnchor;if(this.pendingScrollAnchor=null,o&&this.scrollElement&&this.options.enabled){const[u,c,d,p]=o;u!==null&&!d&&(Ef()&&(this.isScrolling||this._iosTouching||this._iosJustTouchEnded)?p!==0&&(this._iosDeferredAdjustment+=p):this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0})),d&&this.scrollToEnd({behavior:d})}},this._flushIosDeferredIfReady=()=>{if(this._iosDeferredAdjustment===0||this.isScrolling||this._iosTouching||this._iosJustTouchEnded)return;const r=this.getScrollOffset(),i=this.getMaxScrollOffset();if(r<0||r>i)return;const o=this._iosDeferredAdjustment;this._iosDeferredAdjustment=0,this._scrollToOffset(r,{adjustments:this.scrollAdjustments+=o,behavior:void 0})},this.rafId=null,this.getSize=()=>this.options.enabled?(this.scrollRect=this.scrollRect??this.options.initialRect,this.scrollRect[this.options.horizontal?"width":"height"]):(this.scrollRect=null,0),this.getScrollOffset=()=>this.options.enabled?(this.scrollOffset=this.scrollOffset??(typeof this.options.initialOffset=="function"?this.options.initialOffset():this.options.initialOffset),this.scrollOffset):(this.scrollOffset=null,0),this.getFurthestMeasurement=(r,i)=>{const o=new Map,u=new Map;for(let c=i-1;c>=0;c--){const d=r[c];if(o.has(d.lane))continue;const p=u.get(d.lane);if(p==null||d.end>p.end?u.set(d.lane,d):d.endc.end===d.end?c.index-d.index:c.end-d.end)[0]:void 0},this.getMeasurementOptions=wl(()=>[this.options.count,this.options.paddingStart,this.options.scrollMargin,this.options.getItemKey,this.options.enabled,this.options.lanes,this.options.laneAssignmentMode],(r,i,o,u,c,d,p)=>(this.prevLanes!==void 0&&this.prevLanes!==d&&(this.lanesChangedFlag=!0),this.prevLanes=d,this.pendingMin=null,{count:r,paddingStart:i,scrollMargin:o,getItemKey:u,enabled:c,lanes:d,laneAssignmentMode:p}),{key:!1}),this.getMeasurements=wl(()=>[this.getMeasurementOptions(),this.itemSizeCacheVersion],({count:r,paddingStart:i,scrollMargin:o,getItemKey:u,enabled:c,lanes:d,laneAssignmentMode:p},g)=>{const y=this.itemSizeCache;if(!c)return this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),[];if(this.laneAssignments.size>r)for(const N of this.laneAssignments.keys())N>=r&&this.laneAssignments.delete(N);this.lanesChangedFlag&&(this.lanesChangedFlag=!1,this.lanesSettling=!0,this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),this.pendingMin=null),this.measurementsCache.length===0&&!this.lanesSettling&&(this.measurementsCache=this.options.initialMeasurementsCache,this.measurementsCache.forEach(N=>{this.itemSizeCache.set(N.key,N.size)}));const v=this.lanesSettling?0:this.pendingMin??0;if(this.pendingMin=null,this.lanesSettling&&this.measurementsCache.length===r&&(this.lanesSettling=!1),d===1){const N=this.options.gap,C=r*2;let L=this._flatMeasurements;if(!L||L.length0&&V.set(L.subarray(0,v*2)),L=V,this._flatMeasurements=L}let U;if(v===0)U=i+o;else{const V=v-1;U=L[V*2]+L[V*2+1]+N}for(let V=v;V1){U=L;const Y=T[U],Z=Y!==void 0?x[Y]:void 0;A=Z?Z.end+this.options.gap:i+o}else{const Y=this.options.lanes===1?x[N-1]:this.getFurthestMeasurement(x,N);A=Y?Y.end+this.options.gap:i+o,U=Y?Y.lane:N%this.options.lanes,this.options.lanes>1&&V&&this.laneAssignments.set(N,U)}const z=y.get(C),$=typeof z=="number"?z:this.options.estimateSize(N),G=A+$;x[N]={index:N,start:A,size:$,end:G,key:C,lane:U},T[U]=N}return this.measurementsCache=x,x},{key:!1,debug:()=>this.options.debug}),this.calculateRange=wl(()=>[this.getMeasurements(),this.getSize(),this.getScrollOffset(),this.options.lanes],(r,i,o,u)=>this.range=r.length>0&&i>0?mS({measurements:r,outerSize:i,scrollOffset:o,lanes:u,flat:u===1&&this._flatMeasurements!=null?this._flatMeasurements:null}):null,{key:!1,debug:()=>this.options.debug}),this.getVirtualIndexes=wl(()=>{let r=null,i=null;const o=this.calculateRange();return o&&(r=o.startIndex,i=o.endIndex),this.maybeNotify.updateDeps([this.isScrolling,r,i]),[this.options.rangeExtractor,this.options.overscan,this.options.count,r,i]},(r,i,o,u,c)=>u===null||c===null?[]:r({startIndex:u,endIndex:c,overscan:i,count:o}),{key:!1,debug:()=>this.options.debug}),this.indexFromElement=r=>{const i=this.options.indexAttribute,o=r.getAttribute(i);return o?parseInt(o,10):(console.warn(`Missing attribute name '${i}={index}' on measured element.`),-1)},this.shouldMeasureDuringScroll=r=>{var i;if(!this.scrollState||this.scrollState.behavior!=="smooth")return!0;const o=this.scrollState.index??((i=this.getVirtualItemForOffset(this.scrollState.lastTargetOffset))==null?void 0:i.index);if(o!==void 0&&this.range){const u=Math.max(this.options.overscan,Math.ceil((this.range.endIndex-this.range.startIndex)/2)),c=Math.max(0,o-u),d=Math.min(this.options.count-1,o+u);return r>=c&&r<=d}return!0},this.measureElement=r=>{if(!r){this.elementsCache.forEach((c,d)=>{c.isConnected||(this.observer.unobserve(c),this.elementsCache.delete(d))});return}const i=this.indexFromElement(r),o=this.options.getItemKey(i),u=this.elementsCache.get(o);u!==r&&(u&&this.observer.unobserve(u),this.observer.observe(r),this.elementsCache.set(o,r)),(!this.isScrolling||this.scrollState)&&this.shouldMeasureDuringScroll(i)&&this.resizeItem(i,this.options.measureElement(r,void 0,this))},this.resizeItem=(r,i)=>{var o,u;if(r<0||r>=this.options.count)return;let c,d,p;const g=this._flatMeasurements;if(this.options.lanes===1&&g!==null)p=this.options.getItemKey(r),d=g[r*2],c=g[r*2+1];else{const x=this.measurementsCache[r];if(!x)return;p=x.key,d=x.start,c=x.size}const y=this.itemSizeCache.get(p)??c,v=i-y;if(v!==0){const x=this.options.anchorTo==="end"&&((o=this.scrollState)==null?void 0:o.behavior)!=="smooth"&&this.getVirtualDistanceFromEnd()<=this.options.scrollEndThreshold,T=x?this.getTotalSize():0,N=((u=this.scrollState)==null?void 0:u.behavior)!=="smooth"&&(this.shouldAdjustScrollPositionOnItemSizeChange!==void 0?this.shouldAdjustScrollPositionOnItemSizeChange(this.measurementsCache[r]??{index:r,key:p,start:d,size:c,end:d+c,lane:0},v,this):d[this.getVirtualIndexes(),this.getMeasurements()],(r,i)=>{const o=[];for(let u=0,c=r.length;uthis.options.debug}),this.getVirtualItemForOffset=r=>{const i=this.getMeasurements();if(i.length===0)return;const o=this._flatMeasurements,u=this.options.lanes===1&&o!=null,c=Vg(0,i.length-1,u?d=>o[d*2]:d=>Em(i[d]).start,r);return Em(i[c])},this.getMaxScrollOffset=()=>{if(!this.scrollElement)return 0;if("scrollHeight"in this.scrollElement)return this.options.horizontal?this.scrollElement.scrollWidth-this.scrollElement.clientWidth:this.scrollElement.scrollHeight-this.scrollElement.clientHeight;{const r=this.scrollElement.document.documentElement;return this.options.horizontal?r.scrollWidth-this.scrollElement.innerWidth:r.scrollHeight-this.scrollElement.innerHeight}},this.getVirtualDistanceFromEnd=()=>Math.max(this.getTotalSize()-this.getSize()-this.getScrollOffset(),0),this.getDistanceFromEnd=()=>Math.max(this.getMaxScrollOffset()-this.getScrollOffset(),0),this.isAtEnd=(r=this.options.scrollEndThreshold)=>this.getDistanceFromEnd()<=r,this.getOffsetForAlignment=(r,i,o=0)=>{if(!this.scrollElement)return 0;const u=this.getSize(),c=this.getScrollOffset();i==="auto"&&(i=r>=c+u?"end":"start"),i==="center"?r+=(o-u)/2:i==="end"&&(r-=u);const d=this.getMaxScrollOffset();return Math.max(Math.min(d,r),0)},this.getOffsetForIndex=(r,i="auto")=>{r=Math.max(0,Math.min(r,this.options.count-1));const o=this.getSize(),u=this.getScrollOffset(),c=this.measurementsCache[r];if(!c)return;if(i==="auto")if(c.end>=u+o-this.options.scrollPaddingEnd)i="end";else if(c.start<=u+this.options.scrollPaddingStart)i="start";else return[u,i];if(i==="end"&&r===this.options.count-1)return[this.getMaxScrollOffset(),i];const d=i==="end"?c.end+this.options.scrollPaddingEnd:c.start-this.options.scrollPaddingStart;return[this.getOffsetForAlignment(d,i,c.size),i]},this.scrollToOffset=(r,{align:i="start",behavior:o="auto"}={})=>{const u=this.getOffsetForAlignment(r,i),c=this.now();this.scrollState={index:null,align:i,behavior:o,startedAt:c,lastTargetOffset:u,stableFrames:0},this._scrollToOffset(u,{adjustments:void 0,behavior:o}),this.scheduleScrollReconcile()},this.scrollToIndex=(r,{align:i="auto",behavior:o="auto"}={})=>{r=Math.max(0,Math.min(r,this.options.count-1));const u=this.getOffsetForIndex(r,i);if(!u)return;const[c,d]=u,p=this.now();this.scrollState={index:r,align:d,behavior:o,startedAt:p,lastTargetOffset:c,stableFrames:0},this._scrollToOffset(c,{adjustments:void 0,behavior:o}),this.scheduleScrollReconcile()},this.scrollBy=(r,{behavior:i="auto"}={})=>{const o=this.getScrollOffset()+r,u=this.now();this.scrollState={index:null,align:"start",behavior:i,startedAt:u,lastTargetOffset:o,stableFrames:0},this._scrollToOffset(o,{adjustments:void 0,behavior:i}),this.scheduleScrollReconcile()},this.scrollToEnd=({behavior:r="auto"}={})=>{if(this.options.count>0){this.scrollToIndex(this.options.count-1,{align:"end",behavior:r});return}this.scrollToOffset(Math.max(this.getTotalSize()-this.getSize(),0),{behavior:r})},this.getTotalSize=()=>{var r;const i=this.getMeasurements();let o;if(i.length===0)o=this.options.paddingStart;else if(this.options.lanes===1){const u=i.length-1,c=this._flatMeasurements;c!=null?o=c[u*2]+c[u*2+1]:o=((r=i[u])==null?void 0:r.end)??0}else{const u=Array(this.options.lanes).fill(null);let c=i.length-1;for(;c>=0&&u.some(d=>d===null);){const d=i[c];u[d.lane]===null&&(u[d.lane]=d.end),c--}o=Math.max(...u.filter(d=>d!==null))}return Math.max(o-this.options.scrollMargin+this.options.paddingEnd,0)},this.takeSnapshot=()=>{const r=[];if(this.itemSizeCache.size===0)return r;const i=this.getMeasurements();for(const o of i)o&&this.itemSizeCache.has(o.key)&&r.push({index:o.index,key:o.key,start:o.start,size:o.size,end:o.end,lane:o.lane});return r},this._scrollToOffset=(r,{adjustments:i,behavior:o})=>{this._intendedScrollOffset=r+(i??0),this.options.scrollToFn(r,{behavior:o,adjustments:i},this)},this.measure=()=>{this.pendingMin=null,this.itemSizeCache.clear(),this.laneAssignments.clear(),this.itemSizeCacheVersion++,this.notify(!1)},this.setOptions(t)}applyScrollAdjustment(t,r){t!==0&&(Ef()&&(this.isScrolling||this._iosTouching||this._iosJustTouchEnded)?this._iosDeferredAdjustment+=t:this._scrollToOffset(this.getScrollOffset(),{adjustments:this.scrollAdjustments+=t,behavior:r}))}scheduleScrollReconcile(){if(!this.targetWindow){this.scrollState=null;return}this.rafId==null&&(this.rafId=this.targetWindow.requestAnimationFrame(()=>{this.rafId=null,this.reconcileScroll()}))}reconcileScroll(){if(!this.scrollState||!this.scrollElement)return;if(this.now()-this.scrollState.startedAt>5e3){this.scrollState=null;return}const i=this.scrollState.index!=null?this.getOffsetForIndex(this.scrollState.index,this.scrollState.align):void 0,o=i?i[0]:this.scrollState.lastTargetOffset,u=1,c=o!==this.scrollState.lastTargetOffset;if(!c&&rS(o,this.getScrollOffset())){if(this.scrollState.stableFrames++,this.scrollState.stableFrames>=u){this.getScrollOffset()!==o&&this._scrollToOffset(o,{adjustments:void 0,behavior:"auto"}),this.scrollState=null;return}}else if(this.scrollState.stableFrames=0,c){const d=this.getSize()||600,p=Math.abs(o-this.getScrollOffset()),g=this.scrollState.behavior==="smooth"&&p>d;this.scrollState.lastTargetOffset=o,g||(this.scrollState.behavior="auto"),this._scrollToOffset(o,{adjustments:void 0,behavior:g?"smooth":"auto"})}this.scheduleScrollReconcile()}}const Vg=(l,t,r,i)=>{for(;l<=t;){const o=(l+t)/2|0,u=r(o);if(ui)t=o-1;else return o}return l>0?l-1:0};function mS({measurements:l,outerSize:t,scrollOffset:r,lanes:i,flat:o}){const u=l.length-1,c=o?y=>o[y*2]:y=>l[y].start,d=o?y=>o[y*2]+o[y*2+1]:y=>l[y].end;if(l.length<=i)return{startIndex:0,endIndex:u};let p=Vg(0,u,c,r),g=p;if(i===1)for(;g1){const y=Array(i).fill(0);for(;gx=0&&v.some(x=>x>=r);){const x=l[p];v[x.lane]=x.start,p--}p=Math.max(0,p-p%i),g=Math.min(u,g+(i-1-g%i))}return{startIndex:p,endIndex:g}}const Cf=typeof document<"u"?P.useLayoutEffect:P.useEffect;function gS({useFlushSync:l=!0,directDomUpdates:t=!1,directDomUpdatesMode:r="transform",...i}){const o=P.useReducer(g=>g+1,0)[1],u=P.useRef({enabled:t,mode:r,container:null,lastSize:null,lastPositions:new WeakMap,prevRange:null});u.current.enabled=t,u.current.mode=r;const c=g=>{const y=u.current;if(!y.enabled||!y.container)return;const v=g.getTotalSize();if(v!==y.lastSize){y.lastSize=v;const U=g.options.horizontal?"width":"height";y.container.style[U]=`${v}px`}const x=!!g.options.horizontal,T=y.mode==="transform",N=x?"left":"top",C=g.options.scrollMargin,L=g.getVirtualItems();for(const U of L){const A=U.start-C,V=g.elementsCache.get(U.key);V&&y.lastPositions.get(V)!==A&&(y.lastPositions.set(V,A),T?V.style.transform=x?`translate3d(${A}px, 0, 0)`:`translate3d(0, ${A}px, 0)`:V.style[N]=`${A}px`)}},d={...i,onChange:(g,y)=>{var v;const x=u.current;let T=!0;if(x.enabled){c(g);const N=g.range,C=x.prevRange;T=!C||C.isScrolling!==g.isScrolling||C.startIndex!==(N==null?void 0:N.startIndex)||C.endIndex!==(N==null?void 0:N.endIndex),T&&(x.prevRange=N?{startIndex:N.startIndex,endIndex:N.endIndex,isScrolling:g.isScrolling}:null)}T&&(l&&y?Ps.flushSync(o):o()),(v=i.onChange)==null||v.call(i,g,y)}},[p]=P.useState(()=>{const g=new pS(d);return Object.assign(g,{containerRef:y=>{const v=u.current;if(v.container=y,v.lastSize=null,y&&v.enabled){const x=g.getTotalSize();v.lastSize=x;const T=g.options.horizontal?"width":"height";y.style[T]=`${x}px`}}})});return p.setOptions(d),Cf(()=>p._didMount(),[]),Cf(()=>p._willUpdate()),Cf(()=>{c(p)}),p}function vS(l){return gS({observeElementRect:oS,observeElementOffset:cS,scrollToFn:hS,...l})}const yS={pos:"p",vel:"v",torque:"τ",kp:"kp",kd:"kd",vel_limit:"vlim",torque_limit:"τlim",t_mos:"Tm",t_rotor:"Tr"},wS=["pos","vel","torque","kp","kd","t_mos","t_rotor"];function SS(l){const t=[];for(const r of wS)r in l.fields&&t.push(`${yS[r]||r} ${l.fields[r].toFixed(2)}`);return t.join(" ")||l.note||""}function xS(l){const t=new Date(l*1e3),r=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0"),o=String(t.getSeconds()).padStart(2,"0"),u=String(Math.floor(l%1*1e3)).padStart(3,"0");return`${r}:${i}:${o}.${u}`}function _S(){const[,l]=P.useState(0),[t,r]=P.useState(!1),i=P.useRef(null),o=P.useRef([]);P.useEffect(()=>{vm(!0);const d=H1(()=>{t||(o.current=F1(),l(p=>p+1))});return()=>{vm(!1),d()}},[t]);const u=o.current,c=vS({count:u.length,getScrollElement:()=>i.current,estimateSize:()=>22,overscan:12});return P.useEffect(()=>{!t&&u.length&&c.scrollToIndex(u.length-1)},[u.length,t,c]),R.jsxs("div",{className:"panel rawlog-panel",children:[R.jsxs("div",{className:"rawlog-toolbar",children:[R.jsx("button",{className:t?"btn small":"btn small active",onClick:()=>r(d=>!d),children:t?"Resume":"Pause"}),R.jsxs("span",{className:"muted",children:[u.length," frames"]})]}),R.jsxs("div",{className:"rawlog-body",ref:i,children:[R.jsxs("div",{className:"rawlog-head",children:[R.jsx("span",{className:"c-t",children:"time"}),R.jsx("span",{className:"c-arb",children:"arb"}),R.jsx("span",{className:"c-m",children:"motor"}),R.jsx("span",{className:"c-k",children:"kind"}),R.jsx("span",{className:"c-f",children:"decoded"}),R.jsx("span",{className:"c-r",children:"raw"})]}),R.jsx("div",{style:{height:c.getTotalSize(),position:"relative"},children:c.getVirtualItems().map(d=>{const p=u[d.index];return R.jsxs("div",{className:"rawlog-row k-"+p.kind,style:{transform:`translateY(${d.start}px)`},children:[R.jsx("span",{className:"c-t mono",children:xS(p.t)}),R.jsxs("span",{className:"c-arb mono",children:["0x",p.arb.toString(16).toUpperCase()]}),R.jsxs("span",{className:"c-m mono",children:["m",p.motorId]}),R.jsx("span",{className:"c-k",children:p.mode||p.kind}),R.jsx("span",{className:"c-f mono",children:SS(p)}),R.jsx("span",{className:"c-r mono dim",children:p.raw})]},p.seq)})})]})]})}async function is(l){return(await fetch(l)).json()}async function ui(l,t){return(await fetch(l,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t||{})})).json()}async function km(l,t){return(await fetch(l,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}const Cn={status:()=>is("/api/status"),setMode:l=>ui("/api/mode",{mode:l}),connect:l=>ui("/api/connect",l),disconnect:()=>ui("/api/disconnect"),scan:l=>ui("/api/control/scan",{motor_type:l}),motors:()=>is("/api/control/motors"),enable:l=>ui(`/api/control/motors/${l}/enable`),disable:l=>ui(`/api/control/motors/${l}/disable`),setZero:l=>ui(`/api/control/motors/${l}/set-zero`),clearError:l=>ui(`/api/control/motors/${l}/clear-error`),storeParams:l=>ui(`/api/control/motors/${l}/store-parameters`),command:(l,t)=>ui(`/api/control/motors/${l}/command`,t),state:l=>is(`/api/control/motors/${l}/state`),getRegisters:l=>is(`/api/control/motors/${l}/registers`),setRegister:(l,t,r)=>km(`/api/control/motors/${l}/registers/${t}`,{value:r}),setMotorType:(l,t)=>km(`/api/control/motors/${l}/motor-type`,{motor_type:t}),registerTable:()=>is("/api/register-table"),motorTypes:()=>is("/api/motor-types"),canInterfaces:l=>is(`/api/can-interfaces?bustype=${l}`),platform:()=>is("/api/platform")};function ES(){const l=We(K=>K.mode),t=We(K=>K.status),r=We(K=>K.controlMotors),i=We(K=>K.currentMotorId),o=We(K=>K.setControlMotors),u=We(K=>K.setCurrentMotor),c=We(K=>K.motorTypes),[d,p]=P.useState("socketcan"),[g,y]=P.useState("can0"),[v,x]=P.useState(1e6),[T,N]=P.useState([]),[C,L]=P.useState("DM4310"),[U,A]=P.useState(!1),[V,z]=P.useState(null),$=!!(t!=null&&t.connected);P.useEffect(()=>{Cn.platform().then(K=>{K!=null&&K.success&&(p(K.default_bustype),y(K.default_channel))})},[]),P.useEffect(()=>{Cn.canInterfaces(d).then(K=>N((K==null?void 0:K.interfaces)||[]))},[d]);const G=async()=>{A(!0),z(null);const K={channel:g,bustype:d};d==="gs_usb"&&(K.bitrate=v),l==="control"&&(K.motor_type=C);const fe=await Cn.connect(K);if(A(!1),fe.success){const q=fe.motors||[];o(q),q.length&&u(q[0].id)}else z(fe.error||"Connect failed")},Y=async()=>{await Cn.disconnect(),o([]),u(null)},Z=async()=>{A(!0);const K=await Cn.scan(C);A(!1),K.success?(o(K.motors||[]),(K.motors||[]).length&&u(K.motors[0].id)):z(K.error||"Scan failed")};return R.jsxs("div",{className:"panel control-form",children:[R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Bus"}),R.jsxs("select",{value:d,onChange:K=>p(K.target.value),disabled:$,children:[R.jsx("option",{value:"socketcan",children:"socketcan"}),R.jsx("option",{value:"gs_usb",children:"gs_usb"})]})]}),R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Channel"}),R.jsx("input",{list:"ifaces",value:g,onChange:K=>y(K.target.value),disabled:$}),R.jsx("datalist",{id:"ifaces",children:T.map(K=>R.jsx("option",{value:K},K))})]}),d==="gs_usb"&&R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Bitrate"}),R.jsx("input",{type:"number",value:v,onChange:K=>x(Number(K.target.value)),disabled:$})]}),l==="control"&&R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Motor type"}),R.jsx("select",{value:C,onChange:K=>L(K.target.value),children:c.map(K=>R.jsx("option",{value:K,children:K},K))})]}),R.jsxs("div",{className:"form-actions",children:[$?R.jsx("button",{className:"btn",onClick:Y,children:"Disconnect"}):R.jsx("button",{className:"btn primary",onClick:G,disabled:U,children:U?"Connecting…":"Connect"}),l==="control"&&$&&R.jsx("button",{className:"btn",onClick:Z,disabled:U,children:"Rescan"})]}),l==="control"&&$&&R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Motor"}),R.jsxs("select",{value:i??"",onChange:K=>u(K.target.value?Number(K.target.value):null),children:[R.jsx("option",{value:"",children:"select…"}),r.map(K=>R.jsxs("option",{value:K.id,children:["Motor ",K.id," (0x",K.id.toString(16).toUpperCase(),")"]},K.id))]})]}),l==="monitor"&&R.jsx("div",{className:"muted small",style:{marginTop:6},children:"Monitor mode: listening only. Switch to Control to drive motors."}),V&&R.jsx("div",{className:"form-error",children:V})]})}function CS(){const l=We(ee=>ee.mode),t=We(ee=>ee.status),r=We(ee=>ee.currentMotorId),i=We(ee=>ee.motorTypes),o=!!(t!=null&&t.connected),u=l==="control"&&o&&r!=null,[c,d]=P.useState("MIT"),[p,g]=P.useState(0),[y,v]=P.useState(0),[x,T]=P.useState(0),[N,C]=P.useState(0),[L,U]=P.useState(0),[A,V]=P.useState(0),[z,$]=P.useState(0),[G,Y]=P.useState(!1),[Z,K]=P.useState(50),[fe,q]=P.useState(!1),[xe,ce]=P.useState(null),ge=P.useRef(null),ye=()=>({control_mode:c,target_position:p,target_velocity:y,stiffness:x,damping:N,feedforward_torque:L,velocity_limit:A,torque_limit_ratio:z}),ke=()=>{ge.current!=null&&(clearInterval(ge.current),ge.current=null),q(!1)};P.useEffect(()=>ke,[]),P.useEffect(()=>{ke()},[r,l]);const le=async()=>{if(r==null)return;const ee=await Cn.command(r,ye());ee.success||(ce(ee.error||"command failed"),ke())},oe=()=>{if(!G)le();else if(fe)ke();else{q(!0),ce(null),le();const ee=1e3/Math.max(1,Math.min(1e3,Z));ge.current=window.setInterval(le,ee)}},ae=async(ee,be)=>{const he=await ee();ce(he!=null&&he.success?`${be} ✓`:`${be} failed: ${(he==null?void 0:he.error)||""}`)};if(!u)return R.jsx("div",{className:"panel control-form",children:R.jsx("div",{className:"muted center pad",children:l!=="control"?"Monitor mode — switch to Control to drive motors.":o?"Select a motor in the Connection widget.":"Connect to a bus (Connection widget)."})});const J=c!=="VEL",M=c==="MIT",W=c==="FORCE_POS",X=c==="POS_VEL"||W?"Vel limit":"Velocity";return R.jsxs("div",{className:"panel control-form",children:[R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Mode"}),R.jsxs("select",{value:c,onChange:ee=>d(ee.target.value),children:[R.jsx("option",{children:"MIT"}),R.jsx("option",{children:"POS_VEL"}),R.jsx("option",{children:"VEL"}),R.jsx("option",{children:"FORCE_POS"})]})]}),J&&R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Position"}),R.jsx("input",{type:"number",step:"0.001",value:p,onChange:ee=>g(+ee.target.value)})]}),R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:X}),R.jsx("input",{type:"number",step:"0.01",value:c==="FORCE_POS"?A:y,onChange:ee=>c==="FORCE_POS"?V(+ee.target.value):v(+ee.target.value)})]}),M&&R.jsxs(R.Fragment,{children:[R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Stiffness Kp"}),R.jsx("input",{type:"number",step:"0.1",value:x,onChange:ee=>T(+ee.target.value)})]}),R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Damping Kd"}),R.jsx("input",{type:"number",step:"0.01",value:N,onChange:ee=>C(+ee.target.value)})]}),R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Torque"}),R.jsx("input",{type:"number",step:"0.01",value:L,onChange:ee=>U(+ee.target.value)})]})]}),W&&R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Torque limit"}),R.jsx("input",{type:"number",step:"0.01",min:"0",max:"1",value:z,onChange:ee=>$(+ee.target.value)})]}),R.jsxs("div",{className:"form-actions",children:[R.jsx("button",{className:"btn ok",onClick:()=>ae(()=>Cn.enable(r),"Enable"),children:"Enable"}),R.jsx("button",{className:"btn danger",onClick:()=>{ke(),ae(()=>Cn.disable(r),"Disable")},children:"Disable"})]}),R.jsxs("div",{className:"form-actions",children:[R.jsx("button",{className:"btn "+(fe?"danger":"primary"),onClick:oe,children:G?fe?"Stop":"Start":"Send"}),R.jsxs("label",{className:"toggle",children:[R.jsx("input",{type:"checkbox",checked:G,onChange:ee=>{Y(ee.target.checked),ke()}}),"continuous"]}),G&&R.jsx("input",{className:"freq",type:"number",min:"1",max:"1000",value:Z,onChange:ee=>K(+ee.target.value),title:"Hz"})]}),R.jsxs("div",{className:"form-actions wrap",children:[R.jsx("button",{className:"btn",onClick:()=>ae(()=>Cn.setZero(r),"Set zero"),children:"Set Zero"}),R.jsx("button",{className:"btn",onClick:()=>ae(()=>Cn.clearError(r),"Clear error"),children:"Clear Err"}),R.jsx("button",{className:"btn",onClick:()=>ae(()=>Cn.storeParams(r),"Store"),children:"Store"})]}),R.jsxs("div",{className:"form-row",children:[R.jsx("label",{children:"Type"}),R.jsxs("select",{defaultValue:"",onChange:ee=>ee.target.value&&ae(()=>Cn.setMotorType(r,ee.target.value),"Type"),children:[R.jsx("option",{value:"",children:"set…"}),i.map(ee=>R.jsx("option",{value:ee,children:ee},ee))]})]}),xe&&R.jsx("div",{className:"form-msg",children:xe})]})}const Rm={1:"MIT",2:"POS_VEL",3:"VEL",4:"FORCE_POS"},Nm={0:"125K",1:"200K",2:"250K",3:"500K",4:"1M"};function kS(){const l=We(z=>z.mode),t=We(z=>z.status),r=We(z=>z.currentMotorId),i=We(z=>z.registerTable),o=We(z=>z.setCurrentMotor),u=!!(t!=null&&t.connected),c=l==="control"&&u&&r!=null,[d,p]=P.useState({}),[g,y]=P.useState({}),[v,x]=P.useState(null),[T,N]=P.useState(!1),C=async()=>{if(r==null)return;N(!0);const z=await Cn.getRegisters(r);N(!1),z.success?(p(z.registers||{}),y({})):x(z.error||"read failed")};if(P.useEffect(()=>{c?C():p({})},[r,c]),!c)return R.jsx("div",{className:"panel control-form",children:R.jsx("div",{className:"muted center pad",children:l!=="control"?"Monitor mode — registers unavailable.":"Connect + select a motor."})});const L=(z,$,G)=>{if(z===7||z===8){const Y=$.trim();return Y.toLowerCase().startsWith("0x")?parseInt(Y,16):parseInt(Y,16)||parseInt(Y,10)}return G==="float"?parseFloat($):parseInt($,10)},U=async z=>{var K;const $=i[z],G=($==null?void 0:$.data_type)||"float";let Y;if(z===10||z===35?Y=parseInt(g[z],10):z===9?Y=parseFloat(g[z]):Y=L(z,g[z]??"",G),Number.isNaN(Y)){x("invalid value");return}const Z=await Cn.setRegister(r,z,Y);Z.success?(((K=Z.updated_ids)==null?void 0:K.motor_id)!=null&&o(Z.updated_ids.motor_id),x(`reg ${z} written ✓`),setTimeout(C,100)):x(Z.error||"write failed")},A=(z,$)=>{if(z===9)return`${$} ms`;if(z===7||z===8)return`0x${Number($).toString(16).toUpperCase()} (${$})`;if(z===10)return Rm[Number($)]||String($);if(z===35)return Nm[Number($)]||String($);const G=i[z];return(G==null?void 0:G.data_type)==="float"?Number($).toFixed(4):String($)},V=Object.keys(d).map(Number).sort((z,$)=>z-$);return R.jsxs("div",{className:"panel registers-panel",children:[R.jsxs("div",{className:"rawlog-toolbar",children:[R.jsx("button",{className:"btn small",onClick:C,children:T?"…":"Refresh"}),v&&R.jsx("span",{className:"muted small",children:v})]}),R.jsx("div",{className:"registers-body",children:R.jsxs("table",{className:"motor-table reg-table",children:[R.jsx("thead",{children:R.jsxs("tr",{children:[R.jsx("th",{children:"Register"}),R.jsx("th",{children:"Value"}),R.jsx("th",{})]})}),R.jsx("tbody",{children:V.map(z=>{const $=i[z],G=($==null?void 0:$.access)==="RO";return R.jsxs("tr",{children:[R.jsx("td",{title:$==null?void 0:$.variable,children:($==null?void 0:$.description)||`reg ${z}`}),R.jsx("td",{className:"mono",children:G?A(z,d[z]):z===10||z===35?R.jsx("select",{value:g[z]??String(d[z]),onChange:Y=>y({...g,[z]:Y.target.value}),children:Object.entries(z===10?Rm:Nm).map(([Y,Z])=>R.jsxs("option",{value:Y,children:[Z," (",Y,")"]},Y))}):R.jsx("input",{value:g[z]??(z===7||z===8?`0x${Number(d[z]).toString(16).toUpperCase()}`:String(d[z])),onChange:Y=>y({...g,[z]:Y.target.value})})}),R.jsx("td",{children:!G&&R.jsx("button",{className:"btn small",onClick:()=>U(z),children:"Write"})})]},z)})})]})})]})}const $g=[{kind:"connection",title:"Connection",icon:"⇄",description:"Connect to a CAN bus, scan, and select a motor.",render:()=>R.jsx(ES,{})},{kind:"control",title:"Motor Control",icon:"◉",description:"Drive the selected motor (MIT/POS_VEL/VEL/FORCE_POS), enable, zero, store.",render:()=>R.jsx(CS,{})},{kind:"registers",title:"Registers",icon:"≡",description:"Read/write the selected motor's registers.",render:()=>R.jsx(kS,{})},{kind:"plot",title:"Plot",icon:"〜",description:"Time-series chart; drag signals onto it (cmd over fb to overlay).",render:l=>R.jsx(Z1,{panelId:l})},{kind:"table",title:"Motor Table",icon:"▦",description:"One row per motor: commanded vs actual.",render:()=>R.jsx(eS,{})},{kind:"cards",title:"Motor Cards",icon:"▢",description:"Per-motor cards/gauges with big readouts.",render:()=>R.jsx(tS,{})},{kind:"rawlog",title:"Raw CAN Log",icon:"≣",description:"Scrolling decoded frame log.",render:()=>R.jsx(_S,{})}],RS=Object.fromEntries($g.map(l=>[l.kind,l])),Gg="damiao.monitor.theme";function Kg(){return localStorage.getItem(Gg)==="dark"?"dark":"light"}function Yg(l){document.documentElement.setAttribute("data-theme",l)}function NS(l){try{localStorage.setItem(Gg,l)}catch{}Yg(l)}function bS(){Yg(Kg())}function TS(){var T,N;const l=We(C=>C.connected),t=We(C=>C.status),r=We(C=>C.mode),i=We(C=>C.setMode),o=We(C=>C.setControlMotors),u=We(C=>C.setCurrentMotor),c=Ro(C=>C.addWidget),d=Ro(C=>C.resetWidgets),[p,g]=P.useState(Kg()),y=()=>{const C=p==="light"?"dark":"light";NS(C),g(C)},v=async C=>{C!==r&&(i(C),o([]),u(null),await Cn.setMode(C))},x=t!=null&&t.demo?"demo":(t==null?void 0:t.channel)||"—";return R.jsxs("header",{className:"toolbar",children:[R.jsxs("div",{className:"brand",children:[R.jsx("span",{className:"brand-dot"}),"DaMiao ",R.jsx("span",{className:"brand-sub",children:"Studio"})]}),R.jsxs("div",{className:"mode-switch",role:"tablist",children:[R.jsx("button",{className:"mode-tab "+(r==="control"?"active":""),onClick:()=>v("control"),children:"◉ Control"}),R.jsx("button",{className:"mode-tab "+(r==="monitor"?"active":""),onClick:()=>v("monitor"),children:"◎ Monitor"})]}),R.jsxs("div",{className:"conn",children:[R.jsx("span",{className:"dot "+(l?"on":"off")}),R.jsx("span",{className:"mono",children:x}),r==="control"?R.jsx("span",{className:"badge warn",title:"active mode — transmits",children:"active · TX"}):R.jsx("span",{className:"badge "+(t!=null&&t.listenOnly,"ok"),title:"passive — never transmits",children:"listen-only"}),(t==null?void 0:t.error)&&R.jsx("span",{className:"badge err",title:t.error,children:"error"}),t&&R.jsxs("span",{className:"muted small",children:[((N=(T=t.framesSeen)==null?void 0:T.toLocaleString)==null?void 0:N.call(T))??0," frames"]})]}),R.jsx("div",{className:"spacer"}),R.jsxs("div",{className:"actions",children:[$g.map(C=>R.jsxs("button",{className:"btn",title:C.description,onClick:()=>c(C.kind),children:[R.jsx("span",{className:"btn-icon",children:C.icon})," ",C.title]},C.kind)),R.jsx("button",{className:"btn ghost",onClick:y,title:`Switch to ${p==="light"?"dark":"light"} mode`,children:p==="light"?"☾":"☀"}),R.jsx("button",{className:"btn ghost",onClick:d,children:"Reset"})]})]})}function MS({sig:l}){const{attributes:t,listeners:r,setNodeRef:i,isDragging:o}=d0({id:`sig:${l.id}`,data:{signalId:l.id}}),u=Y1(l);return R.jsxs("div",{ref:i,className:"sig-chip"+(o?" dragging":""),...r,...t,title:l.id,children:[R.jsx("span",{className:"sig-swatch",style:{background:u,borderStyle:l.source==="cmd"?"dashed":"solid"}}),R.jsxs("span",{className:"sig-name",children:[l.source,".",l.field]}),l.unit&&R.jsx("span",{className:"sig-unit",children:l.unit})]})}function DS(l){return[...l].sort((t,r)=>{if(t.source!==r.source)return t.source==="cmd"?-1:1;const i=xm.indexOf(t.field),o=xm.indexOf(r.field);return(i<0?99:i)-(o<0?99:o)})}function zS(){const l=We(u=>u.signals),t=We(u=>u.status),[r,i]=P.useState(""),o=P.useMemo(()=>{const u=new Map;for(const c of l){if(r&&!c.id.toLowerCase().includes(r.toLowerCase()))continue;const d=u.get(c.motorId)||[];d.push(c),u.set(c.motorId,d)}return Array.from(u.entries()).sort((c,d)=>c[0]-d[0])},[l,r]);return R.jsxs("aside",{className:"sidebar",children:[R.jsxs("div",{className:"sidebar-head",children:[R.jsx("div",{className:"sidebar-title",children:"Signals"}),R.jsx("input",{className:"filter",placeholder:"filter…",value:r,onChange:u=>i(u.target.value)})]}),R.jsxs("div",{className:"sidebar-body",children:[o.length===0&&R.jsx("div",{className:"muted pad",children:t!=null&&t.error?"Bus error — see top bar.":"No signals yet. Start a controller on the bus (or run --demo)."}),o.map(([u,c])=>R.jsxs("div",{className:"motor-group",children:[R.jsxs("div",{className:"motor-group-title",children:["Motor ",u]}),R.jsx("div",{className:"chips",children:DS(c).map(d=>R.jsx(MS,{sig:d},d.id))})]},u))]}),R.jsxs("div",{className:"sidebar-foot muted",children:["Drag a signal onto a plot. Drop ",R.jsx("b",{children:"cmd"})," onto its ",R.jsx("b",{children:"fb"})," plot to overlay."]})]})}function OS(l,t,r,i,o){const u=(...c)=>(console.warn("gridstack.js: Function `"+r+"` is deprecated in "+o+" and has been replaced with `"+i+"`. It will be **removed** in a future release"),t.apply(l,c));return u.prototype=t.prototype,u}class F{static getElements(t,r=document){if(typeof t=="string"){const i="getElementById"in r?r:void 0;if(i&&!isNaN(+t[0])){const u=i.getElementById(t);return u?[u]:[]}let o=r.querySelectorAll(t);return!o.length&&t[0]!=="."&&t[0]!=="#"&&(o=r.querySelectorAll("."+t),o.length||(o=r.querySelectorAll("#"+t))),Array.from(o)}return[t]}static getElement(t,r=document){if(typeof t=="string"){const i="getElementById"in r?r:void 0;if(!t.length)return null;if(i&&t[0]==="#")return i.getElementById(t.substring(1));if(t[0]==="#"||t[0]==="."||t[0]==="[")return r.querySelector(t);if(i&&!isNaN(+t[0]))return i.getElementById(t);let o=r.querySelector(t);return i&&!o&&(o=i.getElementById(t)),o||(o=r.querySelector("."+t)),o}return t}static lazyLoad(t){var r,i;return t.lazyLoad||((i=(r=t.grid)==null?void 0:r.opts)==null?void 0:i.lazyLoad)&&t.lazyLoad!==!1}static createDiv(t,r){const i=document.createElement("div");return t.forEach(o=>{o&&i.classList.add(o)}),r==null||r.appendChild(i),i}static shouldSizeToContent(t,r=!1){return(t==null?void 0:t.grid)&&(r?t.sizeToContent===!0||t.grid.opts.sizeToContent===!0&&t.sizeToContent===void 0:!!t.sizeToContent||t.grid.opts.sizeToContent&&t.sizeToContent!==!1)}static isIntercepted(t,r){return!(t.y>=r.y+r.h||t.y+t.h<=r.y||t.x+t.w<=r.x||t.x>=r.x+r.w)}static isTouching(t,r){return F.isIntercepted(t,{x:r.x-.5,y:r.y-.5,w:r.w+1,h:r.h+1})}static areaIntercept(t,r){const i=t.x>r.x?t.x:r.x,o=t.x+t.wr.y?t.y:r.y,c=t.y+t.h{const c=r*((o.y??1e4)-(u.y??1e4));return c===0?r*((o.x??1e4)-(u.x??1e4)):c})}static find(t,r){return r?t.find(i=>i.id===r):void 0}static createStylesheet(t,r,i){const o=document.createElement("style"),u=i==null?void 0:i.nonce;return u&&(o.nonce=u),o.setAttribute("type","text/css"),o.setAttribute("gs-style-id",t),o.styleSheet?o.styleSheet.cssText="":o.appendChild(document.createTextNode("")),r?r.insertBefore(o,r.firstChild):(r=document.getElementsByTagName("head")[0],r.appendChild(o)),o}static removeStylesheet(t,r){const o=(r||document).querySelector("STYLE[gs-style-id="+t+"]");o&&o.parentNode&&o.remove()}static addCSSRule(t,r,i){t.textContent+=`${r} { ${i} } `}static toBool(t){return typeof t=="boolean"?t:typeof t=="string"?(t=t.toLowerCase(),!(t===""||t==="no"||t==="false"||t==="0")):!!t}static toNumber(t){return t===null||t.length===0?void 0:Number(t)}static parseHeight(t){let r,i="px";if(typeof t=="string")if(t==="auto"||t==="")r=0;else{const o=t.match(/^(-[0-9]+\.[0-9]+|[0-9]*\.[0-9]+|-[0-9]+|[0-9]+)(px|em|rem|vh|vw|%|cm|mm)?$/);if(!o)throw new Error(`Invalid height val = ${t}`);i=o[2]||"px",r=parseFloat(o[1])}else r=t;return{h:r,unit:i}}static defaults(t,...r){return r.forEach(i=>{for(const o in i){if(!i.hasOwnProperty(o))return;t[o]===null||t[o]===void 0?t[o]=i[o]:typeof i[o]=="object"&&typeof t[o]=="object"&&this.defaults(t[o],i[o])}}),t}static same(t,r){if(typeof t!="object")return t==r;if(typeof t!=typeof r||Object.keys(t).length!==Object.keys(r).length)return!1;for(const i in t)if(t[i]!==r[i])return!1;return!0}static copyPos(t,r,i=!1){return r.x!==void 0&&(t.x=r.x),r.y!==void 0&&(t.y=r.y),r.w!==void 0&&(t.w=r.w),r.h!==void 0&&(t.h=r.h),i&&(r.minW&&(t.minW=r.minW),r.minH&&(t.minH=r.minH),r.maxW&&(t.maxW=r.maxW),r.maxH&&(t.maxH=r.maxH)),t}static samePos(t,r){return t&&r&&t.x===r.x&&t.y===r.y&&(t.w||1)===(r.w||1)&&(t.h||1)===(r.h||1)}static sanitizeMinMax(t){t.minW||delete t.minW,t.minH||delete t.minH,t.maxW||delete t.maxW,t.maxH||delete t.maxH}static removeInternalAndSame(t,r){if(!(typeof t!="object"||typeof r!="object"))for(let i in t){const o=t[i],u=r[i];i[0]==="_"||o===u?delete t[i]:o&&typeof o=="object"&&u!==void 0&&(F.removeInternalAndSame(o,u),Object.keys(o).length||delete t[i])}}static removeInternalForSave(t,r=!0){for(let i in t)(i[0]==="_"||t[i]===null||t[i]===void 0)&&delete t[i];delete t.grid,r&&delete t.el,t.autoPosition||delete t.autoPosition,t.noResize||delete t.noResize,t.noMove||delete t.noMove,t.locked||delete t.locked,(t.w===1||t.w===t.minW)&&delete t.w,(t.h===1||t.h===t.minH)&&delete t.h}static throttle(t,r){let i=!1;return(...o)=>{i||(i=!0,setTimeout(()=>{t(...o),i=!1},r))}}static removePositioningStyles(t){const r=t.style;r.position&&r.removeProperty("position"),r.left&&r.removeProperty("left"),r.top&&r.removeProperty("top"),r.width&&r.removeProperty("width"),r.height&&r.removeProperty("height")}static getScrollElement(t){if(!t)return document.scrollingElement||document.documentElement;const r=getComputedStyle(t);return/(auto|scroll)/.test(r.overflow+r.overflowY)?t:this.getScrollElement(t.parentElement)}static updateScrollPosition(t,r,i){const o=t.getBoundingClientRect(),u=window.innerHeight||document.documentElement.clientHeight;if(o.top<0||o.bottom>u){const c=o.bottom-u,d=o.top,p=this.getScrollElement(t);if(p!==null){const g=p.scrollTop;o.top<0&&i<0?t.offsetHeight>u?p.scrollTop+=i:p.scrollTop+=Math.abs(d)>Math.abs(i)?i:d:i>0&&(t.offsetHeight>u?p.scrollTop+=i:p.scrollTop+=c>i?i:c),r.top+=p.scrollTop-g}}}static updateScrollResize(t,r,i){const o=this.getScrollElement(r),u=o.clientHeight,c=o===this.getScrollElement()?0:o.getBoundingClientRect().top,d=t.clientY-c,p=du-i;p?o.scrollBy({behavior:"smooth",top:d-i}):g&&o.scrollBy({behavior:"smooth",top:i-(u-d)})}static clone(t){return t==null||typeof t!="object"?t:t instanceof Array?[...t]:{...t}}static cloneDeep(t){const r=["parentGrid","el","grid","subGrid","engine"],i=F.clone(t);for(const o in i)i.hasOwnProperty(o)&&typeof i[o]=="object"&&o.substring(0,2)!=="__"&&!r.find(u=>u===o)&&(i[o]=F.cloneDeep(t[o]));return i}static cloneNode(t){const r=t.cloneNode(!0);return r.removeAttribute("id"),r}static appendTo(t,r){let i;typeof r=="string"?i=F.getElement(r):i=r,i&&i.appendChild(t)}static addElStyles(t,r){if(r instanceof Object)for(const i in r)r.hasOwnProperty(i)&&(Array.isArray(r[i])?r[i].forEach(o=>{t.style[i]=o}):t.style[i]=r[i])}static initEvent(t,r){const i={type:r.type},o={button:0,which:0,buttons:1,bubbles:!0,cancelable:!0,target:r.target?r.target:t.target};return["altKey","ctrlKey","metaKey","shiftKey"].forEach(u=>i[u]=t[u]),["pageX","pageY","clientX","clientY","screenX","screenY"].forEach(u=>i[u]=t[u]),{...i,...o}}static simulateMouseEvent(t,r,i){const o=t,u=new MouseEvent(r,{bubbles:!0,composed:!0,cancelable:!0,view:window,detail:1,screenX:t.screenX,screenY:t.screenY,clientX:t.clientX,clientY:t.clientY,ctrlKey:o.ctrlKey??!1,altKey:o.altKey??!1,shiftKey:o.shiftKey??!1,metaKey:o.metaKey??!1,button:0,relatedTarget:t.target});(i||t.target).dispatchEvent(u)}static getValuesFromTransformedElement(t){const r=document.createElement("div");F.addElStyles(r,{opacity:"0",position:"fixed",top:"0px",left:"0px",width:"1px",height:"1px",zIndex:"-999999"}),t.appendChild(r);const i=r.getBoundingClientRect();return t.removeChild(r),r.remove(),{xScale:1/i.width,yScale:1/i.height,xOffset:i.left,yOffset:i.top}}static swap(t,r,i){if(!t)return;const o=t[r];t[r]=t[i],t[i]=o}static canBeRotated(t){var r;return!(!t||t.w===t.h||t.locked||t.noResize||(r=t.grid)!=null&&r.opts.disableResize||t.minW&&t.minW===t.maxW||t.minH&&t.minH===t.maxH)}}class ci{constructor(t={}){this.addedNodes=[],this.removedNodes=[],this.defaultColumn=12,this.column=t.column||this.defaultColumn,this.column>this.defaultColumn&&(this.defaultColumn=this.column),this.maxRow=t.maxRow,this._float=t.float,this.nodes=t.nodes||[],this.onChange=t.onChange}batchUpdate(t=!0,r=!0){return!!this.batchMode===t?this:(this.batchMode=t,t?(this._prevFloat=this._float,this._float=!0,this.cleanNodes(),this.saveInitial()):(this._float=this._prevFloat,delete this._prevFloat,r&&this._packNodes(),this._notify()),this)}_useEntireRowArea(t,r){return(!this.float||this.batchMode&&!this._prevFloat)&&!this._hasLocked&&(!t._moving||t._skipDown||r.y<=t.y)}_fixCollisions(t,r=t,i,o={}){if(this.sortNodes(-1),i=i||this.collide(t,r),!i)return!1;if(t._moving&&!o.nested&&!this.float&&this.swap(t,i))return!0;let u=r;!this._loading&&this._useEntireRowArea(t,r)&&(u={x:0,w:this.column,y:r.y,h:r.h},i=this.collide(t,u,o.skip));let c=!1;const d={nested:!0,pack:!1};let p=0;for(;i=i||this.collide(t,u,o.skip);){if(p++>this.nodes.length*2)throw new Error("Infinite collide check");let g;if(i.locked||this._loading||t._moving&&!t._skipDown&&r.y>t.y&&!this.float&&(!this.collide(i,{...i,y:t.y},t)||!this.collide(i,{...i,y:r.y-i.h},t))){t._skipDown=t._skipDown||r.y>t.y;const y={...r,y:i.y+i.h,...d};g=this._loading&&F.samePos(t,y)?!0:this.moveNode(t,y),(i.locked||this._loading)&&g?F.copyPos(r,t):!i.locked&&g&&o.pack&&(this._packNodes(),r.y=i.y+i.h,F.copyPos(t,r)),c=c||g}else g=this.moveNode(i,{...i,y:r.y+r.h,skip:t,...d});if(!g)return c;i=void 0}return c}collide(t,r=t,i){const o=t._id,u=i==null?void 0:i._id;return this.nodes.find(c=>c._id!==o&&c._id!==u&&F.isIntercepted(c,r))}collideAll(t,r=t,i){const o=t._id,u=i==null?void 0:i._id;return this.nodes.filter(c=>c._id!==o&&c._id!==u&&F.isIntercepted(c,r))}directionCollideCoverage(t,r,i){if(!r.rect||!t._rect)return;const o=t._rect,u={...r.rect};u.y>o.y?(u.h+=u.y-o.y,u.y=o.y):u.h+=o.y-u.y,u.x>o.x?(u.w+=u.x-o.x,u.x=o.x):u.w+=o.x-u.x;let c,d=.5;for(let p of i){if(p.locked||!p._rect)break;const g=p._rect;let y=Number.MAX_VALUE,v=Number.MAX_VALUE;o.yg.y+g.h&&(y=(g.y+g.h-u.y)/g.h),o.xg.x+g.w&&(v=(g.x+g.w-u.x)/g.w);const x=Math.min(v,y);x>d&&(d=x,c=p)}return r.collide=c,c}cacheRects(t,r,i,o,u,c){return this.nodes.forEach(d=>d._rect={y:d.y*r+i,x:d.x*t+c,w:d.w*t-c-o,h:d.h*r-i-u}),this}swap(t,r){if(!r||r.locked||!t||t.locked)return!1;function i(){const u=r.x,c=r.y;return r.x=t.x,r.y=t.y,t.h!=r.h?(t.x=u,t.y=r.y+r.h):t.w!=r.w?(t.x=r.x+r.w,t.y=c):(t.x=u,t.y=c),t._dirty=r._dirty=!0,!0}let o;if(t.w===r.w&&t.h===r.h&&(t.x===r.x||t.y===r.y)&&(o=F.isTouching(t,r)))return i();if(o!==!1){if(t.w===r.w&&t.x===r.x&&(o||(o=F.isTouching(t,r)))){if(r.y{let g;c.locked||(c.autoPosition=!0,t==="list"&&d&&(g=p[d-1])),this.addNode(c,!1,g)}),o||delete this._inColumnResize,i||this.batchUpdate(!1),this}set float(t){this._float!==t&&(this._float=t||!1,t||this._packNodes()._notify())}get float(){return this._float||!1}sortNodes(t=1){return this.nodes=F.sort(this.nodes,t),this}_packNodes(){return this.batchMode?this:(this.sortNodes(),this.float?this.nodes.forEach(t=>{if(t._updating||t._orig===void 0||t.y===t._orig.y)return;let r=t.y;for(;r>t._orig.y;)--r,this.collide(t,{x:t.x,y:r,w:t.w,h:t.h})||(t._dirty=!0,t.y=r)}):this.nodes.forEach((t,r)=>{if(!t.locked)for(;t.y>0;){const i=r===0?0:t.y-1;if(!(r===0||!this.collide(t,{x:t.x,y:i,w:t.w,h:t.h})))break;t._dirty=t.y!==i,t.y=i}}),this)}prepareNode(t,r){t._id=t._id??ci._idSeq++;const i=t.id;if(i){let u=1;for(;this.nodes.find(c=>c.id===t.id&&c!==t);)t.id=i+"_"+u++}(t.x===void 0||t.y===void 0||t.x===null||t.y===null)&&(t.autoPosition=!0);const o={x:0,y:0,w:1,h:1};return F.defaults(t,o),t.autoPosition||delete t.autoPosition,t.noResize||delete t.noResize,t.noMove||delete t.noMove,F.sanitizeMinMax(t),typeof t.x=="string"&&(t.x=Number(t.x)),typeof t.y=="string"&&(t.y=Number(t.y)),typeof t.w=="string"&&(t.w=Number(t.w)),typeof t.h=="string"&&(t.h=Number(t.h)),isNaN(t.x)&&(t.x=o.x,t.autoPosition=!0),isNaN(t.y)&&(t.y=o.y,t.autoPosition=!0),isNaN(t.w)&&(t.w=o.w),isNaN(t.h)&&(t.h=o.h),this.nodeBoundFix(t,r),t}nodeBoundFix(t,r){const i=t._orig||F.copyPos({},t);if(t.maxW&&(t.w=Math.min(t.w||1,t.maxW)),t.maxH&&(t.h=Math.min(t.h||1,t.maxH)),t.minW&&(t.w=Math.max(t.w||1,t.minW)),t.minH&&(t.h=Math.max(t.h||1,t.minH)),(t.x||0)+(t.w||1)>this.column&&this.columnthis.column?t.w=this.column:t.w<1&&(t.w=1),this.maxRow&&t.h>this.maxRow?t.h=this.maxRow:t.h<1&&(t.h=1),t.x<0&&(t.x=0),t.y<0&&(t.y=0),t.x+t.w>this.column&&(r?t.w=this.column-t.x:t.x=this.column-t.w),this.maxRow&&t.y+t.h>this.maxRow&&(r?t.h=this.maxRow-t.y:t.y=this.maxRow-t.h),F.samePos(t,i)||(t._dirty=!0),this}getDirtyNodes(t){return t?this.nodes.filter(r=>r._dirty&&!F.samePos(r,r._orig)):this.nodes.filter(r=>r._dirty)}_notify(t){if(this.batchMode||!this.onChange)return this;const r=(t||[]).concat(this.getDirtyNodes());return this.onChange(r),this}cleanNodes(){return this.batchMode?this:(this.nodes.forEach(t=>{delete t._dirty,delete t._lastTried}),this)}saveInitial(){return this.nodes.forEach(t=>{t._orig=F.copyPos({},t),delete t._dirty}),this._hasLocked=this.nodes.some(t=>t.locked),this}restoreInitial(){return this.nodes.forEach(t=>{!t._orig||F.samePos(t,t._orig)||(F.copyPos(t,t._orig),t._dirty=!0)}),this._notify(),this}findEmptyPosition(t,r=this.nodes,i=this.column,o){const u=o?o.y*i+(o.x+o.w):0;let c=!1;for(let d=u;!c;++d){const p=d%i,g=Math.floor(d/i);if(p+t.w>i)continue;const y={x:p,y:g,w:t.w,h:t.h};r.find(v=>F.isIntercepted(y,v))||((t.x!==p||t.y!==g)&&(t._dirty=!0),t.x=p,t.y=g,delete t.autoPosition,c=!0)}return c}addNode(t,r=!1,i){const o=this.nodes.find(c=>c._id===t._id);if(o)return o;this._inColumnResize?this.nodeBoundFix(t):this.prepareNode(t),delete t._temporaryRemoved,delete t._removeDOM;let u;return t.autoPosition&&this.findEmptyPosition(t,this.nodes,this.column,i)&&(delete t.autoPosition,u=!0),this.nodes.push(t),r&&this.addedNodes.push(t),u||this._fixCollisions(t),this.batchMode||this._packNodes()._notify(),t}removeNode(t,r=!0,i=!1){return this.nodes.find(o=>o._id===t._id)?(i&&this.removedNodes.push(t),r&&(t._removeDOM=!0),this.nodes=this.nodes.filter(o=>o._id!==t._id),t._isAboutToRemove||this._packNodes(),this._notify([t]),this):this}removeAll(t=!0,r=!0){if(delete this._layouts,!this.nodes.length)return this;t&&this.nodes.forEach(o=>o._removeDOM=!0);const i=this.nodes;return this.removedNodes=r?i:[],this.nodes=[],this._notify(i)}moveNodeCheck(t,r){if(!this.changedPosConstrain(t,r))return!1;if(r.pack=!0,!this.maxRow)return this.moveNode(t,r);let i;const o=new ci({column:this.column,float:this.float,nodes:this.nodes.map(c=>c._id===t._id?(i={...c},i):{...c})});if(!i)return!1;const u=o.moveNode(i,r)&&o.getRow()<=Math.max(this.getRow(),this.maxRow);if(!u&&!r.resizing&&r.collide){const c=r.collide.el.gridstackNode;if(this.swap(t,c))return this._notify(),!0}return u?(o.nodes.filter(c=>c._dirty).forEach(c=>{const d=this.nodes.find(p=>p._id===c._id);d&&(F.copyPos(d,c),d._dirty=!0)}),this._notify(),!0):!1}willItFit(t){if(delete t._willFitPos,!this.maxRow)return!0;const r=new ci({column:this.column,float:this.float,nodes:this.nodes.map(o=>({...o}))}),i={...t};return this.cleanupNode(i),delete i.el,delete i._id,delete i.content,delete i.grid,r.addNode(i),r.getRow()<=this.maxRow?(t._willFitPos=F.copyPos({},i),!0):!1}changedPosConstrain(t,r){return r.w=r.w||t.w,r.h=r.h||t.h,t.x!==r.x||t.y!==r.y?!0:(t.maxW&&(r.w=Math.min(r.w,t.maxW)),t.maxH&&(r.h=Math.min(r.h,t.maxH)),t.minW&&(r.w=Math.max(r.w,t.minW)),t.minH&&(r.h=Math.max(r.h,t.minH)),t.w!==r.w||t.h!==r.h)}moveNode(t,r){var g,y;if(!t||!r)return!1;let i;r.pack===void 0&&!this.batchMode&&(i=r.pack=!0),typeof r.x!="number"&&(r.x=t.x),typeof r.y!="number"&&(r.y=t.y),typeof r.w!="number"&&(r.w=t.w),typeof r.h!="number"&&(r.h=t.h);const o=t.w!==r.w||t.h!==r.h,u=F.copyPos({},t,!0);if(F.copyPos(u,r),this.nodeBoundFix(u,o),F.copyPos(r,u),!r.forceCollide&&F.samePos(t,r))return!1;const c=F.copyPos({},t),d=this.collideAll(t,u,r.skip);let p=!0;if(d.length){const v=t._moving&&!r.nested;let x=v?this.directionCollideCoverage(t,r,d):d[0];if(v&&x&&((y=(g=t.grid)==null?void 0:g.opts)!=null&&y.subGridDynamic)&&!t.grid._isTemp){const T=F.areaIntercept(r.rect,x._rect),N=F.area(r.rect),C=F.area(x._rect);T/(N.8&&(x.grid.makeSubGrid(x.el,void 0,t),x=void 0)}x?p=!this._fixCollisions(t,u,x,r):(p=!1,i&&delete r.pack)}return p&&!F.samePos(t,u)&&(t._dirty=!0,F.copyPos(t,u)),r.pack&&this._packNodes()._notify(),!F.samePos(t,c)}getRow(){return this.nodes.reduce((t,r)=>Math.max(t,r.y+r.h),0)}beginUpdate(t){return t._updating||(t._updating=!0,delete t._skipDown,this.batchMode||this.saveInitial()),this}endUpdate(){const t=this.nodes.find(r=>r._updating);return t&&(delete t._updating,delete t._skipDown),this}save(t=!0,r){var c;const i=(c=this._layouts)==null?void 0:c.length,o=i&&this.column!==i-1?this._layouts[i-1]:null,u=[];return this.sortNodes(),this.nodes.forEach(d=>{const p=o==null?void 0:o.find(y=>y._id===d._id),g={...d,...p||{}};F.removeInternalForSave(g,!t),r&&r(d,g),u.push(g)}),u}layoutsNodesChange(t){return!this._layouts||this._inColumnResize?this:(this._layouts.forEach((r,i)=>{if(!r||i===this.column)return this;if(i{if(!u._orig)return;const c=r.find(d=>d._id===u._id);c&&(c.y>=0&&u.y!==u._orig.y&&(c.y+=u.y-u._orig.y),u.x!==u._orig.x&&(c.x=Math.round(u.x*o)),u.w!==u._orig.w&&(c.w=Math.round(u.w*o)))})}}),this)}columnChanged(t,r,i="moveScale"){var d;if(!this.nodes.length||!r||t===r)return this;const o=i==="compact"||i==="list";o&&this.sortNodes(1),rt&&this._layouts){const p=this._layouts[r]||[],g=this._layouts.length-1;!p.length&&t!==g&&((d=this._layouts[g])!=null&&d.length)&&(t=g,this._layouts[g].forEach(y=>{const v=c.find(x=>x._id===y._id);v&&(!o&&!y.autoPosition&&(v.x=y.x??v.x,v.y=y.y??v.y),v.w=y.w??v.w,(y.x==null||y.y===void 0)&&(v.autoPosition=!0))})),p.forEach(y=>{const v=c.findIndex(x=>x._id===y._id);if(v!==-1){const x=c[v];if(o){x.w=y.w;return}(y.autoPosition||isNaN(y.x)||isNaN(y.y))&&this.findEmptyPosition(y,u),y.autoPosition||(x.x=y.x??x.x,x.y=y.y??x.y,x.w=y.w??x.w,u.push(x)),c.splice(v,1)}})}if(o)this.compact(i,!1);else{if(c.length)if(typeof i=="function")i(r,t,u,c);else{const p=o||i==="none"?1:r/t,g=i==="move"||i==="moveScale",y=i==="scale"||i==="moveScale";c.forEach(v=>{v.x=r===1?0:g?Math.round(v.x*p):Math.min(v.x,r-1),v.w=r===1||t===1?1:y?Math.round(v.w*p)||1:Math.min(v.w,r),u.push(v)}),c=[]}u=F.sort(u,-1),this._inColumnResize=!0,this.nodes=[],u.forEach(p=>{this.addNode(p,!1),delete p._orig})}return this.nodes.forEach(p=>delete p._orig),this.batchUpdate(!1,!o),delete this._inColumnResize,this}cacheLayout(t,r,i=!1){const o=[];return t.forEach((u,c)=>{if(u._id===void 0){const d=u.id?this.nodes.find(p=>p.id===u.id):void 0;u._id=(d==null?void 0:d._id)??ci._idSeq++}o[c]={x:u.x,y:u.y,w:u.w,_id:u._id}}),this._layouts=i?[]:this._layouts||[],this._layouts[r]=o,this}cacheOneLayout(t,r){t._id=t._id??ci._idSeq++;const i={x:t.x,y:t.y,w:t.w,_id:t._id};(t.autoPosition||t.x===void 0)&&(delete i.x,delete i.y,t.autoPosition&&(i.autoPosition=!0)),this._layouts=this._layouts||[],this._layouts[r]=this._layouts[r]||[];const o=this.findCacheLayout(t,r);return o===-1?this._layouts[r].push(i):this._layouts[r][o]=i,this}findCacheLayout(t,r){var i,o;return((o=(i=this._layouts)==null?void 0:i[r])==null?void 0:o.findIndex(u=>u._id===t._id))??-1}removeNodeFromLayoutCache(t){if(this._layouts)for(let r=0;r0||navigator.msMaxTouchPoints>0);class fi{}function gu(l,t){l.touches.length>1||(l.cancelable&&l.preventDefault(),F.simulateMouseEvent(l.changedTouches[0],t))}function Qg(l,t){l.cancelable&&l.preventDefault(),F.simulateMouseEvent(l,t)}function vu(l){fi.touchHandled||(fi.touchHandled=!0,gu(l,"mousedown"))}function yu(l){fi.touchHandled&&gu(l,"mousemove")}function wu(l){if(!fi.touchHandled)return;fi.pointerLeaveTimeout&&(window.clearTimeout(fi.pointerLeaveTimeout),delete fi.pointerLeaveTimeout);const t=!!Le.dragElement;gu(l,"mouseup"),t||gu(l,"click"),fi.touchHandled=!1}function Su(l){l.pointerType!=="mouse"&&l.target.releasePointerCapture(l.pointerId)}function bm(l){Le.dragElement&&l.pointerType!=="mouse"&&Qg(l,"mouseenter")}function Tm(l){Le.dragElement&&l.pointerType!=="mouse"&&(fi.pointerLeaveTimeout=window.setTimeout(()=>{delete fi.pointerLeaveTimeout,Qg(l,"mouseleave")},10))}class Lu{constructor(t,r,i){this.host=t,this.dir=r,this.option=i,this.moving=!1,this._mouseDown=this._mouseDown.bind(this),this._mouseMove=this._mouseMove.bind(this),this._mouseUp=this._mouseUp.bind(this),this._keyEvent=this._keyEvent.bind(this),this._init()}_init(){const t=this.el=document.createElement("div");return t.classList.add("ui-resizable-handle"),t.classList.add(`${Lu.prefix}${this.dir}`),t.style.zIndex="100",t.style.userSelect="none",this.host.appendChild(this.el),this.el.addEventListener("mousedown",this._mouseDown),Qr&&(this.el.addEventListener("touchstart",vu),this.el.addEventListener("pointerdown",Su)),this}destroy(){return this.moving&&this._mouseUp(this.mouseDownEvent),this.el.removeEventListener("mousedown",this._mouseDown),Qr&&(this.el.removeEventListener("touchstart",vu),this.el.removeEventListener("pointerdown",Su)),this.host.removeChild(this.el),delete this.el,delete this.host,this}_mouseDown(t){this.mouseDownEvent=t,document.addEventListener("mousemove",this._mouseMove,{capture:!0,passive:!0}),document.addEventListener("mouseup",this._mouseUp,!0),Qr&&(this.el.addEventListener("touchmove",yu),this.el.addEventListener("touchend",wu)),t.stopPropagation(),t.preventDefault()}_mouseMove(t){const r=this.mouseDownEvent;this.moving?this._triggerEvent("move",t):Math.abs(t.x-r.x)+Math.abs(t.y-r.y)>2&&(this.moving=!0,this._triggerEvent("start",this.mouseDownEvent),this._triggerEvent("move",t),document.addEventListener("keydown",this._keyEvent)),t.stopPropagation()}_mouseUp(t){this.moving&&(this._triggerEvent("stop",t),document.removeEventListener("keydown",this._keyEvent)),document.removeEventListener("mousemove",this._mouseMove,!0),document.removeEventListener("mouseup",this._mouseUp,!0),Qr&&(this.el.removeEventListener("touchmove",yu),this.el.removeEventListener("touchend",wu)),delete this.moving,delete this.mouseDownEvent,t.stopPropagation(),t.preventDefault()}_keyEvent(t){var r,i;t.key==="Escape"&&((i=(r=this.host.gridstackNode)==null?void 0:r.grid)==null||i.engine.restoreInitial(),this._mouseUp(this.mouseDownEvent))}_triggerEvent(t,r){return this.option[t]&&this.option[t](r),this}}Lu.prefix="ui-resizable-";class cd{constructor(){this._eventRegister={}}get disabled(){return this._disabled}on(t,r){this._eventRegister[t]=r}off(t){delete this._eventRegister[t]}enable(){this._disabled=!1}disable(){this._disabled=!0}destroy(){delete this._eventRegister}triggerEvent(t,r){if(!this.disabled&&this._eventRegister&&this._eventRegister[t])return this._eventRegister[t](r)}}class Oo extends cd{constructor(t,r={}){super(),this.el=t,this.option=r,this.rectScale={x:1,y:1},this._ui=()=>{const o=this.el.parentElement.getBoundingClientRect(),u={width:this.originalRect.width,height:this.originalRect.height+this.scrolled,left:this.originalRect.left,top:this.originalRect.top-this.scrolled},c=this.temporalRect||u;return{position:{left:(c.left-o.left)*this.rectScale.x,top:(c.top-o.top)*this.rectScale.y},size:{width:c.width*this.rectScale.x,height:c.height*this.rectScale.y}}},this._mouseOver=this._mouseOver.bind(this),this._mouseOut=this._mouseOut.bind(this),this.enable(),this._setupAutoHide(this.option.autoHide),this._setupHandlers()}on(t,r){super.on(t,r)}off(t){super.off(t)}enable(){super.enable(),this.el.classList.remove("ui-resizable-disabled"),this._setupAutoHide(this.option.autoHide)}disable(){super.disable(),this.el.classList.add("ui-resizable-disabled"),this._setupAutoHide(!1)}destroy(){this._removeHandlers(),this._setupAutoHide(!1),delete this.el,super.destroy()}updateOption(t){const r=t.handles&&t.handles!==this.option.handles,i=t.autoHide&&t.autoHide!==this.option.autoHide;return Object.keys(t).forEach(o=>this.option[o]=t[o]),r&&(this._removeHandlers(),this._setupHandlers()),i&&this._setupAutoHide(this.option.autoHide),this}_setupAutoHide(t){return t?(this.el.classList.add("ui-resizable-autohide"),this.el.addEventListener("mouseover",this._mouseOver),this.el.addEventListener("mouseout",this._mouseOut)):(this.el.classList.remove("ui-resizable-autohide"),this.el.removeEventListener("mouseover",this._mouseOver),this.el.removeEventListener("mouseout",this._mouseOut),Le.overResizeElement===this&&delete Le.overResizeElement),this}_mouseOver(t){Le.overResizeElement||Le.dragElement||(Le.overResizeElement=this,this.el.classList.remove("ui-resizable-autohide"))}_mouseOut(t){Le.overResizeElement===this&&(delete Le.overResizeElement,this.el.classList.add("ui-resizable-autohide"))}_setupHandlers(){return this.handlers=this.option.handles.split(",").map(t=>t.trim()).map(t=>new Lu(this.el,t,{start:r=>{this._resizeStart(r)},stop:r=>{this._resizeStop(r)},move:r=>{this._resizing(r,t)}})),this}_resizeStart(t){this.sizeToContent=F.shouldSizeToContent(this.el.gridstackNode,!0),this.originalRect=this.el.getBoundingClientRect(),this.scrollEl=F.getScrollElement(this.el),this.scrollY=this.scrollEl.scrollTop,this.scrolled=0,this.startEvent=t,this._setupHelper(),this._applyChange();const r=F.initEvent(t,{type:"resizestart",target:this.el});return this.option.start&&this.option.start(r,this._ui()),this.el.classList.add("ui-resizable-resizing"),this.triggerEvent("resizestart",r),this}_resizing(t,r){this.scrolled=this.scrollEl.scrollTop-this.scrollY,this.temporalRect=this._getChange(t,r),this._applyChange();const i=F.initEvent(t,{type:"resize",target:this.el});return this.option.resize&&this.option.resize(i,this._ui()),this.triggerEvent("resize",i),this}_resizeStop(t){const r=F.initEvent(t,{type:"resizestop",target:this.el});return this.option.stop&&this.option.stop(r),this.el.classList.remove("ui-resizable-resizing"),this.triggerEvent("resizestop",r),this._cleanHelper(),delete this.startEvent,delete this.originalRect,delete this.temporalRect,delete this.scrollY,delete this.scrolled,this}_setupHelper(){this.elOriginStyleVal=Oo._originStyleProp.map(i=>this.el.style[i]),this.parentOriginStylePosition=this.el.parentElement.style.position;const t=this.el.parentElement,r=F.getValuesFromTransformedElement(t);return this.rectScale={x:r.xScale,y:r.yScale},getComputedStyle(this.el.parentElement).position.match(/static/)&&(this.el.parentElement.style.position="relative"),this.el.style.position="absolute",this.el.style.opacity="0.8",this}_cleanHelper(){return Oo._originStyleProp.forEach((t,r)=>{this.el.style[t]=this.elOriginStyleVal[r]||null}),this.el.parentElement.style.position=this.parentOriginStylePosition||null,this}_getChange(t,r){const i=this.startEvent,o={width:this.originalRect.width,height:this.originalRect.height+this.scrolled,left:this.originalRect.left,top:this.originalRect.top-this.scrolled},u=t.clientX-i.clientX,c=this.sizeToContent?0:t.clientY-i.clientY;let d,p;r.indexOf("e")>-1?o.width+=u:r.indexOf("w")>-1&&(o.width-=u,o.left+=u,d=!0),r.indexOf("s")>-1?o.height+=c:r.indexOf("n")>-1&&(o.height-=c,o.top+=c,p=!0);const g=this._constrainSize(o.width,o.height,d,p);return Math.round(o.width)!==Math.round(g.width)&&(r.indexOf("w")>-1&&(o.left+=o.width-g.width),o.width=g.width),Math.round(o.height)!==Math.round(g.height)&&(r.indexOf("n")>-1&&(o.top+=o.height-g.height),o.height=g.height),o}_constrainSize(t,r,i,o){const u=this.option,c=(i?u.maxWidthMoveLeft:u.maxWidth)||Number.MAX_SAFE_INTEGER,d=u.minWidth/this.rectScale.x||t,p=(o?u.maxHeightMoveUp:u.maxHeight)||Number.MAX_SAFE_INTEGER,g=u.minHeight/this.rectScale.y||r,y=Math.min(c,Math.max(d,t)),v=Math.min(p,Math.max(g,r));return{width:y,height:v}}_applyChange(){let t={left:0,top:0,width:0,height:0};if(this.el.style.position==="absolute"){const r=this.el.parentElement,{left:i,top:o}=r.getBoundingClientRect();t={left:i,top:o,width:0,height:0}}return this.temporalRect?(Object.keys(this.temporalRect).forEach(r=>{const i=this.temporalRect[r],o=r==="width"||r==="left"?this.rectScale.x:r==="height"||r==="top"?this.rectScale.y:1;this.el.style[r]=(i-t[r])*o+"px"}),this):this}_removeHandlers(){return this.handlers.forEach(t=>t.destroy()),delete this.handlers,this}}Oo._originStyleProp=["width","height","position","left","top","opacity","zIndex"];const LS='input,textarea,button,select,option,[contenteditable="true"],.ui-resizable-handle';class Lo extends cd{constructor(t,r={}){var u;super(),this.el=t,this.option=r,this.dragTransform={xScale:1,yScale:1,xOffset:0,yOffset:0};const i=(u=r==null?void 0:r.handle)==null?void 0:u.substring(1),o=t.gridstackNode;this.dragEls=!i||t.classList.contains(i)?[t]:o!=null&&o.subGrid?[t.querySelector(r.handle)||t]:Array.from(t.querySelectorAll(r.handle)),this.dragEls.length===0&&(this.dragEls=[t]),this._mouseDown=this._mouseDown.bind(this),this._mouseMove=this._mouseMove.bind(this),this._mouseUp=this._mouseUp.bind(this),this._keyEvent=this._keyEvent.bind(this),this.enable()}on(t,r){super.on(t,r)}off(t){super.off(t)}enable(){this.disabled!==!1&&(super.enable(),this.dragEls.forEach(t=>{t.addEventListener("mousedown",this._mouseDown),Qr&&(t.addEventListener("touchstart",vu),t.addEventListener("pointerdown",Su))}),this.el.classList.remove("ui-draggable-disabled"))}disable(t=!1){this.disabled!==!0&&(super.disable(),this.dragEls.forEach(r=>{r.removeEventListener("mousedown",this._mouseDown),Qr&&(r.removeEventListener("touchstart",vu),r.removeEventListener("pointerdown",Su))}),t||this.el.classList.add("ui-draggable-disabled"))}destroy(){this.dragTimeout&&window.clearTimeout(this.dragTimeout),delete this.dragTimeout,this.mouseDownEvent&&this._mouseUp(this.mouseDownEvent),this.disable(!0),delete this.el,delete this.helper,delete this.option,super.destroy()}updateOption(t){return Object.keys(t).forEach(r=>this.option[r]=t[r]),this}_mouseDown(t){if(!Le.mouseHandled)return t.button!==0||!this.dragEls.find(r=>r===t.target)&&t.target.closest(LS)||this.option.cancel&&t.target.closest(this.option.cancel)||(this.mouseDownEvent=t,delete this.dragging,delete Le.dragElement,delete Le.dropElement,document.addEventListener("mousemove",this._mouseMove,{capture:!0,passive:!0}),document.addEventListener("mouseup",this._mouseUp,!0),Qr&&(t.currentTarget.addEventListener("touchmove",yu),t.currentTarget.addEventListener("touchend",wu)),t.preventDefault(),document.activeElement&&document.activeElement.blur(),Le.mouseHandled=!0),!0}_callDrag(t){if(!this.dragging)return;const r=F.initEvent(t,{target:this.el,type:"drag"});this.option.drag&&this.option.drag(r,this.ui()),this.triggerEvent("drag",r)}_mouseMove(t){var i;const r=this.mouseDownEvent;if(this.lastDrag=t,this.dragging)if(this._dragFollow(t),Le.pauseDrag){const o=Number.isInteger(Le.pauseDrag)?Le.pauseDrag:100;this.dragTimeout&&window.clearTimeout(this.dragTimeout),this.dragTimeout=window.setTimeout(()=>this._callDrag(t),o)}else this._callDrag(t);else if(Math.abs(t.x-r.x)+Math.abs(t.y-r.y)>3){this.dragging=!0,Le.dragElement=this;const o=(i=this.el.gridstackNode)==null?void 0:i.grid;o?Le.dropElement=o.el.ddElement.ddDroppable:delete Le.dropElement,this.helper=this._createHelper(),this._setupHelperContainmentStyle(),this.dragTransform=F.getValuesFromTransformedElement(this.helperContainment),this.dragOffset=this._getDragOffset(t,this.el,this.helperContainment),this._setupHelperStyle(t);const u=F.initEvent(t,{target:this.el,type:"dragstart"});this.option.start&&this.option.start(u,this.ui()),this.triggerEvent("dragstart",u),document.addEventListener("keydown",this._keyEvent)}return!0}_mouseUp(t){var r,i;if(document.removeEventListener("mousemove",this._mouseMove,!0),document.removeEventListener("mouseup",this._mouseUp,!0),Qr&&t.currentTarget&&(t.currentTarget.removeEventListener("touchmove",yu,!0),t.currentTarget.removeEventListener("touchend",wu,!0)),this.dragging){delete this.dragging,(r=this.el.gridstackNode)==null||delete r._origRotate,document.removeEventListener("keydown",this._keyEvent),((i=Le.dropElement)==null?void 0:i.el)===this.el.parentElement&&delete Le.dropElement,this.helperContainment.style.position=this.parentOriginStylePosition||null,this.helper!==this.el&&this.helper.remove(),this._removeHelperStyle();const o=F.initEvent(t,{target:this.el,type:"dragstop"});this.option.stop&&this.option.stop(o),this.triggerEvent("dragstop",o),Le.dropElement&&Le.dropElement.drop(t)}delete this.helper,delete this.mouseDownEvent,delete Le.dragElement,delete Le.dropElement,delete Le.mouseHandled,t.preventDefault()}_keyEvent(t){var o,u;const r=this.el.gridstackNode,i=(r==null?void 0:r.grid)||((u=(o=Le.dropElement)==null?void 0:o.el)==null?void 0:u.gridstack);if(t.key==="Escape")r&&r._origRotate&&(r._orig=r._origRotate,delete r._origRotate),i==null||i.cancelDrag(),this._mouseUp(this.mouseDownEvent);else if(r&&i&&(t.key==="r"||t.key==="R")){if(!F.canBeRotated(r))return;r._origRotate=r._origRotate||{...r._orig},delete r._moving,i.setAnimation(!1).rotate(r.el,{top:-this.dragOffset.offsetTop,left:-this.dragOffset.offsetLeft}).setAnimation(),r._moving=!0,this.dragOffset=this._getDragOffset(this.lastDrag,r.el,this.helperContainment),this.helper.style.width=this.dragOffset.width+"px",this.helper.style.height=this.dragOffset.height+"px",F.swap(r._orig,"w","h"),delete r._rect,this._mouseMove(this.lastDrag)}}_createHelper(){let t=this.el;return typeof this.option.helper=="function"?t=this.option.helper(this.el):this.option.helper==="clone"&&(t=F.cloneNode(this.el)),t.parentElement||F.appendTo(t,this.option.appendTo==="parent"?this.el.parentElement:this.option.appendTo),this.dragElementOriginStyle=Lo.originStyleProp.map(r=>this.el.style[r]),t}_setupHelperStyle(t){this.helper.classList.add("ui-draggable-dragging");const r=this.helper.style;return r.pointerEvents="none",r.width=this.dragOffset.width+"px",r.height=this.dragOffset.height+"px",r.willChange="left, top",r.position="fixed",this._dragFollow(t),r.transition="none",setTimeout(()=>{this.helper&&(r.transition=null)},0),this}_removeHelperStyle(){var r;this.helper.classList.remove("ui-draggable-dragging");const t=(r=this.helper)==null?void 0:r.gridstackNode;if(!(t!=null&&t._isAboutToRemove)&&this.dragElementOriginStyle){const i=this.helper,o=this.dragElementOriginStyle.transition||null;i.style.transition=this.dragElementOriginStyle.transition="none",Lo.originStyleProp.forEach(u=>i.style[u]=this.dragElementOriginStyle[u]||null),setTimeout(()=>i.style.transition=o,50)}return delete this.dragElementOriginStyle,this}_dragFollow(t){const r={left:0,top:0},i=this.helper.style,o=this.dragOffset;i.left=(t.clientX+o.offsetLeft-r.left)*this.dragTransform.xScale+"px",i.top=(t.clientY+o.offsetTop-r.top)*this.dragTransform.yScale+"px"}_setupHelperContainmentStyle(){return this.helperContainment=this.helper.parentElement,this.helper.style.position!=="fixed"&&(this.parentOriginStylePosition=this.helperContainment.style.position,getComputedStyle(this.helperContainment).position.match(/static/)&&(this.helperContainment.style.position="relative")),this}_getDragOffset(t,r,i){let o=0,u=0;i&&(o=this.dragTransform.xOffset,u=this.dragTransform.yOffset);const c=r.getBoundingClientRect();return{left:c.left,top:c.top,offsetLeft:-t.clientX+c.left-o,offsetTop:-t.clientY+c.top-u,width:c.width*this.dragTransform.xScale,height:c.height*this.dragTransform.yScale}}ui(){const r=this.el.parentElement.getBoundingClientRect(),i=this.helper.getBoundingClientRect();return{position:{top:(i.top-r.top)*this.dragTransform.yScale,left:(i.left-r.left)*this.dragTransform.xScale}}}}Lo.originStyleProp=["width","height","transform","transform-origin","transition","pointerEvents","position","left","top","minWidth","willChange"];class PS extends cd{constructor(t,r={}){super(),this.el=t,this.option=r,this._mouseEnter=this._mouseEnter.bind(this),this._mouseLeave=this._mouseLeave.bind(this),this.enable(),this._setupAccept()}on(t,r){super.on(t,r)}off(t){super.off(t)}enable(){this.disabled!==!1&&(super.enable(),this.el.classList.add("ui-droppable"),this.el.classList.remove("ui-droppable-disabled"),this.el.addEventListener("mouseenter",this._mouseEnter),this.el.addEventListener("mouseleave",this._mouseLeave),Qr&&(this.el.addEventListener("pointerenter",bm),this.el.addEventListener("pointerleave",Tm)))}disable(t=!1){this.disabled!==!0&&(super.disable(),this.el.classList.remove("ui-droppable"),t||this.el.classList.add("ui-droppable-disabled"),this.el.removeEventListener("mouseenter",this._mouseEnter),this.el.removeEventListener("mouseleave",this._mouseLeave),Qr&&(this.el.removeEventListener("pointerenter",bm),this.el.removeEventListener("pointerleave",Tm)))}destroy(){this.disable(!0),this.el.classList.remove("ui-droppable"),this.el.classList.remove("ui-droppable-disabled"),super.destroy()}updateOption(t){return Object.keys(t).forEach(r=>this.option[r]=t[r]),this._setupAccept(),this}_mouseEnter(t){if(!Le.dragElement||!this._canDrop(Le.dragElement.el))return;t.preventDefault(),t.stopPropagation(),Le.dropElement&&Le.dropElement!==this&&Le.dropElement._mouseLeave(t,!0),Le.dropElement=this;const r=F.initEvent(t,{target:this.el,type:"dropover"});this.option.over&&this.option.over(r,this._ui(Le.dragElement)),this.triggerEvent("dropover",r),this.el.classList.add("ui-droppable-over")}_mouseLeave(t,r=!1){var o;if(!Le.dragElement||Le.dropElement!==this)return;t.preventDefault(),t.stopPropagation();const i=F.initEvent(t,{target:this.el,type:"dropout"});if(this.option.out&&this.option.out(i,this._ui(Le.dragElement)),this.triggerEvent("dropout",i),Le.dropElement===this&&(delete Le.dropElement,!r)){let u,c=this.el.parentElement;for(;!u&&c;)u=(o=c.ddElement)==null?void 0:o.ddDroppable,c=c.parentElement;u&&u._mouseEnter(t)}}drop(t){t.preventDefault();const r=F.initEvent(t,{target:this.el,type:"drop"});this.option.drop&&this.option.drop(r,this._ui(Le.dragElement)),this.triggerEvent("drop",r)}_canDrop(t){return t&&(!this.accept||this.accept(t))}_setupAccept(){return this.option.accept?(typeof this.option.accept=="string"?this.accept=t=>t.classList.contains(this.option.accept)||t.matches(this.option.accept):this.accept=this.option.accept,this):this}_ui(t){return{draggable:t.el,...t.ui()}}}class fd{static init(t){return t.ddElement||(t.ddElement=new fd(t)),t.ddElement}constructor(t){this.el=t}on(t,r){return this.ddDraggable&&["drag","dragstart","dragstop"].indexOf(t)>-1?this.ddDraggable.on(t,r):this.ddDroppable&&["drop","dropover","dropout"].indexOf(t)>-1?this.ddDroppable.on(t,r):this.ddResizable&&["resizestart","resize","resizestop"].indexOf(t)>-1&&this.ddResizable.on(t,r),this}off(t){return this.ddDraggable&&["drag","dragstart","dragstop"].indexOf(t)>-1?this.ddDraggable.off(t):this.ddDroppable&&["drop","dropover","dropout"].indexOf(t)>-1?this.ddDroppable.off(t):this.ddResizable&&["resizestart","resize","resizestop"].indexOf(t)>-1&&this.ddResizable.off(t),this}setupDraggable(t){return this.ddDraggable?this.ddDraggable.updateOption(t):this.ddDraggable=new Lo(this.el,t),this}cleanDraggable(){return this.ddDraggable&&(this.ddDraggable.destroy(),delete this.ddDraggable),this}setupResizable(t){return this.ddResizable?this.ddResizable.updateOption(t):this.ddResizable=new Oo(this.el,t),this}cleanResizable(){return this.ddResizable&&(this.ddResizable.destroy(),delete this.ddResizable),this}setupDroppable(t){return this.ddDroppable?this.ddDroppable.updateOption(t):this.ddDroppable=new PS(this.el,t),this}cleanDroppable(){return this.ddDroppable&&(this.ddDroppable.destroy(),delete this.ddDroppable),this}}class AS{resizable(t,r,i,o){return this._getDDElements(t,r).forEach(u=>{if(r==="disable"||r==="enable")u.ddResizable&&u.ddResizable[r]();else if(r==="destroy")u.ddResizable&&u.cleanResizable();else if(r==="option")u.setupResizable({[i]:o});else{const d=u.el.gridstackNode.grid;let p=u.el.getAttribute("gs-resize-handles")||d.opts.resizable.handles||"e,s,se";p==="all"&&(p="n,e,s,w,se,sw,ne,nw");const g=!d.opts.alwaysShowResizeHandle;u.setupResizable({...d.opts.resizable,handles:p,autoHide:g,start:r.start,stop:r.stop,resize:r.resize})}}),this}draggable(t,r,i,o){return this._getDDElements(t,r).forEach(u=>{if(r==="disable"||r==="enable")u.ddDraggable&&u.ddDraggable[r]();else if(r==="destroy")u.ddDraggable&&u.cleanDraggable();else if(r==="option")u.setupDraggable({[i]:o});else{const c=u.el.gridstackNode.grid;u.setupDraggable({...c.opts.draggable,start:r.start,stop:r.stop,drag:r.drag})}}),this}dragIn(t,r){return this._getDDElements(t).forEach(i=>i.setupDraggable(r)),this}droppable(t,r,i,o){return typeof r.accept=="function"&&!r._accept&&(r._accept=r.accept,r.accept=u=>r._accept(u)),this._getDDElements(t,r).forEach(u=>{r==="disable"||r==="enable"?u.ddDroppable&&u.ddDroppable[r]():r==="destroy"?u.ddDroppable&&u.cleanDroppable():r==="option"?u.setupDroppable({[i]:o}):u.setupDroppable(r)}),this}isDroppable(t){var r;return!!((r=t==null?void 0:t.ddElement)!=null&&r.ddDroppable&&!t.ddElement.ddDroppable.disabled)}isDraggable(t){var r;return!!((r=t==null?void 0:t.ddElement)!=null&&r.ddDraggable&&!t.ddElement.ddDraggable.disabled)}isResizable(t){var r;return!!((r=t==null?void 0:t.ddElement)!=null&&r.ddResizable&&!t.ddElement.ddResizable.disabled)}on(t,r,i){return this._getDDElements(t).forEach(o=>o.on(r,u=>{i(u,Le.dragElement?Le.dragElement.el:u.target,Le.dragElement?Le.dragElement.helper:null)})),this}off(t,r){return this._getDDElements(t).forEach(i=>i.off(r)),this}_getDDElements(t,r){const i=t.gridstack||r!=="destroy"&&r!=="disable",o=F.getElements(t);return o.length?o.map(c=>c.ddElement||(i?fd.init(c):null)).filter(c=>c):[]}}/*! + * GridStack 11.5.1 + * https://gridstackjs.com/ + * + * Copyright (c) 2021-2024 Alain Dumesny + * see root license https://github.com/gridstack/gridstack.js/tree/master/LICENSE + */const Gn=new AS;class Te{static init(t={},r=".grid-stack"){if(typeof document>"u")return null;const i=Te.getGridElement(r);return i?(i.gridstack||(i.gridstack=new Te(i,F.cloneDeep(t))),i.gridstack):(console.error(typeof r=="string"?'GridStack.initAll() no grid was found with selector "'+r+`" - element missing or wrong selector ? +Note: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.`:"GridStack.init() no grid element was passed."),null)}static initAll(t={},r=".grid-stack"){const i=[];return typeof document>"u"||(Te.getGridElements(r).forEach(o=>{o.gridstack||(o.gridstack=new Te(o,F.cloneDeep(t))),i.push(o.gridstack)}),i.length===0&&console.error('GridStack.initAll() no grid was found with selector "'+r+`" - element missing or wrong selector ? +Note: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.`)),i}static addGrid(t,r={}){if(!t)return null;let i=t;if(i.gridstack){const c=i.gridstack;return r&&(c.opts={...c.opts,...r}),r.children!==void 0&&c.load(r.children),c}return(!t.classList.contains("grid-stack")||Te.addRemoveCB)&&(Te.addRemoveCB?i=Te.addRemoveCB(t,r,!0,!0):i=F.createDiv(["grid-stack",r.class],t)),Te.init(r,i)}static registerEngine(t){Te.engineClass=t}get placeholder(){if(!this._placeholder){this._placeholder=F.createDiv([this.opts.placeholderClass,wr.itemClass,this.opts.itemClass]);const t=F.createDiv(["placeholder-content"],this._placeholder);this.opts.placeholderText&&(t.textContent=this.opts.placeholderText)}return this._placeholder}constructor(t,r={}){var v,x,T;this.el=t,this.opts=r,this.animationDelay=310,this._gsEventHandler={},this._extraDragRow=0,this.dragTransform={xScale:1,yScale:1,xOffset:0,yOffset:0},t.gridstack=this,this.opts=r=r||{},t.classList.contains("grid-stack")||this.el.classList.add("grid-stack"),r.row&&(r.minRow=r.maxRow=r.row,delete r.row);const i=F.toNumber(t.getAttribute("gs-row"));r.column==="auto"&&delete r.column,r.alwaysShowResizeHandle!==void 0&&(r._alwaysShowResizeHandle=r.alwaysShowResizeHandle);let o=(v=r.columnOpts)==null?void 0:v.breakpoints;const u=r;if(u.oneColumnModeDomSort&&(delete u.oneColumnModeDomSort,console.log("warning: Gridstack oneColumnModeDomSort no longer supported. Use GridStackOptions.columnOpts instead.")),u.oneColumnSize||u.disableOneColumnMode===!1){const N=u.oneColumnSize||768;delete u.oneColumnSize,delete u.disableOneColumnMode,r.columnOpts=r.columnOpts||{},o=r.columnOpts.breakpoints=r.columnOpts.breakpoints||[];let C=o.find(L=>L.c===1);C?C.w=N:(C={c:1,w:N},o.push(C,{c:12,w:N+1}))}const c=r.columnOpts;c&&(!c.columnWidth&&!((x=c.breakpoints)!=null&&x.length)?(delete r.columnOpts,o=void 0):c.columnMax=c.columnMax||12),(o==null?void 0:o.length)>1&&o.sort((N,C)=>(C.w||0)-(N.w||0));const d={...F.cloneDeep(wr),column:F.toNumber(t.getAttribute("gs-column"))||wr.column,minRow:i||F.toNumber(t.getAttribute("gs-min-row"))||wr.minRow,maxRow:i||F.toNumber(t.getAttribute("gs-max-row"))||wr.maxRow,staticGrid:F.toBool(t.getAttribute("gs-static"))||wr.staticGrid,sizeToContent:F.toBool(t.getAttribute("gs-size-to-content"))||void 0,draggable:{handle:(r.handleClass?"."+r.handleClass:r.handle?r.handle:"")||wr.draggable.handle},removableOptions:{accept:r.itemClass||wr.removableOptions.accept,decline:wr.removableOptions.decline}};t.getAttribute("gs-animate")&&(d.animate=F.toBool(t.getAttribute("gs-animate"))),r=F.defaults(r,d),this._initMargin(),this.checkDynamicColumn(),this.el.classList.add("gs-"+r.column),r.rtl==="auto"&&(r.rtl=t.style.direction==="rtl"),r.rtl&&this.el.classList.add("grid-stack-rtl");const p=this.el.closest("."+wr.itemClass),g=p==null?void 0:p.gridstackNode;g&&(g.subGrid=this,this.parentGridNode=g,this.el.classList.add("grid-stack-nested"),g.el.classList.add("grid-stack-sub-grid")),this._isAutoCellHeight=r.cellHeight==="auto",this._isAutoCellHeight||r.cellHeight==="initial"?this.cellHeight(void 0,!1):(typeof r.cellHeight=="number"&&r.cellHeightUnit&&r.cellHeightUnit!==wr.cellHeightUnit&&(r.cellHeight=r.cellHeight+r.cellHeightUnit,delete r.cellHeightUnit),this.cellHeight(r.cellHeight,!1)),r.alwaysShowResizeHandle==="mobile"&&(r.alwaysShowResizeHandle=Qr),this._styleSheetClass="gs-id-"+ci._idSeq++,this.el.classList.add(this._styleSheetClass),this._setStaticClass();const y=r.engineClass||Te.engineClass||ci;if(this.engine=new y({column:this.getColumn(),float:r.float,maxRow:r.maxRow,onChange:N=>{let C=0;this.engine.nodes.forEach(L=>{C=Math.max(C,L.y+L.h)}),N.forEach(L=>{const U=L.el;U&&(L._removeDOM?(U&&U.remove(),delete L._removeDOM):this._writePosAttr(U,L))}),this._updateStyles(!1,C)}}),this._updateStyles(!1,0),r.auto&&(this.batchUpdate(),this.engine._loading=!0,this.getGridItems().forEach(N=>this._prepareElement(N)),delete this.engine._loading,this.batchUpdate(!1)),r.children){const N=r.children;delete r.children,N.length&&this.load(N)}this.setAnimation(),r.subGridDynamic&&!Le.pauseDrag&&(Le.pauseDrag=!0),((T=r.draggable)==null?void 0:T.pause)!==void 0&&(Le.pauseDrag=r.draggable.pause),this._setupRemoveDrop(),this._setupAcceptWidget(),this._updateResizeEvent()}addWidget(t){if(typeof t=="string"){console.error("V11: GridStack.addWidget() does not support string anymore. see #2736");return}if(t.ELEMENT_NODE)return console.error("V11: GridStack.addWidget() does not support HTMLElement anymore. use makeWidget()"),this.makeWidget(t);let r,i=t;if(i.grid=this,i!=null&&i.el?r=i.el:Te.addRemoveCB?r=Te.addRemoveCB(this.el,t,!0,!1):r=this.createWidgetDivs(i),!r)return;if(i=r.gridstackNode,i&&r.parentElement===this.el&&this.engine.nodes.find(u=>u._id===i._id))return r;const o=this._readAttr(r);return F.defaults(t,o),this.engine.prepareNode(t),this.el.appendChild(r),this.makeWidget(r,t),r}createWidgetDivs(t){const r=F.createDiv(["grid-stack-item",this.opts.itemClass]),i=F.createDiv(["grid-stack-item-content"],r);return F.lazyLoad(t)?t.visibleObservable||(t.visibleObservable=new IntersectionObserver(([o])=>{var u,c;o.isIntersecting&&((u=t.visibleObservable)==null||u.disconnect(),delete t.visibleObservable,Te.renderCB(i,t),(c=t.grid)==null||c.prepareDragDrop(t.el))}),window.setTimeout(()=>{var o;return(o=t.visibleObservable)==null?void 0:o.observe(r)})):Te.renderCB(i,t),r}makeSubGrid(t,r,i,o=!0){var T,N,C;let u=t.gridstackNode;if(u||(u=this.makeWidget(t).gridstackNode),(T=u.subGrid)!=null&&T.el)return u.subGrid;let c,d=this;for(;d&&!c;)c=(N=d.opts)==null?void 0:N.subGridOpts,d=(C=d.parentGridNode)==null?void 0:C.grid;r=F.cloneDeep({...this.opts,id:void 0,children:void 0,column:"auto",columnOpts:void 0,layout:"list",subGridOpts:void 0,...c||{},...r||u.subGridOpts||{}}),u.subGridOpts=r;let p;r.column==="auto"&&(p=!0,r.column=Math.max(u.w||1,(i==null?void 0:i.w)||1),delete r.columnOpts);let g=u.el.querySelector(".grid-stack-item-content"),y,v;if(o&&(this._removeDD(u.el),v={...u,x:0,y:0},F.removeInternalForSave(v),delete v.subGridOpts,u.content&&(v.content=u.content,delete u.content),Te.addRemoveCB?y=Te.addRemoveCB(this.el,v,!0,!1):(y=F.createDiv(["grid-stack-item"]),y.appendChild(g),g=F.createDiv(["grid-stack-item-content"],u.el)),this.prepareDragDrop(u.el)),i){const L=p?r.column:u.w,U=u.h+i.h,A=u.el.style;A.transition="none",this.update(u.el,{w:L,h:U}),setTimeout(()=>A.transition=null)}const x=u.subGrid=Te.addGrid(g,r);return i!=null&&i._moving&&(x._isTemp=!0),p&&(x._autoColumn=!0),o&&x.makeWidget(y,v),i&&(i._moving?window.setTimeout(()=>F.simulateMouseEvent(i._event,"mouseenter",x.el),0):x.makeWidget(u.el,u)),this.resizeToContentCheck(!1,u),x}removeAsSubGrid(t){var i;const r=(i=this.parentGridNode)==null?void 0:i.grid;r&&(r.batchUpdate(),r.removeWidget(this.parentGridNode.el,!0,!0),this.engine.nodes.forEach(o=>{o.x+=this.parentGridNode.x,o.y+=this.parentGridNode.y,r.makeWidget(o.el,o)}),r.batchUpdate(!1),this.parentGridNode&&delete this.parentGridNode.subGrid,delete this.parentGridNode,t&&window.setTimeout(()=>F.simulateMouseEvent(t._event,"mouseenter",r.el),0))}save(t=!0,r=!1,i=Te.saveCB){const o=this.engine.save(t,i);if(o.forEach(u=>{var c;if(t&&u.el&&!u.subGrid&&!i){const d=u.el.querySelector(".grid-stack-item-content");u.content=d==null?void 0:d.innerHTML,u.content||delete u.content}else if(!t&&!i&&delete u.content,(c=u.subGrid)!=null&&c.el){const d=u.subGrid.save(t,r,i);u.subGridOpts=r?d:{children:d},delete u.subGrid}delete u.el}),r){const u=F.cloneDeep(this.opts);u.marginBottom===u.marginTop&&u.marginRight===u.marginLeft&&u.marginTop===u.marginRight&&(u.margin=u.marginTop,delete u.marginTop,delete u.marginRight,delete u.marginBottom,delete u.marginLeft),u.rtl===(this.el.style.direction==="rtl")&&(u.rtl="auto"),this._isAutoCellHeight&&(u.cellHeight="auto"),this._autoColumn&&(u.column="auto");const c=u._alwaysShowResizeHandle;return delete u._alwaysShowResizeHandle,c!==void 0?u.alwaysShowResizeHandle=c:delete u.alwaysShowResizeHandle,F.removeInternalAndSame(u,wr),u.children=o,u}return o}load(t,r=Te.addRemoveCB||!0){var g;t=F.cloneDeep(t);const i=this.getColumn();t.forEach(y=>{y.w=y.w||1,y.h=y.h||1}),t=F.sort(t),this.engine.skipCacheUpdate=this._ignoreLayoutsNodeChange=!0;let o=0;t.forEach(y=>{o=Math.max(o,(y.x||0)+y.w)}),o>this.engine.defaultColumn&&(this.engine.defaultColumn=o),o>i&&this.engine.cacheLayout(t,o,!0);const u=Te.addRemoveCB;typeof r=="function"&&(Te.addRemoveCB=r);const c=[];this.batchUpdate();const d=!this.engine.nodes.length;d&&this.setAnimation(!1),!d&&r&&[...this.engine.nodes].forEach(v=>{if(!v.id)return;F.find(t,v.id)||(Te.addRemoveCB&&Te.addRemoveCB(this.el,v,!1,!1),c.push(v),this.removeWidget(v.el,!0,!1))}),this.engine._loading=!0;const p=[];return this.engine.nodes=this.engine.nodes.filter(y=>F.find(t,y.id)?(p.push(y),!1):!0),t.forEach(y=>{var x;const v=F.find(p,y.id);if(v){if(F.shouldSizeToContent(v)&&(y.h=v.h),this.engine.nodeBoundFix(y),(y.autoPosition||y.x===void 0||y.y===void 0)&&(y.w=y.w||v.w,y.h=y.h||v.h,this.engine.findEmptyPosition(y)),this.engine.nodes.push(v),F.samePos(v,y)&&this.engine.nodes.length>1&&(this.moveNode(v,{...y,forceCollide:!0}),F.copyPos(y,v)),this.update(v.el,y),(x=y.subGridOpts)!=null&&x.children){const T=v.el.querySelector(".grid-stack");T&&T.gridstack&&T.gridstack.load(y.subGridOpts.children)}}else r&&this.addWidget(y)}),delete this.engine._loading,this.engine.removedNodes=c,this.batchUpdate(!1),delete this._ignoreLayoutsNodeChange,delete this.engine.skipCacheUpdate,u?Te.addRemoveCB=u:delete Te.addRemoveCB,d&&((g=this.opts)!=null&&g.animate)&&this.setAnimation(this.opts.animate,!0),this}batchUpdate(t=!0){return this.engine.batchUpdate(t),t||(this._updateContainerHeight(),this._triggerRemoveEvent(),this._triggerAddEvent(),this._triggerChangeEvent()),this}getCellHeight(t=!1){if(this.opts.cellHeight&&this.opts.cellHeight!=="auto"&&(!t||!this.opts.cellHeightUnit||this.opts.cellHeightUnit==="px"))return this.opts.cellHeight;if(this.opts.cellHeightUnit==="rem")return this.opts.cellHeight*parseFloat(getComputedStyle(document.documentElement).fontSize);if(this.opts.cellHeightUnit==="em")return this.opts.cellHeight*parseFloat(getComputedStyle(this.el).fontSize);if(this.opts.cellHeightUnit==="cm")return this.opts.cellHeight*(96/2.54);if(this.opts.cellHeightUnit==="mm")return this.opts.cellHeight*(96/2.54)/10;const r=this.el.querySelector("."+this.opts.itemClass);if(r){const o=F.toNumber(r.getAttribute("gs-h"))||1;return Math.round(r.offsetHeight/o)}const i=parseInt(this.el.getAttribute("gs-current-row"));return i?Math.round(this.el.getBoundingClientRect().height/i):this.opts.cellHeight}cellHeight(t,r=!0){if(r&&t!==void 0&&this._isAutoCellHeight!==(t==="auto")&&(this._isAutoCellHeight=t==="auto",this._updateResizeEvent()),(t==="initial"||t==="auto")&&(t=void 0),t===void 0){const o=-this.opts.marginRight-this.opts.marginLeft+this.opts.marginTop+this.opts.marginBottom;t=this.cellWidth()+o}const i=F.parseHeight(t);return this.opts.cellHeightUnit===i.unit&&this.opts.cellHeight===i.h?this:(this.opts.cellHeightUnit=i.unit,this.opts.cellHeight=i.h,this.resizeToContentCheck(),r&&this._updateStyles(!0),this)}cellWidth(){return this._widthOrContainer()/this.getColumn()}_widthOrContainer(t=!1){var r;return t&&((r=this.opts.columnOpts)!=null&&r.breakpointForWindow)?window.innerWidth:this.el.clientWidth||this.el.parentElement.clientWidth||window.innerWidth}checkDynamicColumn(){var u,c;const t=this.opts.columnOpts;if(!t||!t.columnWidth&&!((u=t.breakpoints)!=null&&u.length))return!1;const r=this.getColumn();let i=r;const o=this._widthOrContainer(!0);if(t.columnWidth)i=Math.min(Math.round(o/t.columnWidth)||1,t.columnMax);else{i=t.columnMax;let d=0;for(;dp.c===i);return this.column(i,(d==null?void 0:d.layout)||t.layout),!0}return!1}compact(t="compact",r=!0){return this.engine.compact(t,r),this._triggerChangeEvent(),this}column(t,r="moveScale"){if(!t||t<1||this.opts.column===t)return this;const i=this.getColumn();return this.opts.column=t,this.engine?(this.engine.column=t,this.el.classList.remove("gs-"+i),this.el.classList.add("gs-"+t),this.engine.columnChanged(i,t,r),this._isAutoCellHeight&&this.cellHeight(),this.resizeToContentCheck(!0),this._ignoreLayoutsNodeChange=!0,this._triggerChangeEvent(),delete this._ignoreLayoutsNodeChange,this):this}getColumn(){return this.opts.column}getGridItems(){return Array.from(this.el.children).filter(t=>t.matches("."+this.opts.itemClass)&&!t.matches("."+this.opts.placeholderClass))}isIgnoreChangeCB(){return this._ignoreLayoutsNodeChange}destroy(t=!0){var r,i;if(this.el)return this.offAll(),this._updateResizeEvent(!0),this.setStatic(!0,!1),this.setAnimation(!1),t?this.el.parentNode.removeChild(this.el):(this.removeAll(t),this.el.classList.remove(this._styleSheetClass),this.el.removeAttribute("gs-current-row")),this._removeStylesheet(),(r=this.parentGridNode)==null||delete r.subGrid,delete this.parentGridNode,delete this.opts,(i=this._placeholder)==null||delete i.gridstackNode,delete this._placeholder,delete this.engine,delete this.el.gridstack,delete this.el,this}float(t){return this.opts.float!==t&&(this.opts.float=this.engine.float=t,this._triggerChangeEvent()),this}getFloat(){return this.engine.float}getCellFromPixel(t,r=!1){const i=this.el.getBoundingClientRect();let o;r?o={top:i.top+document.documentElement.scrollTop,left:i.left}:o={top:this.el.offsetTop,left:this.el.offsetLeft};const u=t.left-o.left,c=t.top-o.top,d=i.width/this.getColumn(),p=i.height/parseInt(this.el.getAttribute("gs-current-row"));return{x:Math.floor(u/d),y:Math.floor(c/p)}}getRow(){return Math.max(this.engine.getRow(),this.opts.minRow)}isAreaEmpty(t,r,i,o){return this.engine.isAreaEmpty(t,r,i,o)}makeWidget(t,r){const i=Te.getElement(t);if(!i)return;i.parentElement||this.el.appendChild(i),this._prepareElement(i,!0,r);const o=i.gridstackNode;this._updateContainerHeight(),o.subGridOpts&&this.makeSubGrid(i,o.subGridOpts,void 0,!1);let u;return this.opts.column===1&&!this._ignoreLayoutsNodeChange&&(u=this._ignoreLayoutsNodeChange=!0),this._triggerAddEvent(),this._triggerChangeEvent(),u&&delete this._ignoreLayoutsNodeChange,i}on(t,r){return t.indexOf(" ")!==-1?(t.split(" ").forEach(o=>this.on(o,r)),this):(t==="change"||t==="added"||t==="removed"||t==="enable"||t==="disable"?(t==="enable"||t==="disable"?this._gsEventHandler[t]=o=>r(o):this._gsEventHandler[t]=o=>{o.detail&&r(o,o.detail)},this.el.addEventListener(t,this._gsEventHandler[t])):t==="drag"||t==="dragstart"||t==="dragstop"||t==="resizestart"||t==="resize"||t==="resizestop"||t==="dropped"||t==="resizecontent"?this._gsEventHandler[t]=r:console.error("GridStack.on("+t+") event not supported"),this)}off(t){return t.indexOf(" ")!==-1?(t.split(" ").forEach(i=>this.off(i)),this):((t==="change"||t==="added"||t==="removed"||t==="enable"||t==="disable")&&this._gsEventHandler[t]&&this.el.removeEventListener(t,this._gsEventHandler[t]),delete this._gsEventHandler[t],this)}offAll(){return Object.keys(this._gsEventHandler).forEach(t=>this.off(t)),this}removeWidget(t,r=!0,i=!0){return t?(Te.getElements(t).forEach(o=>{if(o.parentElement&&o.parentElement!==this.el)return;let u=o.gridstackNode;u||(u=this.engine.nodes.find(c=>o===c.el)),u&&(r&&Te.addRemoveCB&&Te.addRemoveCB(this.el,u,!1,!1),delete o.gridstackNode,this._removeDD(o),this.engine.removeNode(u,r,i),r&&o.parentElement&&o.remove())}),i&&(this._triggerRemoveEvent(),this._triggerChangeEvent()),this):(console.error("Error: GridStack.removeWidget(undefined) called"),this)}removeAll(t=!0,r=!0){return this.engine.nodes.forEach(i=>{t&&Te.addRemoveCB&&Te.addRemoveCB(this.el,i,!1,!1),delete i.el.gridstackNode,this.opts.staticGrid||this._removeDD(i.el)}),this.engine.removeAll(t,r),r&&this._triggerRemoveEvent(),this}setAnimation(t=this.opts.animate,r){return r?setTimeout(()=>{this.opts&&this.setAnimation(t)}):t?this.el.classList.add("grid-stack-animate"):this.el.classList.remove("grid-stack-animate"),this}hasAnimationCSS(){return this.el.classList.contains("grid-stack-animate")}setStatic(t,r=!0,i=!0){return!!this.opts.staticGrid===t?this:(t?this.opts.staticGrid=!0:delete this.opts.staticGrid,this._setupRemoveDrop(),this._setupAcceptWidget(),this.engine.nodes.forEach(o=>{this.prepareDragDrop(o.el),o.subGrid&&i&&o.subGrid.setStatic(t,r,i)}),r&&this._setStaticClass(),this)}updateOptions(t){var i;const r=this.opts;return t.acceptWidgets!==void 0&&this._setupAcceptWidget(),t.animate!==void 0&&this.setAnimation(),t.cellHeight&&(this.cellHeight(t.cellHeight,!0),delete t.cellHeight),t.class&&t.class!==r.class&&(r.class&&this.el.classList.remove(r.class),this.el.classList.add(t.class)),typeof t.column=="number"&&!t.columnOpts&&(this.column(t.column),delete t.column),t.margin!==void 0&&this.margin(t.margin),t.staticGrid!==void 0&&this.setStatic(t.staticGrid),t.disableDrag!==void 0&&!t.staticGrid&&this.enableMove(!t.disableDrag),t.disableResize!==void 0&&!t.staticGrid&&this.enableResize(!t.disableResize),t.float!==void 0&&this.float(t.float),t.row!==void 0&&(r.minRow=r.maxRow=t.row),(i=t.children)!=null&&i.length&&(this.load(t.children),delete t.children),this.opts={...this.opts,...t},this}update(t,r){return Te.getElements(t).forEach(i=>{var y;const o=i==null?void 0:i.gridstackNode;if(!o)return;const u={...F.copyPos({},o),...F.cloneDeep(r)};this.engine.nodeBoundFix(u),delete u.autoPosition;const c=["x","y","w","h"];let d;if(c.some(v=>u[v]!==void 0&&u[v]!==o[v])&&(d={},c.forEach(v=>{d[v]=u[v]!==void 0?u[v]:o[v],delete u[v]})),!d&&(u.minW||u.minH||u.maxW||u.maxH)&&(d={}),u.content!==void 0){const v=i.querySelector(".grid-stack-item-content");v&&v.textContent!==u.content&&(o.content=u.content,Te.renderCB(v,u),(y=o.subGrid)!=null&&y.el&&(v.appendChild(o.subGrid.el),o.subGrid.opts.styleInHead||o.subGrid._updateStyles(!0))),delete u.content}let p=!1,g=!1;for(const v in u)v[0]!=="_"&&o[v]!==u[v]&&(o[v]=u[v],p=!0,g=g||!this.opts.staticGrid&&(v==="noResize"||v==="noMove"||v==="locked"));if(F.sanitizeMinMax(o),d){const v=d.w!==void 0&&d.w!==o.w;this.moveNode(o,d),v&&o.subGrid?o.subGrid.onResize(this.hasAnimationCSS()?o.w:void 0):this.resizeToContentCheck(v,o),delete o._orig}(d||p)&&this._writeAttr(i,o),g&&this.prepareDragDrop(o.el)}),this}moveNode(t,r){const i=t._updating;i||this.engine.cleanNodes().beginUpdate(t),this.engine.moveNode(t,r),this._updateContainerHeight(),i||(this._triggerChangeEvent(),this.engine.endUpdate())}resizeToContent(t){var x,T;if(!t||(t.classList.remove("size-to-content-max"),!t.clientHeight))return;const r=t.gridstackNode;if(!r)return;const i=r.grid;if(!i||t.parentElement!==i.el)return;const o=i.getCellHeight(!0);if(!o)return;let u=r.h?r.h*o:t.clientHeight,c;if(r.resizeToContentParent&&(c=t.querySelector(r.resizeToContentParent)),c||(c=t.querySelector(Te.resizeToContentParent)),!c)return;const d=t.clientHeight-c.clientHeight,p=r.h?r.h*o-d:c.clientHeight;let g;if(r.subGrid){g=r.subGrid.getRow()*r.subGrid.getCellHeight(!0);const N=r.subGrid.el.getBoundingClientRect(),C=r.subGrid.el.parentElement.getBoundingClientRect();g+=N.top-C.top}else{if((T=(x=r.subGridOpts)==null?void 0:x.children)!=null&&T.length)return;{const N=c.firstElementChild;if(!N){console.error(`Error: GridStack.resizeToContent() widget id:${r.id} '${Te.resizeToContentParent}'.firstElementChild is null, make sure to have a div like container. Skipping sizing.`);return}g=N.getBoundingClientRect().height||p}}if(p===g)return;u+=g-p;let y=Math.ceil(u/o);const v=Number.isInteger(r.sizeToContent)?r.sizeToContent:0;v&&y>v&&(y=v,t.classList.add("size-to-content-max")),r.minH&&yr.maxH&&(y=r.maxH),y!==r.h&&(i._ignoreLayoutsNodeChange=!0,i.moveNode(r,{h:y}),delete i._ignoreLayoutsNodeChange)}resizeToContentCBCheck(t){Te.resizeToContentCB?Te.resizeToContentCB(t):this.resizeToContent(t)}rotate(t,r){return Te.getElements(t).forEach(i=>{const o=i.gridstackNode;if(!F.canBeRotated(o))return;const u={w:o.h,h:o.w,minH:o.minW,minW:o.minH,maxH:o.maxW,maxW:o.maxH};if(r){const d=r.left>0?Math.floor(r.left/this.cellWidth()):0,p=r.top>0?Math.floor(r.top/this.opts.cellHeight):0;u.x=o.x+d-(o.h-(p+1)),u.y=o.y+p-d}Object.keys(u).forEach(d=>{u[d]===void 0&&delete u[d]});const c=o._orig;this.update(i,u),o._orig=c}),this}margin(t){if(!(typeof t=="string"&&t.split(" ").length>1)){const i=F.parseHeight(t);if(this.opts.marginUnit===i.unit&&this.opts.margin===i.h)return}return this.opts.margin=t,this.opts.marginTop=this.opts.marginBottom=this.opts.marginLeft=this.opts.marginRight=void 0,this._initMargin(),this._updateStyles(!0),this}getMargin(){return this.opts.margin}willItFit(t){if(arguments.length>1){console.warn("gridstack.ts: `willItFit(x,y,w,h,autoPosition)` is deprecated. Use `willItFit({x, y,...})`. It will be removed soon");const r=arguments;let i=0,o={x:r[i++],y:r[i++],w:r[i++],h:r[i++],autoPosition:r[i++]};return this.willItFit(o)}return this.engine.willItFit(t)}_triggerChangeEvent(){if(this.engine.batchMode)return this;const t=this.engine.getDirtyNodes(!0);return t&&t.length&&(this._ignoreLayoutsNodeChange||this.engine.layoutsNodesChange(t),this._triggerEvent("change",t)),this.engine.saveInitial(),this}_triggerAddEvent(){var t;if(this.engine.batchMode)return this;if((t=this.engine.addedNodes)!=null&&t.length){this._ignoreLayoutsNodeChange||this.engine.layoutsNodesChange(this.engine.addedNodes),this.engine.addedNodes.forEach(i=>{delete i._dirty});const r=[...this.engine.addedNodes];this.engine.addedNodes=[],this._triggerEvent("added",r)}return this}_triggerRemoveEvent(){var t;if(this.engine.batchMode)return this;if((t=this.engine.removedNodes)!=null&&t.length){const r=[...this.engine.removedNodes];this.engine.removedNodes=[],this._triggerEvent("removed",r)}return this}_triggerEvent(t,r){const i=r?new CustomEvent(t,{bubbles:!1,detail:r}):new Event(t);return this.el.dispatchEvent(i),this}_removeStylesheet(){if(this._styles){const t=this.opts.styleInHead?void 0:this.el.parentNode;F.removeStylesheet(this._styleSheetClass,t),delete this._styles}return this}_updateStyles(t=!1,r){if(t&&this._removeStylesheet(),r===void 0&&(r=this.getRow()),this._updateContainerHeight(),this.opts.cellHeight===0)return this;const i=this.opts.cellHeight,o=this.opts.cellHeightUnit,u=`.${this._styleSheetClass} > .${this.opts.itemClass}`;if(!this._styles){const c=this.opts.styleInHead?void 0:this.el.parentNode;if(this._styles=F.createStylesheet(this._styleSheetClass,c,{nonce:this.opts.nonce}),!this._styles)return this;this._styles._max=0,F.addCSSRule(this._styles,u,`height: ${i}${o}`);const d=this.opts.marginTop+this.opts.marginUnit,p=this.opts.marginBottom+this.opts.marginUnit,g=this.opts.marginRight+this.opts.marginUnit,y=this.opts.marginLeft+this.opts.marginUnit,v=`${u} > .grid-stack-item-content`,x=`.${this._styleSheetClass} > .grid-stack-placeholder > .placeholder-content`;F.addCSSRule(this._styles,v,`top: ${d}; right: ${g}; bottom: ${p}; left: ${y};`),F.addCSSRule(this._styles,x,`top: ${d}; right: ${g}; bottom: ${p}; left: ${y};`),F.addCSSRule(this._styles,`${u} > .ui-resizable-n`,`top: ${d};`),F.addCSSRule(this._styles,`${u} > .ui-resizable-s`,`bottom: ${p}`),F.addCSSRule(this._styles,`${u} > .ui-resizable-ne`,`right: ${g}; top: ${d}`),F.addCSSRule(this._styles,`${u} > .ui-resizable-e`,`right: ${g}`),F.addCSSRule(this._styles,`${u} > .ui-resizable-se`,`right: ${g}; bottom: ${p}`),F.addCSSRule(this._styles,`${u} > .ui-resizable-nw`,`left: ${y}; top: ${d}`),F.addCSSRule(this._styles,`${u} > .ui-resizable-w`,`left: ${y}`),F.addCSSRule(this._styles,`${u} > .ui-resizable-sw`,`left: ${y}; bottom: ${p}`)}if(r=r||this._styles._max,r>this._styles._max){const c=d=>i*d+o;for(let d=this._styles._max+1;d<=r;d++)F.addCSSRule(this._styles,`${u}[gs-y="${d}"]`,`top: ${c(d)}`),F.addCSSRule(this._styles,`${u}[gs-h="${d+1}"]`,`height: ${c(d+1)}`);this._styles._max=r}return this}_updateContainerHeight(){if(!this.engine||this.engine.batchMode)return this;const t=this.parentGridNode;let r=this.getRow()+this._extraDragRow;const i=this.opts.cellHeight,o=this.opts.cellHeightUnit;if(!i)return this;if(!t){const u=F.parseHeight(getComputedStyle(this.el).minHeight);if(u.h>0&&u.unit===o){const c=Math.floor(u.h/i);r1?t.setAttribute("gs-w",String(r.w)):t.removeAttribute("gs-w"),r.h>1?t.setAttribute("gs-h",String(r.h)):t.removeAttribute("gs-h"),this}_writeAttr(t,r){if(!r)return this;this._writePosAttr(t,r);const i={noResize:"gs-no-resize",noMove:"gs-no-move",locked:"gs-locked",id:"gs-id",sizeToContent:"gs-size-to-content"};for(const o in i)r[o]?t.setAttribute(i[o],String(r[o])):t.removeAttribute(i[o]);return this}_readAttr(t,r=!0){const i={};i.x=F.toNumber(t.getAttribute("gs-x")),i.y=F.toNumber(t.getAttribute("gs-y")),i.w=F.toNumber(t.getAttribute("gs-w")),i.h=F.toNumber(t.getAttribute("gs-h")),i.autoPosition=F.toBool(t.getAttribute("gs-auto-position")),i.noResize=F.toBool(t.getAttribute("gs-no-resize")),i.noMove=F.toBool(t.getAttribute("gs-no-move")),i.locked=F.toBool(t.getAttribute("gs-locked"));const o=t.getAttribute("gs-size-to-content");o&&(o==="true"||o==="false"?i.sizeToContent=F.toBool(o):i.sizeToContent=parseInt(o,10)),i.id=t.getAttribute("gs-id"),i.maxW=F.toNumber(t.getAttribute("gs-max-w")),i.minW=F.toNumber(t.getAttribute("gs-min-w")),i.maxH=F.toNumber(t.getAttribute("gs-max-h")),i.minH=F.toNumber(t.getAttribute("gs-min-h")),r&&(i.w===1&&t.removeAttribute("gs-w"),i.h===1&&t.removeAttribute("gs-h"),i.maxW&&t.removeAttribute("gs-max-w"),i.minW&&t.removeAttribute("gs-min-w"),i.maxH&&t.removeAttribute("gs-max-h"),i.minH&&t.removeAttribute("gs-min-h"));for(const u in i){if(!i.hasOwnProperty(u))return;!i[u]&&i[u]!==0&&u!=="gs-size-to-content"&&delete i[u]}return i}_setStaticClass(){const t=["grid-stack-static"];return this.opts.staticGrid?(this.el.classList.add(...t),this.el.setAttribute("gs-static","true")):(this.el.classList.remove(...t),this.el.removeAttribute("gs-static")),this}onResize(t=(r=>(r=this.el)==null?void 0:r.clientWidth)()){if(!t||this.prevWidth===t)return;this.prevWidth=t,this.batchUpdate();let i=!1;return this._autoColumn&&this.parentGridNode?this.opts.column!==this.parentGridNode.w&&(this.column(this.parentGridNode.w,this.opts.layout||"list"),i=!0):i=this.checkDynamicColumn(),this._isAutoCellHeight&&this.cellHeight(),this.engine.nodes.forEach(o=>{o.subGrid&&o.subGrid.onResize()}),this._skipInitialResize||this.resizeToContentCheck(i),delete this._skipInitialResize,this.batchUpdate(!1),this}resizeToContentCheck(t=!1,r=void 0){if(this.engine){if(t&&this.hasAnimationCSS())return setTimeout(()=>this.resizeToContentCheck(!1,r),this.animationDelay);if(r)F.shouldSizeToContent(r)&&this.resizeToContentCBCheck(r.el);else if(this.engine.nodes.some(i=>F.shouldSizeToContent(i))){const i=[...this.engine.nodes];this.batchUpdate(),i.forEach(o=>{F.shouldSizeToContent(o)&&this.resizeToContentCBCheck(o.el)}),this.batchUpdate(!1)}this._gsEventHandler.resizecontent&&this._gsEventHandler.resizecontent(null,r?[r]:this.engine.nodes)}}_updateResizeEvent(t=!1){const r=!this.parentGridNode&&(this._isAutoCellHeight||this.opts.sizeToContent||this.opts.columnOpts||this.engine.nodes.find(i=>i.sizeToContent));return!t&&r&&!this.resizeObserver?(this._sizeThrottle=F.throttle(()=>this.onResize(),this.opts.cellHeightThrottle),this.resizeObserver=new ResizeObserver(()=>this._sizeThrottle()),this.resizeObserver.observe(this.el),this._skipInitialResize=!0):(t||!r)&&this.resizeObserver&&(this.resizeObserver.disconnect(),delete this.resizeObserver,delete this._sizeThrottle),this}static getElement(t=".grid-stack-item"){return F.getElement(t)}static getElements(t=".grid-stack-item"){return F.getElements(t)}static getGridElement(t){return Te.getElement(t)}static getGridElements(t){return F.getElements(t)}_initMargin(){let t,r=0,i=[];return typeof this.opts.margin=="string"&&(i=this.opts.margin.split(" ")),i.length===2?(this.opts.marginTop=this.opts.marginBottom=i[0],this.opts.marginLeft=this.opts.marginRight=i[1]):i.length===4?(this.opts.marginTop=i[0],this.opts.marginRight=i[1],this.opts.marginBottom=i[2],this.opts.marginLeft=i[3]):(t=F.parseHeight(this.opts.margin),this.opts.marginUnit=t.unit,r=this.opts.margin=t.h),this.opts.marginTop===void 0?this.opts.marginTop=r:(t=F.parseHeight(this.opts.marginTop),this.opts.marginTop=t.h,delete this.opts.margin),this.opts.marginBottom===void 0?this.opts.marginBottom=r:(t=F.parseHeight(this.opts.marginBottom),this.opts.marginBottom=t.h,delete this.opts.margin),this.opts.marginRight===void 0?this.opts.marginRight=r:(t=F.parseHeight(this.opts.marginRight),this.opts.marginRight=t.h,delete this.opts.margin),this.opts.marginLeft===void 0?this.opts.marginLeft=r:(t=F.parseHeight(this.opts.marginLeft),this.opts.marginLeft=t.h,delete this.opts.margin),this.opts.marginUnit=t.unit,this.opts.marginTop===this.opts.marginBottom&&this.opts.marginLeft===this.opts.marginRight&&this.opts.marginTop===this.opts.marginRight&&(this.opts.margin=this.opts.marginTop),this}static getDD(){return Gn}static setupDragIn(t,r,i,o=document){(r==null?void 0:r.pause)!==void 0&&(Le.pauseDrag=r.pause),r={appendTo:"body",helper:"clone",...r||{}},(typeof t=="string"?F.getElements(t,o):t).forEach((c,d)=>{Gn.isDraggable(c)||Gn.dragIn(c,r),i!=null&&i[d]&&(c.gridstackNode=i[d])})}movable(t,r){return this.opts.staticGrid?this:(Te.getElements(t).forEach(i=>{const o=i.gridstackNode;o&&(r?delete o.noMove:o.noMove=!0,this.prepareDragDrop(o.el))}),this)}resizable(t,r){return this.opts.staticGrid?this:(Te.getElements(t).forEach(i=>{const o=i.gridstackNode;o&&(r?delete o.noResize:o.noResize=!0,this.prepareDragDrop(o.el))}),this)}disable(t=!0){if(!this.opts.staticGrid)return this.enableMove(!1,t),this.enableResize(!1,t),this._triggerEvent("disable"),this}enable(t=!0){if(!this.opts.staticGrid)return this.enableMove(!0,t),this.enableResize(!0,t),this._triggerEvent("enable"),this}enableMove(t,r=!0){return this.opts.staticGrid?this:(t?delete this.opts.disableDrag:this.opts.disableDrag=!0,this.engine.nodes.forEach(i=>{this.prepareDragDrop(i.el),i.subGrid&&r&&i.subGrid.enableMove(t,r)}),this)}enableResize(t,r=!0){return this.opts.staticGrid?this:(t?delete this.opts.disableResize:this.opts.disableResize=!0,this.engine.nodes.forEach(i=>{this.prepareDragDrop(i.el),i.subGrid&&r&&i.subGrid.enableResize(t,r)}),this)}cancelDrag(){var r;const t=(r=this._placeholder)==null?void 0:r.gridstackNode;t&&(t._isExternal?(t._isAboutToRemove=!0,this.engine.removeNode(t)):t._isAboutToRemove&&Te._itemRemoving(t.el,!1),this.engine.restoreInitial())}_removeDD(t){return Gn.draggable(t,"destroy").resizable(t,"destroy"),t.gridstackNode&&delete t.gridstackNode._initDD,delete t.ddElement,this}_setupAcceptWidget(){if(this.opts.staticGrid||!this.opts.acceptWidgets&&!this.opts.removable)return Gn.droppable(this.el,"destroy"),this;let t,r;const i=(o,u,c)=>{var x;c=c||u;const d=c.gridstackNode;if(!d)return;if(!((x=d.grid)!=null&&x.el)){c.style.transform=`scale(${1/this.dragTransform.xScale},${1/this.dragTransform.yScale})`;const T=c.getBoundingClientRect();c.style.left=T.x+(this.dragTransform.xScale-1)*(o.clientX-T.x)/this.dragTransform.xScale+"px",c.style.top=T.y+(this.dragTransform.yScale-1)*(o.clientY-T.y)/this.dragTransform.yScale+"px",c.style.transformOrigin="0px 0px"}let{top:p,left:g}=c.getBoundingClientRect();const y=this.el.getBoundingClientRect();g-=y.left,p-=y.top;const v={position:{top:p*this.dragTransform.xScale,left:g*this.dragTransform.yScale}};if(d._temporaryRemoved){if(d.x=Math.max(0,Math.round(g/r)),d.y=Math.max(0,Math.round(p/t)),delete d.autoPosition,this.engine.nodeBoundFix(d),!this.engine.willItFit(d)){if(d.autoPosition=!0,!this.engine.willItFit(d)){Gn.off(u,"drag");return}d._willFitPos&&(F.copyPos(d,d._willFitPos),delete d._willFitPos)}this._onStartMoving(c,o,v,d,r,t)}else this._dragOrResize(c,o,v,d,r,t)};return Gn.droppable(this.el,{accept:o=>{const u=o.gridstackNode||this._readAttr(o,!1);if((u==null?void 0:u.grid)===this)return!0;if(!this.opts.acceptWidgets)return!1;let c=!0;if(typeof this.opts.acceptWidgets=="function")c=this.opts.acceptWidgets(o);else{const d=this.opts.acceptWidgets===!0?".grid-stack-item":this.opts.acceptWidgets;c=o.matches(d)}if(c&&u&&this.opts.maxRow){const d={w:u.w,h:u.h,minW:u.minW,minH:u.minH};c=this.engine.willItFit(d)}return c}}).on(this.el,"dropover",(o,u,c)=>{let d=(c==null?void 0:c.gridstackNode)||u.gridstackNode;if((d==null?void 0:d.grid)===this&&!d._temporaryRemoved)return!1;if(d!=null&&d._sidebarOrig&&(d.w=d._sidebarOrig.w,d.h=d._sidebarOrig.h),d!=null&&d.grid&&d.grid!==this&&!d._temporaryRemoved&&d.grid._leave(u,c),c=c||u,r=this.cellWidth(),t=this.getCellHeight(!0),!d){const y=c.getAttribute("data-gs-widget")||c.getAttribute("gridstacknode");if(y){try{d=JSON.parse(y)}catch{console.error("Gridstack dropover: Bad JSON format: ",y)}c.removeAttribute("data-gs-widget"),c.removeAttribute("gridstacknode")}d||(d=this._readAttr(c)),d._sidebarOrig={w:d.w,h:d.h}}d.grid||(d.el||(d={...d}),d._isExternal=!0,c.gridstackNode=d);const p=d.w||Math.round(c.offsetWidth/r)||1,g=d.h||Math.round(c.offsetHeight/t)||1;return d.grid&&d.grid!==this?(u._gridstackNodeOrig||(u._gridstackNodeOrig=d),u.gridstackNode=d={...d,w:p,h:g,grid:this},delete d.x,delete d.y,this.engine.cleanupNode(d).nodeBoundFix(d),d._initDD=d._isExternal=d._temporaryRemoved=!0):(d.w=p,d.h=g,d._temporaryRemoved=!0),Te._itemRemoving(d.el,!1),Gn.on(u,"drag",i),i(o,u,c),!1}).on(this.el,"dropout",(o,u,c)=>{const d=(c==null?void 0:c.gridstackNode)||u.gridstackNode;return d&&(!d.grid||d.grid===this)&&(this._leave(u,c),this._isTemp&&this.removeAsSubGrid(d)),!1}).on(this.el,"drop",(o,u,c)=>{var T,N,C;const d=(c==null?void 0:c.gridstackNode)||u.gridstackNode;if((d==null?void 0:d.grid)===this&&!d._isExternal)return!1;const p=!!this.placeholder.parentElement,g=u!==c;this.placeholder.remove(),delete this.placeholder.gridstackNode;const y=p&&this.opts.animate;y&&this.setAnimation(!1);const v=u._gridstackNodeOrig;if(delete u._gridstackNodeOrig,p&&(v!=null&&v.grid)&&v.grid!==this){const L=v.grid;L.engine.removeNodeFromLayoutCache(v),L.engine.removedNodes.push(v),L._triggerRemoveEvent()._triggerChangeEvent(),L.parentGridNode&&!L.engine.nodes.length&&L.opts.subGridDynamic&&L.removeAsSubGrid()}if(!d||(p&&(this.engine.cleanupNode(d),d.grid=this),(T=d.grid)==null||delete T._isTemp,Gn.off(u,"drag"),c!==u?(c.remove(),u=c):u.remove(),this._removeDD(u),!p))return!1;const x=(C=(N=d.subGrid)==null?void 0:N.el)==null?void 0:C.gridstack;return F.copyPos(d,this._readAttr(this.placeholder)),F.removePositioningStyles(u),g&&(d.content||d.subGridOpts||Te.addRemoveCB)?(delete d.el,u=this.addWidget(d)):(this._prepareElement(u,!0,d),this.el.appendChild(u),this.resizeToContentCheck(!1,d),x&&(x.parentGridNode=d,x.opts.styleInHead||x._updateStyles(!0)),this._updateContainerHeight()),this.engine.addedNodes.push(d),this._triggerAddEvent(),this._triggerChangeEvent(),this.engine.endUpdate(),this._gsEventHandler.dropped&&this._gsEventHandler.dropped({...o,type:"dropped"},v&&v.grid?v:void 0,d),y&&this.setAnimation(this.opts.animate,!0),!1}),this}static _itemRemoving(t,r){if(!t)return;const i=t?t.gridstackNode:void 0;!(i!=null&&i.grid)||t.classList.contains(i.grid.opts.removableOptions.decline)||(r?i._isAboutToRemove=!0:delete i._isAboutToRemove,r?t.classList.add("grid-stack-item-removing"):t.classList.remove("grid-stack-item-removing"))}_setupRemoveDrop(){if(typeof this.opts.removable!="string")return this;const t=document.querySelector(this.opts.removable);return t?(!this.opts.staticGrid&&!Gn.isDroppable(t)&&Gn.droppable(t,this.opts.removableOptions).on(t,"dropover",(r,i)=>Te._itemRemoving(i,!0)).on(t,"dropout",(r,i)=>Te._itemRemoving(i,!1)),this):this}prepareDragDrop(t,r=!1){const i=t==null?void 0:t.gridstackNode;if(!i)return;const o=i.noMove||this.opts.disableDrag,u=i.noResize||this.opts.disableResize,c=this.opts.staticGrid||o&&u;if((r||c)&&(i._initDD&&(this._removeDD(t),delete i._initDD),c&&t.classList.add("ui-draggable-disabled","ui-resizable-disabled"),!r))return this;if(!i._initDD){let d,p;const g=(x,T)=>{this._gsEventHandler[x.type]&&this._gsEventHandler[x.type](x,x.target),d=this.cellWidth(),p=this.getCellHeight(!0),this._onStartMoving(t,x,T,i,d,p)},y=(x,T)=>{this._dragOrResize(t,x,T,i,d,p)},v=x=>{this.placeholder.remove(),delete this.placeholder.gridstackNode,delete i._moving,delete i._event,delete i._lastTried;const T=i.w!==i._orig.w,N=x.target;if(!(!N.gridstackNode||N.gridstackNode.grid!==this)){if(i.el=N,i._isAboutToRemove){const C=t.gridstackNode.grid;C._gsEventHandler[x.type]&&C._gsEventHandler[x.type](x,N),C.engine.nodes.push(i),C.removeWidget(t,!0,!0)}else F.removePositioningStyles(N),i._temporaryRemoved?(F.copyPos(i,i._orig),this._writePosAttr(N,i),this.engine.addNode(i)):this._writePosAttr(N,i),this._gsEventHandler[x.type]&&this._gsEventHandler[x.type](x,N);this._extraDragRow=0,this._updateContainerHeight(),this._triggerChangeEvent(),this.engine.endUpdate(),x.type==="resizestop"&&(Number.isInteger(i.sizeToContent)&&(i.sizeToContent=i.h),this.resizeToContentCheck(T,i))}};Gn.draggable(t,{start:g,stop:v,drag:y}).resizable(t,{start:g,stop:v,resize:y}),i._initDD=!0}return Gn.draggable(t,o?"disable":"enable").resizable(t,u?"disable":"enable"),this}_onStartMoving(t,r,i,o,u,c){var d;if(this.engine.cleanNodes().beginUpdate(o),this._writePosAttr(this.placeholder,o),this.el.appendChild(this.placeholder),this.placeholder.gridstackNode=o,(d=o.grid)!=null&&d.el)this.dragTransform=F.getValuesFromTransformedElement(t);else if(this.placeholder&&this.placeholder.closest(".grid-stack")){const p=this.placeholder.closest(".grid-stack");this.dragTransform=F.getValuesFromTransformedElement(p)}else this.dragTransform={xScale:1,xOffset:0,yScale:1,yOffset:0};if(o.el=this.placeholder,o._lastUiPosition=i.position,o._prevYPix=i.position.top,o._moving=r.type==="dragstart",delete o._lastTried,r.type==="dropover"&&o._temporaryRemoved&&(this.engine.addNode(o),o._moving=!0),this.engine.cacheRects(u,c,this.opts.marginTop,this.opts.marginRight,this.opts.marginBottom,this.opts.marginLeft),r.type==="resizestart"){const p=this.getColumn()-o.x,g=(this.opts.maxRow||Number.MAX_SAFE_INTEGER)-o.y;Gn.resizable(t,"option","minWidth",u*Math.min(o.minW||1,p)).resizable(t,"option","minHeight",c*Math.min(o.minH||1,g)).resizable(t,"option","maxWidth",u*Math.min(o.maxW||Number.MAX_SAFE_INTEGER,p)).resizable(t,"option","maxWidthMoveLeft",u*Math.min(o.maxW||Number.MAX_SAFE_INTEGER,o.x+o.w)).resizable(t,"option","maxHeight",c*Math.min(o.maxH||Number.MAX_SAFE_INTEGER,g)).resizable(t,"option","maxHeightMoveUp",c*Math.min(o.maxH||Number.MAX_SAFE_INTEGER,o.y+o.h))}}_dragOrResize(t,r,i,o,u,c){const d={...o._orig};let p,g=this.opts.marginLeft,y=this.opts.marginRight,v=this.opts.marginTop,x=this.opts.marginBottom;const T=Math.round(c*.1),N=Math.round(u*.1);if(g=Math.min(g,N),y=Math.min(y,N),v=Math.min(v,T),x=Math.min(x,T),r.type==="drag"){if(o._temporaryRemoved)return;const L=i.position.top-o._prevYPix;o._prevYPix=i.position.top,this.opts.draggable.scroll!==!1&&F.updateScrollPosition(t,i.position,L);const U=i.position.left+(i.position.left>o._lastUiPosition.left?-y:g),A=i.position.top+(i.position.top>o._lastUiPosition.top?-x:v);d.x=Math.round(U/u),d.y=Math.round(A/c);const V=this._extraDragRow;if(this.engine.collide(o,d)){const z=this.getRow();let $=Math.max(0,d.y+o.h-z);this.opts.maxRow&&z+$>this.opts.maxRow&&($=Math.max(0,this.opts.maxRow-z)),this._extraDragRow=$}else this._extraDragRow=0;if(this._extraDragRow!==V&&this._updateContainerHeight(),o.x===d.x&&o.y===d.y)return}else if(r.type==="resize"){if(d.x<0||(F.updateScrollResize(r,t,c),d.w=Math.round((i.size.width-g)/u),d.h=Math.round((i.size.height-v)/c),o.w===d.w&&o.h===d.h)||o._lastTried&&o._lastTried.w===d.w&&o._lastTried.h===d.h)return;const L=i.position.left+g,U=i.position.top+v;d.x=Math.round(L/u),d.y=Math.round(U/c),p=!0}o._event=r,o._lastTried=d;const C={x:i.position.left+g,y:i.position.top+v,w:(i.size?i.size.width:o.w*u)-g-y,h:(i.size?i.size.height:o.h*c)-v-x};if(this.engine.moveNodeCheck(o,{...d,cellWidth:u,cellHeight:c,rect:C,resizing:p})){o._lastUiPosition=i.position,this.engine.cacheRects(u,c,v,y,x,g),delete o._skipDown,p&&o.subGrid&&o.subGrid.onResize(),this._extraDragRow=0,this._updateContainerHeight();const L=r.target;o._sidebarOrig||this._writePosAttr(L,o),this._gsEventHandler[r.type]&&this._gsEventHandler[r.type](r,L)}}_leave(t,r){r=r||t;const i=r.gridstackNode;if(!i||(r.style.transform=r.style.transformOrigin=null,Gn.off(t,"drag"),i._temporaryRemoved))return;i._temporaryRemoved=!0,this.engine.removeNode(i),i.el=i._isExternal&&r?r:t;const o=i._sidebarOrig;i._isExternal&&this.engine.cleanupNode(i),i._sidebarOrig=o,this.opts.removable===!0&&Te._itemRemoving(t,!0),t._gridstackNodeOrig?(t.gridstackNode=t._gridstackNodeOrig,delete t._gridstackNodeOrig):i._isExternal&&this.engine.restoreInitial()}commit(){return OS(this,this.batchUpdate(!1),"commit","batchUpdate","5.2"),this}}Te.renderCB=(l,t)=>{l&&(t!=null&&t.content)&&(l.textContent=t.content)};Te.resizeToContentParent=".grid-stack-item-content";Te.Utils=F;Te.Engine=ci;Te.GDRev="11.5.1";function jS({widget:l,onRemove:t}){const r=RS[l.kind];return R.jsxs("div",{className:"widget",children:[R.jsxs("div",{className:"widget-header",children:[R.jsx("span",{className:"widget-grip","aria-hidden":!0,children:"⠿"}),R.jsx("span",{className:"widget-icon",children:r==null?void 0:r.icon}),R.jsx("span",{className:"widget-title",children:(r==null?void 0:r.title)||l.kind}),R.jsx("button",{className:"widget-close",title:"Remove widget",onClick:t,children:"×"})]}),R.jsx("div",{className:"widget-body",children:r?r.render(l.id):null})]})}function IS(){const l=Ro(y=>y.widgets),t=Ro(y=>y.updateGeom),r=Ro(y=>y.removeWidget),i=P.useRef(null),o=P.useRef(null),u=P.useRef(new Map),[c,d]=P.useState(new Map),[p,g]=P.useState(!1);return P.useEffect(()=>{if(!i.current)return;const y=Te.init({column:12,cellHeight:56,margin:8,float:!0,handle:".widget-header",resizable:{handles:"e, se, s, sw, w"},animate:!0},i.current);return o.current=y,y.on("change",(v,x)=>{const T=x.map(N=>({id:String(N.id),x:N.x??0,y:N.y??0,w:N.w??1,h:N.h??1}));T.length&&t(T)}),g(!0),()=>{y.destroy(!1),o.current=null}},[t]),P.useEffect(()=>{const y=o.current;if(!y||!p)return;const v=new Set(l.map(N=>N.id));let x=!1;const T=new Map(c);y.batchUpdate();for(const N of l){if(u.current.has(N.id))continue;const C=y.addWidget({x:N.x,y:N.y,w:N.w,h:N.h,id:N.id}),L=C.querySelector(".grid-stack-item-content");u.current.set(N.id,C),T.set(N.id,L),x=!0}for(const[N,C]of Array.from(u.current.entries()))v.has(N)||(y.removeWidget(C,!0),u.current.delete(N),T.delete(N),x=!0);y.commit(),x&&d(T)},[l,p]),R.jsxs("div",{className:"canvas",children:[R.jsx("div",{className:"grid-stack",ref:i}),l.map(y=>{const v=c.get(y.id);return v?Ps.createPortal(R.jsx(jS,{widget:y,onRemove:()=>r(y.id)}),v,y.id):null})]})}function FS(){const l=We(y=>y.addSignalToPlot),t=We(y=>y.setMotorTypes),r=We(y=>y.setMode),i=We(y=>y.setStatus),o=We(y=>y.setRegisterTable),[u,c]=P.useState(null),d=dy(fy(Kf,{activationConstraint:{distance:4}}));P.useEffect(()=>{Bg(),$1().then(t),Cn.status().then(y=>{y!=null&&y.mode&&r(y.mode),i(y)}),Cn.registerTable().then(y=>{if(y!=null&&y.registers){const v={};for(const x of y.registers)v[x.rid]=x;o(v)}})},[t,r,i,o]);const p=y=>{var x;const v=(x=y.active.data.current)==null?void 0:x.signalId;c(v?Ff(v):null)},g=y=>{var T,N,C,L;c(null);const v=(T=y.active.data.current)==null?void 0:T.signalId,x=((C=(N=y.over)==null?void 0:N.id)==null?void 0:C.toString())||"";if(v&&x.startsWith("plot:")){const U=(L=y.over.data.current)==null?void 0:L.panelId;l(U,v)}};return R.jsxs(u0,{sensors:d,onDragStart:p,onDragEnd:g,children:[R.jsxs("div",{className:"app",children:[R.jsx(TS,{}),R.jsxs("div",{className:"body",children:[R.jsx(zS,{}),R.jsx("main",{className:"canvas-host",children:R.jsx(IS,{})})]})]}),R.jsx(T0,{dropAnimation:null,children:u?R.jsx("div",{className:"drag-ghost",children:u}):null})]})}bS();Xv.createRoot(document.getElementById("root")).render(R.jsx(pt.StrictMode,{children:R.jsx(FS,{})})); diff --git a/damiao_motor/gui/webapp/dist/assets/index-CrxIlrMA.css b/damiao_motor/gui/webapp/dist/assets/index-CrxIlrMA.css new file mode 100644 index 0000000..4d9e3ad --- /dev/null +++ b/damiao_motor/gui/webapp/dist/assets/index-CrxIlrMA.css @@ -0,0 +1 @@ +.uplot,.uplot *,.uplot *:before,.uplot *:after{box-sizing:border-box}.uplot{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5;width:min-content}.u-title{text-align:center;font-size:18px;font-weight:700}.u-wrap{position:relative;-webkit-user-select:none;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;position:relative;width:100%;height:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{vertical-align:middle;display:inline-block}.u-legend .u-marker{width:1em;height:1em;margin-right:4px;background-clip:padding-box!important}.u-inline.u-live th:after{content:":";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:#00000012;position:absolute;pointer-events:none}.u-cursor-x,.u-cursor-y{position:absolute;left:0;top:0;pointer-events:none;will-change:transform}.u-hz .u-cursor-x,.u-vt .u-cursor-y{height:100%;border-right:1px dashed #607D8B}.u-hz .u-cursor-y,.u-vt .u-cursor-x{width:100%;border-bottom:1px dashed #607D8B}.u-cursor-pt{position:absolute;top:0;left:0;border-radius:50%;border:0 solid;pointer-events:none;will-change:transform;background-clip:padding-box!important}.u-axis.u-off,.u-select.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-cursor-pt.u-off{display:none}.grid-stack{position:relative}.grid-stack-rtl{direction:ltr}.grid-stack-rtl>.grid-stack-item{direction:rtl}.grid-stack-placeholder>.placeholder-content{background-color:#0000001a;margin:0;position:absolute;width:auto;z-index:0!important}.grid-stack>.grid-stack-item{position:absolute;padding:0}.grid-stack>.grid-stack-item>.grid-stack-item-content{margin:0;position:absolute;width:auto;overflow-x:hidden;overflow-y:auto}.grid-stack>.grid-stack-item.size-to-content:not(.size-to-content-max)>.grid-stack-item-content{overflow-y:hidden}.grid-stack-item>.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.grid-stack-item.ui-resizable-autohide>.ui-resizable-handle,.grid-stack-item.ui-resizable-disabled>.ui-resizable-handle{display:none}.grid-stack-item>.ui-resizable-ne,.grid-stack-item>.ui-resizable-nw,.grid-stack-item>.ui-resizable-se,.grid-stack-item>.ui-resizable-sw{background-image:url('data:image/svg+xml;utf8,');background-repeat:no-repeat;background-position:center}.grid-stack-item>.ui-resizable-ne{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-sw{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-nw{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-se{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-nw{cursor:nw-resize;width:20px;height:20px;top:0}.grid-stack-item>.ui-resizable-n{cursor:n-resize;height:10px;top:0;left:25px;right:25px}.grid-stack-item>.ui-resizable-ne{cursor:ne-resize;width:20px;height:20px;top:0}.grid-stack-item>.ui-resizable-e{cursor:e-resize;width:10px;top:15px;bottom:15px}.grid-stack-item>.ui-resizable-se{cursor:se-resize;width:20px;height:20px}.grid-stack-item>.ui-resizable-s{cursor:s-resize;height:10px;left:25px;bottom:0;right:25px}.grid-stack-item>.ui-resizable-sw{cursor:sw-resize;width:20px;height:20px}.grid-stack-item>.ui-resizable-w{cursor:w-resize;width:10px;top:15px;bottom:15px}.grid-stack-item.ui-draggable-dragging>.ui-resizable-handle{display:none!important}.grid-stack-item.ui-draggable-dragging{will-change:left,top;cursor:move}.grid-stack-item.ui-resizable-resizing{will-change:width,height}.ui-draggable-dragging,.ui-resizable-resizing{z-index:10000}.ui-draggable-dragging>.grid-stack-item-content,.ui-resizable-resizing>.grid-stack-item-content{box-shadow:1px 4px 6px #0003;opacity:.8}.grid-stack-animate,.grid-stack-animate .grid-stack-item{transition:left .3s,top .3s,height .3s,width .3s}.grid-stack-animate .grid-stack-item.grid-stack-placeholder,.grid-stack-animate .grid-stack-item.ui-draggable-dragging,.grid-stack-animate .grid-stack-item.ui-resizable-resizing{transition:left 0s,top 0s,height 0s,width 0s}.grid-stack>.grid-stack-item[gs-y="0"]{top:0}.grid-stack>.grid-stack-item[gs-x="0"]{left:0}.gs-12>.grid-stack-item{width:8.333%}.gs-12>.grid-stack-item[gs-x="1"]{left:8.333%}.gs-12>.grid-stack-item[gs-w="2"]{width:16.667%}.gs-12>.grid-stack-item[gs-x="2"]{left:16.667%}.gs-12>.grid-stack-item[gs-w="3"]{width:25%}.gs-12>.grid-stack-item[gs-x="3"]{left:25%}.gs-12>.grid-stack-item[gs-w="4"]{width:33.333%}.gs-12>.grid-stack-item[gs-x="4"]{left:33.333%}.gs-12>.grid-stack-item[gs-w="5"]{width:41.667%}.gs-12>.grid-stack-item[gs-x="5"]{left:41.667%}.gs-12>.grid-stack-item[gs-w="6"]{width:50%}.gs-12>.grid-stack-item[gs-x="6"]{left:50%}.gs-12>.grid-stack-item[gs-w="7"]{width:58.333%}.gs-12>.grid-stack-item[gs-x="7"]{left:58.333%}.gs-12>.grid-stack-item[gs-w="8"]{width:66.667%}.gs-12>.grid-stack-item[gs-x="8"]{left:66.667%}.gs-12>.grid-stack-item[gs-w="9"]{width:75%}.gs-12>.grid-stack-item[gs-x="9"]{left:75%}.gs-12>.grid-stack-item[gs-w="10"]{width:83.333%}.gs-12>.grid-stack-item[gs-x="10"]{left:83.333%}.gs-12>.grid-stack-item[gs-w="11"]{width:91.667%}.gs-12>.grid-stack-item[gs-x="11"]{left:91.667%}.gs-12>.grid-stack-item[gs-w="12"]{width:100%}.gs-1>.grid-stack-item{width:100%}:root{--bg: #f4f6f9;--bg-1: #eef1f6;--surface: #ffffff;--surface-2: #eef2f7;--hover: #e6ecf3;--border: #d6dde7;--border-soft: #e7ecf2;--text: #1e2733;--muted: #5f6a78;--accent: #2f6fed;--ok: #16a34a;--warn: #d97706;--err: #e11d48;--radius: 14px;--radius-sm: 9px;--shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 8px 24px -16px rgba(16, 24, 40, .28);--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace}:root[data-theme=dark]{--bg: #0f1216;--bg-1: #141a21;--surface: #171d25;--surface-2: #1d242e;--hover: #232c38;--border: #262e3a;--border-soft: #1f2630;--text: #d7dde5;--muted: #8a94a3;--accent: #6aa3ff;--ok: #4ade80;--warn: #fbbf24;--err: #fb7185;--shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 10px 28px -16px rgba(0, 0, 0, .65)}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{font-family:var(--font);background:var(--bg);color:var(--text);font-size:13px;-webkit-font-smoothing:antialiased}.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}.small{font-size:11px}.center{text-align:center}.pad{padding:16px}.strong{font-weight:650}.dim{opacity:.5}.app{display:flex;flex-direction:column;height:100%}.body{flex:1;display:flex;min-height:0}.canvas-host{flex:1;min-width:0;position:relative;overflow:auto}.canvas{min-height:100%;padding:6px}.toolbar{display:flex;align-items:center;gap:16px;height:52px;padding:0 16px;background:var(--surface);border-bottom:1px solid var(--border)}.brand{font-weight:650;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:9px}.brand-sub{color:var(--muted);font-weight:500;font-size:12px}.brand-dot{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px var(--accent)}.conn{display:flex;align-items:center;gap:9px}.conn .dot{width:8px;height:8px;border-radius:50%}.dot.on{background:var(--ok);box-shadow:0 0 9px var(--ok)}.dot.off{background:var(--err)}.spacer{flex:1}.actions{display:flex;gap:7px}.badge{font-size:10.5px;padding:2px 8px;border-radius:999px;font-weight:650;border:1px solid transparent;text-transform:uppercase;letter-spacing:.4px}.badge.ok{color:var(--ok);border-color:#4ade8059;background:#4ade801a}.badge.warn{color:var(--warn);border-color:#fbbf2459;background:#fbbf241a}.badge.err{color:var(--err);border-color:#fb718559;background:#fb71851a}.btn{background:var(--surface-2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 11px;font-size:12px;cursor:pointer;transition:background .15s,border-color .15s,transform .05s}.btn:hover{background:var(--hover);border-color:var(--border)}.btn:active{transform:translateY(1px)}.btn.ghost{background:transparent}.btn.small{padding:3px 9px;font-size:11px}.btn.active{border-color:var(--accent);color:var(--accent)}.btn-icon{color:var(--accent);margin-right:2px}.sidebar{width:236px;flex-shrink:0;background:var(--bg-1);border-right:1px solid var(--border-soft);display:flex;flex-direction:column}.sidebar-head{padding:12px 14px;border-bottom:1px solid var(--border-soft)}.sidebar-title{font-weight:650;margin-bottom:9px}.filter,.type-select,select,input[type=text],input[type=number]{width:100%;background:var(--surface-2);border:1px solid var(--border);color:var(--text);border-radius:var(--radius-sm);padding:6px 9px;font-size:12px;outline:none;transition:border-color .15s,box-shadow .15s}.filter:focus,select:focus,input:focus{border-color:var(--accent);box-shadow:0 0 0 3px #6aa3ff26}.sidebar-body{flex:1;overflow-y:auto;padding:10px}.sidebar-foot{padding:10px 14px;border-top:1px solid var(--border-soft);font-size:11px;line-height:1.55;color:var(--muted)}.motor-group{margin-bottom:14px}.motor-group-title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);margin:0 2px 6px}.chips{display:flex;flex-direction:column;gap:5px}.sig-chip{display:flex;align-items:center;gap:8px;padding:6px 9px;background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--radius-sm);cursor:grab;-webkit-user-select:none;user-select:none;font-size:12px;transition:background .12s,border-color .12s}.sig-chip:hover{background:var(--surface-2);border-color:var(--border)}.sig-chip.dragging{opacity:.4}.sig-swatch{width:10px;height:10px;border-radius:3px;border:2px solid;flex-shrink:0}.sig-name{flex:1;font-family:var(--mono)}.sig-unit{color:var(--muted);font-size:10.5px}.drag-ghost{background:var(--accent);color:#05203f;font-weight:650;font-size:12px;padding:6px 11px;border-radius:var(--radius-sm);font-family:var(--mono);box-shadow:0 10px 26px #0000008c}.grid-stack{background:transparent}.grid-stack-item-content{top:0;right:0;bottom:0;left:0;overflow:visible;background:transparent;border:none}.widget{height:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}.widget-header{display:flex;align-items:center;gap:8px;height:34px;padding:0 8px 0 10px;flex-shrink:0;border-bottom:1px solid var(--border-soft);background:var(--surface-2);cursor:move}.widget-grip{color:var(--muted);opacity:.5;font-size:12px;letter-spacing:-2px}.widget-icon{color:var(--accent);font-size:12px}.widget-title{flex:1;font-size:12.5px;font-weight:600;letter-spacing:.2px}.widget-close{width:22px;height:22px;border:none;background:transparent;color:var(--muted);border-radius:6px;cursor:pointer;font-size:16px;line-height:1;opacity:0;transition:opacity .12s,background .12s,color .12s}.widget:hover .widget-close{opacity:1}.widget-close:hover{background:#fb718526;color:var(--err)}.widget-body{flex:1;min-height:0;position:relative}.widget-body .panel{height:100%}.grid-stack-item>.ui-resizable-handle{filter:opacity(.45)}.grid-stack-item:hover>.ui-resizable-handle{filter:opacity(.9)}.grid-stack-placeholder>.placeholder-content{border:1px dashed var(--accent);border-radius:var(--radius);background:#6aa3ff0f}.panel{height:100%;display:flex;flex-direction:column;overflow:hidden}.plot-toolbar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--border-soft);flex-wrap:wrap}.plot-toolbar select{width:auto}.legend{display:flex;gap:6px;flex-wrap:wrap}.legend-chip{display:inline-flex;align-items:center;gap:5px;font-size:11px;padding:2px 7px 2px 6px;border:1px solid var(--border);border-radius:999px;font-family:var(--mono)}.legend-swatch{width:9px;height:9px;border-radius:2px;border:1.5px solid}.legend-x{background:none;border:none;color:var(--muted);cursor:pointer;font-size:13px;padding:0 0 0 2px}.legend-x:hover{color:var(--err)}.plot-host{flex:1;min-height:0;position:relative;padding:6px}.plot-host.drop-over{outline:2px dashed var(--accent);outline-offset:-5px;background:#6aa3ff0f;border-radius:10px}.drop-hint{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:12px;pointer-events:none;text-align:center;padding:22px}.table-panel{overflow:auto}.motor-table{width:100%;border-collapse:collapse;font-size:12px}.motor-table th,.motor-table td{padding:6px 10px;text-align:right;border-bottom:1px solid var(--border-soft);white-space:nowrap}.motor-table th:first-child,.motor-table td:first-child{text-align:left}.motor-table th{position:sticky;top:0;background:var(--surface-2);color:var(--muted);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.4px}.motor-table tr:hover td{background:var(--hover)}.cmd-col{color:var(--accent)}.status-pill{font-size:10px;padding:2px 8px;border-radius:999px;font-weight:650}.status-pill.ok{color:var(--ok);background:#4ade801f}.status-pill.off{color:var(--muted);background:#8a94a31f}.status-pill.warn{color:var(--warn);background:#fbbf241f}.cards-panel{overflow:auto}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:11px;padding:12px}.motor-card{background:var(--bg-1);border:1px solid var(--border-soft);border-radius:12px;padding:13px}.motor-card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px}.motor-card-sub{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:11px}.type-select{width:auto;padding:3px 7px;font-size:11px}.metric{margin-bottom:9px}.metric-label{font-size:11px;color:var(--text);margin-bottom:3px}.metric-values{display:flex;align-items:baseline;gap:10px}.metric-act{font-family:var(--mono);font-size:20px;font-weight:650}.metric-cmd{font-family:var(--mono);font-size:12px;color:var(--accent)}.temp-row{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:7px;border-top:1px solid var(--border-soft);padding-top:7px}.rawlog-panel{font-size:11.5px}.rawlog-toolbar{display:flex;align-items:center;gap:10px;padding:6px 10px;border-bottom:1px solid var(--border-soft)}.rawlog-body{flex:1;overflow:auto}.rawlog-head,.rawlog-row{display:grid;grid-template-columns:96px 60px 46px 76px minmax(0,1fr) 150px;gap:10px;align-items:center;padding:0 10px}.rawlog-head{position:sticky;top:0;z-index:2;height:26px;background:var(--surface-2);border-bottom:1px solid var(--border-soft);color:var(--muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.4px}.rawlog-row{position:absolute;left:0;right:0;height:22px;line-height:22px;border-bottom:1px solid var(--border-soft)}.rawlog-head>span,.rawlog-row>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.rawlog-row .c-f{color:var(--text)}.rawlog-row.k-command .c-k{color:var(--accent)}.rawlog-row.k-feedback .c-k{color:var(--ok)}.rawlog-row.k-special .c-k{color:var(--warn)}.message,.loading{color:var(--muted);text-align:center;padding:24px}.mode-switch{display:inline-flex;background:var(--surface-2);border:1px solid var(--border);border-radius:999px;padding:2px;gap:2px}.mode-tab{border:none;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:600;padding:4px 12px;border-radius:999px;transition:all .12s}.mode-tab:hover{color:var(--text)}.mode-tab.active{background:var(--accent);color:#fff}.btn.primary{background:var(--accent);color:#fff;border-color:transparent}.btn.primary:hover{filter:brightness(1.06)}.btn.ok{background:#16a34a24;color:var(--ok);border-color:#16a34a66}.btn.danger{background:#e11d4824;color:var(--err);border-color:#e11d4866}.control-form{padding:12px;overflow:auto;gap:0}.form-row{display:grid;grid-template-columns:92px 1fr;align-items:center;gap:8px;margin-bottom:8px}.form-row label{font-size:12px;color:var(--muted)}.form-row input,.form-row select{width:100%}.form-actions{display:flex;align-items:center;gap:8px;margin:10px 0 4px}.form-actions.wrap{flex-wrap:wrap}.form-actions .toggle{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--muted)}.form-actions .freq{width:64px}.form-error{color:var(--err);font-size:12px;margin-top:8px}.form-msg{color:var(--muted);font-size:12px;margin-top:8px}.registers-panel{overflow:hidden}.registers-body{flex:1;overflow:auto}.reg-table td{vertical-align:middle}.reg-table td:first-child{white-space:normal;max-width:160px}.reg-table input,.reg-table select{padding:3px 6px;font-size:11.5px}.reg-table .btn.small{padding:2px 8px} diff --git a/damiao_motor/gui/webapp/dist/index.html b/damiao_motor/gui/webapp/dist/index.html new file mode 100644 index 0000000..e628999 --- /dev/null +++ b/damiao_motor/gui/webapp/dist/index.html @@ -0,0 +1,13 @@ + + + + + + DaMiao Monitor + + + + +
+ + diff --git a/damiao_motor/gui/webapp/index.html b/damiao_motor/gui/webapp/index.html new file mode 100644 index 0000000..6a08fc0 --- /dev/null +++ b/damiao_motor/gui/webapp/index.html @@ -0,0 +1,12 @@ + + + + + + DaMiao Monitor + + +
+ + + diff --git a/damiao_motor/gui/webapp/package-lock.json b/damiao_motor/gui/webapp/package-lock.json new file mode 100644 index 0000000..d54be4a --- /dev/null +++ b/damiao_motor/gui/webapp/package-lock.json @@ -0,0 +1,2016 @@ +{ + "name": "damiao-monitor-webapp", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "damiao-monitor-webapp", + "version": "0.0.0", + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@tanstack/react-virtual": "^3.10.8", + "gridstack": "^11.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "uplot": "^1.6.31", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^6.0.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.3.tgz", + "integrity": "sha512-k/cnHPVaOfn46hSbiY6n4Dzf4QjCGWSF40zR5QIIYUqPAjpA6TN7InfYmcMiDVQGP2iUn9xsRbAl8u1v3UmeVQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.1.tgz", + "integrity": "sha512-VZyW2Uiml5tmBZwPGrSD3Sz73OxzljQMCmzYHsUTPEuTsERf5xwa+uWb01xEzkz3ZSYTjj8NEb/mKHvgKxyZdA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", + "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/gridstack": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/gridstack/-/gridstack-11.5.1.tgz", + "integrity": "sha512-qgbH65F6TtyKyi9t6fCkrxLhiobgYR3RBjnK0AzZl+YO7hreMVlsZ1MFbkPV0+7ZhjXdvcaRSZp3UA1yAJqYBQ==", + "funding": [ + { + "type": "paypal", + "url": "https://www.paypal.me/alaind831" + }, + { + "type": "venmo", + "url": "https://www.venmo.com/adumesny" + } + ], + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uplot": { + "version": "1.6.32", + "resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.32.tgz", + "integrity": "sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/damiao_motor/gui/webapp/package.json b/damiao_motor/gui/webapp/package.json new file mode 100644 index 0000000..322c32c --- /dev/null +++ b/damiao_motor/gui/webapp/package.json @@ -0,0 +1,30 @@ +{ + "name": "damiao-monitor-webapp", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@tanstack/react-virtual": "^3.10.8", + "gridstack": "^11.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "uplot": "^1.6.31", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^6.0.3" + }, + "allowScripts": { + "esbuild@0.25.12": true + } +} diff --git a/damiao_motor/gui/webapp/src/App.tsx b/damiao_motor/gui/webapp/src/App.tsx new file mode 100644 index 0000000..1bf9609 --- /dev/null +++ b/damiao_motor/gui/webapp/src/App.tsx @@ -0,0 +1,78 @@ +import { useEffect, useState } from "react"; +import { + DndContext, + DragOverlay, + PointerSensor, + useSensor, + useSensors, + type DragEndEvent, + type DragStartEvent, +} from "@dnd-kit/core"; + +import Toolbar from "./components/Toolbar"; +import SignalSidebar from "./components/SignalSidebar"; +import Canvas from "./components/Canvas"; +import { useApp } from "./lib/store"; +import { connectWs, fetchMotorTypes } from "./lib/ws"; +import { shortSignal } from "./lib/format"; +import { api } from "./lib/control"; + +export default function App() { + const addSignalToPlot = useApp((s) => s.addSignalToPlot); + const setMotorTypes = useApp((s) => s.setMotorTypes); + const setMode = useApp((s) => s.setMode); + const setStatus = useApp((s) => s.setStatus); + const setRegisterTable = useApp((s) => s.setRegisterTable); + const [dragLabel, setDragLabel] = useState(null); + + // a 4 px activation distance so clicks on chips don't accidentally start drags + const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } })); + + useEffect(() => { + // store hydrates plot configs synchronously at creation; just connect + load metadata + connectWs(); + fetchMotorTypes().then(setMotorTypes); + api.status().then((s) => { + if (s?.mode) setMode(s.mode); + setStatus(s); + }); + api.registerTable().then((d) => { + if (d?.registers) { + const t: Record = {}; + for (const r of d.registers) t[r.rid] = r; + setRegisterTable(t); + } + }); + }, [setMotorTypes, setMode, setStatus, setRegisterTable]); + + const onDragStart = (e: DragStartEvent) => { + const sid = e.active.data.current?.signalId as string | undefined; + setDragLabel(sid ? shortSignal(sid) : null); + }; + const onDragEnd = (e: DragEndEvent) => { + setDragLabel(null); + const sid = e.active.data.current?.signalId as string | undefined; + const overId = e.over?.id?.toString() || ""; + if (sid && overId.startsWith("plot:")) { + const panelId = e.over!.data.current?.panelId as string; + addSignalToPlot(panelId, sid); + } + }; + + return ( + +
+ +
+ +
+ +
+
+
+ + {dragLabel ?
{dragLabel}
: null} +
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/components/Canvas.tsx b/damiao_motor/gui/webapp/src/components/Canvas.tsx new file mode 100644 index 0000000..2df56df --- /dev/null +++ b/damiao_motor/gui/webapp/src/components/Canvas.tsx @@ -0,0 +1,121 @@ +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { GridStack, type GridStackNode } from "gridstack"; +import "gridstack/dist/gridstack.min.css"; + +import { useWidgets, type Widget } from "../lib/widgets"; +import { PANEL_BY_KIND } from "../panels/registry"; + +function WidgetFrame({ widget, onRemove }: { widget: Widget; onRemove: () => void }) { + const def = PANEL_BY_KIND[widget.kind]; + return ( +
+
+ + {def?.icon} + {def?.title || widget.kind} + +
+
{def ? def.render(widget.id) : null}
+
+ ); +} + +export default function Canvas() { + const widgets = useWidgets((s) => s.widgets); + const updateGeom = useWidgets((s) => s.updateGeom); + const removeWidget = useWidgets((s) => s.removeWidget); + + const rootRef = useRef(null); + const gridRef = useRef(null); + const itemEls = useRef>(new Map()); + const [contentEls, setContentEls] = useState>(new Map()); + const [ready, setReady] = useState(false); + + // init GridStack once + useEffect(() => { + if (!rootRef.current) return; + const grid = GridStack.init( + { + column: 12, + cellHeight: 56, + margin: 8, + float: true, + handle: ".widget-header", + resizable: { handles: "e, se, s, sw, w" }, + animate: true, + }, + rootRef.current + ); + gridRef.current = grid; + + grid.on("change", (_e, nodes) => { + const geoms = (nodes as GridStackNode[]).map((n) => ({ + id: String(n.id), + x: n.x ?? 0, + y: n.y ?? 0, + w: n.w ?? 1, + h: n.h ?? 1, + })); + if (geoms.length) updateGeom(geoms); + }); + + setReady(true); + return () => { + grid.destroy(false); + gridRef.current = null; + }; + }, [updateGeom]); + + // reconcile widget list -> gridstack items (add new, remove gone) + useEffect(() => { + const grid = gridRef.current; + if (!grid || !ready) return; + + const wanted = new Set(widgets.map((w) => w.id)); + let changed = false; + const nextContent = new Map(contentEls); + + // add new + grid.batchUpdate(); + for (const w of widgets) { + if (itemEls.current.has(w.id)) continue; + const el = grid.addWidget({ x: w.x, y: w.y, w: w.w, h: w.h, id: w.id }); + const content = el.querySelector(".grid-stack-item-content") as HTMLElement; + itemEls.current.set(w.id, el); + nextContent.set(w.id, content); + changed = true; + } + // remove gone + for (const [id, el] of Array.from(itemEls.current.entries())) { + if (!wanted.has(id)) { + grid.removeWidget(el, true); + itemEls.current.delete(id); + nextContent.delete(id); + changed = true; + } + } + grid.commit(); + + if (changed) setContentEls(nextContent); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [widgets, ready]); + + return ( +
+
+ {widgets.map((w) => { + const c = contentEls.get(w.id); + return c + ? createPortal( + removeWidget(w.id)} />, + c, + w.id + ) + : null; + })} +
+ ); +} diff --git a/damiao_motor/gui/webapp/src/components/SignalChip.tsx b/damiao_motor/gui/webapp/src/components/SignalChip.tsx new file mode 100644 index 0000000..b8862ac --- /dev/null +++ b/damiao_motor/gui/webapp/src/components/SignalChip.tsx @@ -0,0 +1,29 @@ +import { useDraggable } from "@dnd-kit/core"; +import type { SignalDescriptor } from "../lib/types"; +import { signalColor } from "../lib/format"; + +export default function SignalChip({ sig }: { sig: SignalDescriptor }) { + const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ + id: `sig:${sig.id}`, + data: { signalId: sig.id }, + }); + const color = signalColor(sig); + return ( +
+ + + {sig.source}.{sig.field} + + {sig.unit && {sig.unit}} +
+ ); +} diff --git a/damiao_motor/gui/webapp/src/components/SignalSidebar.tsx b/damiao_motor/gui/webapp/src/components/SignalSidebar.tsx new file mode 100644 index 0000000..091d7ab --- /dev/null +++ b/damiao_motor/gui/webapp/src/components/SignalSidebar.tsx @@ -0,0 +1,67 @@ +import { useMemo, useState } from "react"; +import { useApp } from "../lib/store"; +import { FIELD_ORDER } from "../lib/format"; +import SignalChip from "./SignalChip"; +import type { SignalDescriptor } from "../lib/types"; + +function sortSignals(sigs: SignalDescriptor[]): SignalDescriptor[] { + return [...sigs].sort((a, b) => { + if (a.source !== b.source) return a.source === "cmd" ? -1 : 1; + const ai = FIELD_ORDER.indexOf(a.field); + const bi = FIELD_ORDER.indexOf(b.field); + return (ai < 0 ? 99 : ai) - (bi < 0 ? 99 : bi); + }); +} + +export default function SignalSidebar() { + const signals = useApp((s) => s.signals); + const status = useApp((s) => s.status); + const [filter, setFilter] = useState(""); + + const byMotor = useMemo(() => { + const map = new Map(); + for (const s of signals) { + if (filter && !s.id.toLowerCase().includes(filter.toLowerCase())) continue; + const arr = map.get(s.motorId) || []; + arr.push(s); + map.set(s.motorId, arr); + } + return Array.from(map.entries()).sort((a, b) => a[0] - b[0]); + }, [signals, filter]); + + return ( + + ); +} diff --git a/damiao_motor/gui/webapp/src/components/Toolbar.tsx b/damiao_motor/gui/webapp/src/components/Toolbar.tsx new file mode 100644 index 0000000..75ddbb5 --- /dev/null +++ b/damiao_motor/gui/webapp/src/components/Toolbar.tsx @@ -0,0 +1,82 @@ +import { useState } from "react"; +import { useApp } from "../lib/store"; +import { useWidgets } from "../lib/widgets"; +import { PANELS } from "../panels/registry"; +import { getTheme, setTheme, type Theme } from "../lib/theme"; +import { api } from "../lib/control"; + +export default function Toolbar() { + const connected = useApp((s) => s.connected); + const status = useApp((s) => s.status); + const mode = useApp((s) => s.mode); + const setMode = useApp((s) => s.setMode); + const setControlMotors = useApp((s) => s.setControlMotors); + const setCurrentMotor = useApp((s) => s.setCurrentMotor); + const addWidget = useWidgets((s) => s.addWidget); + const resetWidgets = useWidgets((s) => s.resetWidgets); + const [theme, setThemeState] = useState(getTheme()); + + const toggleTheme = () => { + const next: Theme = theme === "light" ? "dark" : "light"; + setTheme(next); + setThemeState(next); + }; + + const switchMode = async (m: "monitor" | "control") => { + if (m === mode) return; + setMode(m); + setControlMotors([]); + setCurrentMotor(null); + await api.setMode(m); + }; + + const busLabel = status?.demo ? "demo" : status?.channel || "—"; + + return ( +
+
+ + DaMiao Studio +
+ +
+ + +
+ +
+ + {busLabel} + {mode === "control" ? ( + active · TX + ) : ( + + listen-only + + )} + {status?.error && error} + {status && ( + {status.framesSeen?.toLocaleString?.() ?? 0} frames + )} +
+ +
+ +
+ {PANELS.map((p) => ( + + ))} + + +
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/index.css b/damiao_motor/gui/webapp/src/index.css new file mode 100644 index 0000000..97d6814 --- /dev/null +++ b/damiao_motor/gui/webapp/src/index.css @@ -0,0 +1,297 @@ +:root { + /* light theme (default) */ + --bg: #f4f6f9; + --bg-1: #eef1f6; + --surface: #ffffff; + --surface-2: #eef2f7; + --hover: #e6ecf3; + --border: #d6dde7; + --border-soft: #e7ecf2; + --text: #1e2733; + --muted: #5f6a78; + --accent: #2f6fed; + --ok: #16a34a; + --warn: #d97706; + --err: #e11d48; + --radius: 14px; + --radius-sm: 9px; + --shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 24px -16px rgba(16, 24, 40, 0.28); + --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; +} + +:root[data-theme="dark"] { + --bg: #0f1216; + --bg-1: #141a21; + --surface: #171d25; + --surface-2: #1d242e; + --hover: #232c38; + --border: #262e3a; + --border-soft: #1f2630; + --text: #d7dde5; + --muted: #8a94a3; + --accent: #6aa3ff; + --ok: #4ade80; + --warn: #fbbf24; + --err: #fb7185; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 10px 28px -16px rgba(0, 0, 0, 0.65); +} + +* { box-sizing: border-box; } +html, body, #root { height: 100%; margin: 0; } +body { + font-family: var(--font); + background: var(--bg); + color: var(--text); + font-size: 13px; + -webkit-font-smoothing: antialiased; +} +.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; } +.muted { color: var(--muted); } +.small { font-size: 11px; } +.center { text-align: center; } +.pad { padding: 16px; } +.strong { font-weight: 650; } +.dim { opacity: 0.5; } + +.app { display: flex; flex-direction: column; height: 100%; } +.body { flex: 1; display: flex; min-height: 0; } +.canvas-host { flex: 1; min-width: 0; position: relative; overflow: auto; } +.canvas { min-height: 100%; padding: 6px; } + +/* --------------------------------------------------------------- toolbar */ +.toolbar { + display: flex; + align-items: center; + gap: 16px; + height: 52px; + padding: 0 16px; + background: var(--surface); + border-bottom: 1px solid var(--border); +} +.brand { font-weight: 650; font-size: 15px; letter-spacing: 0.2px; display: flex; align-items: center; gap: 9px; } +.brand-sub { color: var(--muted); font-weight: 500; font-size: 12px; } +.brand-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 12px var(--accent); } +.conn { display: flex; align-items: center; gap: 9px; } +.conn .dot { width: 8px; height: 8px; border-radius: 50%; } +.dot.on { background: var(--ok); box-shadow: 0 0 9px var(--ok); } +.dot.off { background: var(--err); } +.spacer { flex: 1; } +.actions { display: flex; gap: 7px; } + +.badge { + font-size: 10.5px; padding: 2px 8px; border-radius: 999px; font-weight: 650; + border: 1px solid transparent; text-transform: uppercase; letter-spacing: 0.4px; +} +.badge.ok { color: var(--ok); border-color: rgba(74,222,128,0.35); background: rgba(74,222,128,0.1); } +.badge.warn { color: var(--warn); border-color: rgba(251,191,36,0.35); background: rgba(251,191,36,0.1); } +.badge.err { color: var(--err); border-color: rgba(251,113,133,0.35); background: rgba(251,113,133,0.1); } + +.btn { + background: var(--surface-2); color: var(--text); border: 1px solid var(--border); + border-radius: var(--radius-sm); padding: 6px 11px; font-size: 12px; cursor: pointer; + transition: background 0.15s, border-color 0.15s, transform 0.05s; +} +.btn:hover { background: var(--hover); border-color: var(--border); } +.btn:active { transform: translateY(1px); } +.btn.ghost { background: transparent; } +.btn.small { padding: 3px 9px; font-size: 11px; } +.btn.active { border-color: var(--accent); color: var(--accent); } +.btn-icon { color: var(--accent); margin-right: 2px; } + +/* --------------------------------------------------------------- sidebar */ +.sidebar { + width: 236px; flex-shrink: 0; background: var(--bg-1); + border-right: 1px solid var(--border-soft); display: flex; flex-direction: column; +} +.sidebar-head { padding: 12px 14px; border-bottom: 1px solid var(--border-soft); } +.sidebar-title { font-weight: 650; margin-bottom: 9px; } +.filter, .type-select, select, input[type="text"], input[type="number"] { + width: 100%; background: var(--surface-2); border: 1px solid var(--border); + color: var(--text); border-radius: var(--radius-sm); padding: 6px 9px; font-size: 12px; + outline: none; transition: border-color 0.15s, box-shadow 0.15s; +} +.filter:focus, select:focus, input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(106,163,255,0.15); } +.sidebar-body { flex: 1; overflow-y: auto; padding: 10px; } +.sidebar-foot { padding: 10px 14px; border-top: 1px solid var(--border-soft); font-size: 11px; line-height: 1.55; color: var(--muted); } +.motor-group { margin-bottom: 14px; } +.motor-group-title { + font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px; + color: var(--muted); margin: 0 2px 6px; +} +.chips { display: flex; flex-direction: column; gap: 5px; } + +.sig-chip { + display: flex; align-items: center; gap: 8px; padding: 6px 9px; + background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); + cursor: grab; user-select: none; font-size: 12px; transition: background 0.12s, border-color 0.12s; +} +.sig-chip:hover { background: var(--surface-2); border-color: var(--border); } +.sig-chip.dragging { opacity: 0.4; } +.sig-swatch { width: 10px; height: 10px; border-radius: 3px; border: 2px solid; flex-shrink: 0; } +.sig-name { flex: 1; font-family: var(--mono); } +.sig-unit { color: var(--muted); font-size: 10.5px; } + +.drag-ghost { + background: var(--accent); color: #05203f; font-weight: 650; font-size: 12px; + padding: 6px 11px; border-radius: var(--radius-sm); font-family: var(--mono); + box-shadow: 0 10px 26px rgba(0,0,0,0.55); +} + +/* ----------------------------------------------------- gridstack + widget */ +.grid-stack { background: transparent; } +.grid-stack-item-content { + inset: 0; + overflow: visible; + background: transparent; + border: none; +} +.widget { + height: 100%; + display: flex; + flex-direction: column; + background: var(--surface); + border: 1px solid var(--border-soft); + border-radius: var(--radius); + box-shadow: var(--shadow); + overflow: hidden; +} +.widget-header { + display: flex; align-items: center; gap: 8px; + height: 34px; padding: 0 8px 0 10px; flex-shrink: 0; + border-bottom: 1px solid var(--border-soft); + background: var(--surface-2); + cursor: move; +} +.widget-grip { color: var(--muted); opacity: 0.5; font-size: 12px; letter-spacing: -2px; } +.widget-icon { color: var(--accent); font-size: 12px; } +.widget-title { flex: 1; font-size: 12.5px; font-weight: 600; letter-spacing: 0.2px; } +.widget-close { + width: 22px; height: 22px; border: none; background: transparent; color: var(--muted); + border-radius: 6px; cursor: pointer; font-size: 16px; line-height: 1; opacity: 0; + transition: opacity 0.12s, background 0.12s, color 0.12s; +} +.widget:hover .widget-close { opacity: 1; } +.widget-close:hover { background: rgba(251,113,133,0.15); color: var(--err); } +.widget-body { flex: 1; min-height: 0; position: relative; } +.widget-body .panel { height: 100%; } + +/* gridstack resize handles: subtle */ +.grid-stack-item > .ui-resizable-handle { filter: opacity(0.45); } +.grid-stack-item:hover > .ui-resizable-handle { filter: opacity(0.9); } +.grid-stack-placeholder > .placeholder-content { + border: 1px dashed var(--accent); border-radius: var(--radius); + background: rgba(106,163,255,0.06); +} + +/* ------------------------------------------------------------- panels */ +.panel { height: 100%; display: flex; flex-direction: column; overflow: hidden; } + +.plot-toolbar { + display: flex; align-items: center; gap: 8px; padding: 6px 10px; + border-bottom: 1px solid var(--border-soft); flex-wrap: wrap; +} +.plot-toolbar select { width: auto; } +.legend { display: flex; gap: 6px; flex-wrap: wrap; } +.legend-chip { + display: inline-flex; align-items: center; gap: 5px; font-size: 11px; + padding: 2px 7px 2px 6px; border: 1px solid var(--border); border-radius: 999px; + font-family: var(--mono); +} +.legend-swatch { width: 9px; height: 9px; border-radius: 2px; border: 1.5px solid; } +.legend-x { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 13px; padding: 0 0 0 2px; } +.legend-x:hover { color: var(--err); } +.plot-host { flex: 1; min-height: 0; position: relative; padding: 6px; } +.plot-host.drop-over { outline: 2px dashed var(--accent); outline-offset: -5px; background: rgba(106,163,255,0.06); border-radius: 10px; } +.drop-hint { + position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; + color: var(--muted); font-size: 12px; pointer-events: none; text-align: center; padding: 22px; +} + +/* table */ +.table-panel { overflow: auto; } +.motor-table { width: 100%; border-collapse: collapse; font-size: 12px; } +.motor-table th, .motor-table td { padding: 6px 10px; text-align: right; border-bottom: 1px solid var(--border-soft); white-space: nowrap; } +.motor-table th:first-child, .motor-table td:first-child { text-align: left; } +.motor-table th { + position: sticky; top: 0; background: var(--surface-2); color: var(--muted); + font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; +} +.motor-table tr:hover td { background: var(--hover); } +.cmd-col { color: var(--accent); } +.status-pill { font-size: 10px; padding: 2px 8px; border-radius: 999px; font-weight: 650; } +.status-pill.ok { color: var(--ok); background: rgba(74,222,128,0.12); } +.status-pill.off { color: var(--muted); background: rgba(138,148,163,0.12); } +.status-pill.warn { color: var(--warn); background: rgba(251,191,36,0.12); } + +/* cards */ +.cards-panel { overflow: auto; } +.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 11px; padding: 12px; } +.motor-card { background: var(--bg-1); border: 1px solid var(--border-soft); border-radius: 12px; padding: 13px; } +.motor-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } +.motor-card-sub { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 11px; } +.type-select { width: auto; padding: 3px 7px; font-size: 11px; } +.metric { margin-bottom: 9px; } +.metric-label { font-size: 11px; color: var(--text); margin-bottom: 3px; } +.metric-values { display: flex; align-items: baseline; gap: 10px; } +.metric-act { font-family: var(--mono); font-size: 20px; font-weight: 650; } +.metric-cmd { font-family: var(--mono); font-size: 12px; color: var(--accent); } +.temp-row { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); margin-top: 7px; border-top: 1px solid var(--border-soft); padding-top: 7px; } + +/* raw log */ +.rawlog-panel { font-size: 11.5px; } +.rawlog-toolbar { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border-bottom: 1px solid var(--border-soft); } +.rawlog-body { flex: 1; overflow: auto; } +.rawlog-head, .rawlog-row { + display: grid; + grid-template-columns: 96px 60px 46px 76px minmax(0, 1fr) 150px; + gap: 10px; align-items: center; padding: 0 10px; +} +.rawlog-head { + position: sticky; top: 0; z-index: 2; height: 26px; + background: var(--surface-2); border-bottom: 1px solid var(--border-soft); + color: var(--muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.4px; +} +.rawlog-row { position: absolute; left: 0; right: 0; height: 22px; line-height: 22px; border-bottom: 1px solid var(--border-soft); } +.rawlog-head > span, .rawlog-row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } +.rawlog-row .c-f { color: var(--text); } +.rawlog-row.k-command .c-k { color: var(--accent); } +.rawlog-row.k-feedback .c-k { color: var(--ok); } +.rawlog-row.k-special .c-k { color: var(--warn); } + +.message, .loading { color: var(--muted); text-align: center; padding: 24px; } + +/* ----------------------------------------------------- mode switch */ +.mode-switch { display: inline-flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 2px; gap: 2px; } +.mode-tab { + border: none; background: transparent; color: var(--muted); cursor: pointer; + font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 999px; transition: all 0.12s; +} +.mode-tab:hover { color: var(--text); } +.mode-tab.active { background: var(--accent); color: #fff; } + +/* button color variants */ +.btn.primary { background: var(--accent); color: #fff; border-color: transparent; } +.btn.primary:hover { filter: brightness(1.06); } +.btn.ok { background: rgba(22,163,74,0.14); color: var(--ok); border-color: rgba(22,163,74,0.4); } +.btn.danger { background: rgba(225,29,72,0.14); color: var(--err); border-color: rgba(225,29,72,0.4); } + +/* ----------------------------------------------------- control forms */ +.control-form { padding: 12px; overflow: auto; gap: 0; } +.form-row { display: grid; grid-template-columns: 92px 1fr; align-items: center; gap: 8px; margin-bottom: 8px; } +.form-row label { font-size: 12px; color: var(--muted); } +.form-row input, .form-row select { width: 100%; } +.form-actions { display: flex; align-items: center; gap: 8px; margin: 10px 0 4px; } +.form-actions.wrap { flex-wrap: wrap; } +.form-actions .toggle { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); } +.form-actions .freq { width: 64px; } +.form-error { color: var(--err); font-size: 12px; margin-top: 8px; } +.form-msg { color: var(--muted); font-size: 12px; margin-top: 8px; } + +/* registers panel */ +.registers-panel { overflow: hidden; } +.registers-body { flex: 1; overflow: auto; } +.reg-table td { vertical-align: middle; } +.reg-table td:first-child { white-space: normal; max-width: 160px; } +.reg-table input, .reg-table select { padding: 3px 6px; font-size: 11.5px; } +.reg-table .btn.small { padding: 2px 8px; } diff --git a/damiao_motor/gui/webapp/src/lib/control.ts b/damiao_motor/gui/webapp/src/lib/control.ts new file mode 100644 index 0000000..7cf62a0 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/control.ts @@ -0,0 +1,50 @@ +/** REST client for the unified server (control + common endpoints). */ + +async function jget(url: string): Promise { + const r = await fetch(url); + return r.json(); +} +async function jpost(url: string, body?: any): Promise { + const r = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body || {}), + }); + return r.json(); +} +async function jput(url: string, body: any): Promise { + const r = await fetch(url, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + return r.json(); +} + +export const api = { + status: () => jget("/api/status"), + setMode: (mode: "control" | "monitor") => jpost("/api/mode", { mode }), + connect: (body: { channel: string; bustype: string; bitrate?: number | null; motor_type?: string; feedback_offset?: number }) => + jpost("/api/connect", body), + disconnect: () => jpost("/api/disconnect"), + + scan: (motor_type: string) => jpost("/api/control/scan", { motor_type }), + motors: () => jget("/api/control/motors"), + enable: (id: number) => jpost(`/api/control/motors/${id}/enable`), + disable: (id: number) => jpost(`/api/control/motors/${id}/disable`), + setZero: (id: number) => jpost(`/api/control/motors/${id}/set-zero`), + clearError: (id: number) => jpost(`/api/control/motors/${id}/clear-error`), + storeParams: (id: number) => jpost(`/api/control/motors/${id}/store-parameters`), + command: (id: number, body: any) => jpost(`/api/control/motors/${id}/command`, body), + state: (id: number) => jget(`/api/control/motors/${id}/state`), + getRegisters: (id: number) => jget(`/api/control/motors/${id}/registers`), + setRegister: (id: number, rid: number, value: number) => + jput(`/api/control/motors/${id}/registers/${rid}`, { value }), + setMotorType: (id: number, motor_type: string) => + jput(`/api/control/motors/${id}/motor-type`, { motor_type }), + + registerTable: () => jget("/api/register-table"), + motorTypes: () => jget("/api/motor-types"), + canInterfaces: (bustype: string) => jget(`/api/can-interfaces?bustype=${bustype}`), + platform: () => jget("/api/platform"), +}; diff --git a/damiao_motor/gui/webapp/src/lib/dataStore.ts b/damiao_motor/gui/webapp/src/lib/dataStore.ts new file mode 100644 index 0000000..08bb8e0 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/dataStore.ts @@ -0,0 +1,156 @@ +/** + * Out-of-React time-series store. + * + * Holds a fixed-capacity ring buffer of (t, value) per signal so that high-rate sample + * ingestion never triggers React re-renders. Plot panels read contiguous arrays from + * here on a requestAnimationFrame loop. Also tracks ref-counted subscriptions so the WS + * client can stream only the signals that some panel is currently displaying. + */ + +import type { RawFrame } from "./types"; + +const CAPACITY = 6000; // ~ up to 60 s at 100 Hz per signal + +class Ring { + t: Float64Array; + v: Float64Array; + len = 0; + head = 0; // index of next write + constructor(cap = CAPACITY) { + this.t = new Float64Array(cap); + this.v = new Float64Array(cap); + } + push(t: number, v: number) { + const cap = this.t.length; + this.t[this.head] = t; + this.v[this.head] = v; + this.head = (this.head + 1) % cap; + if (this.len < cap) this.len++; + } + /** Return chronological contiguous arrays for points with t >= sinceT. */ + read(sinceT = -Infinity): { t: Float64Array; v: Float64Array } { + const cap = this.t.length; + const n = this.len; + const start = (this.head - n + cap) % cap; + const ts = new Float64Array(n); + const vs = new Float64Array(n); + let k = 0; + for (let i = 0; i < n; i++) { + const idx = (start + i) % cap; + if (this.t[idx] >= sinceT) { + ts[k] = this.t[idx]; + vs[k] = this.v[idx]; + k++; + } + } + return { t: ts.subarray(0, k), v: vs.subarray(0, k) }; + } + last(): number | null { + if (this.len === 0) return null; + const cap = this.t.length; + return this.v[(this.head - 1 + cap) % cap]; + } +} + +const rings = new Map(); + +function ring(id: string): Ring { + let r = rings.get(id); + if (!r) { + r = new Ring(); + rings.set(id, r); + } + return r; +} + +export function appendSample(id: string, t: number, v: number) { + ring(id).push(t, v); +} + +export function appendBatch(id: string, points: [number, number][]) { + const r = ring(id); + for (const [t, v] of points) r.push(t, v); +} + +export function readSeries(id: string, sinceT = -Infinity) { + const r = rings.get(id); + if (!r) return { t: new Float64Array(0), v: new Float64Array(0) }; + return r.read(sinceT); +} + +export function lastValue(id: string): number | null { + const r = rings.get(id); + return r ? r.last() : null; +} + +// ---------------------------------------------------------------- subscriptions +const subCounts = new Map(); +let subListeners: (() => void)[] = []; + +function notifySubs() { + subListeners.forEach((fn) => fn()); +} + +export function subscribeSignal(id: string) { + subCounts.set(id, (subCounts.get(id) || 0) + 1); + notifySubs(); +} + +export function unsubscribeSignal(id: string) { + const c = (subCounts.get(id) || 0) - 1; + if (c <= 0) subCounts.delete(id); + else subCounts.set(id, c); + notifySubs(); +} + +export function currentSubscriptions(): string[] { + return Array.from(subCounts.keys()); +} + +export function onSubscriptionsChanged(fn: () => void): () => void { + subListeners.push(fn); + return () => { + subListeners = subListeners.filter((f) => f !== fn); + }; +} + +// ----------------------------------------------------------------- raw frames +const RAW_CAP = 3000; +let rawFrames: RawFrame[] = []; +let rawListeners: (() => void)[] = []; + +export function pushRawFrames(frames: RawFrame[]) { + if (!frames.length) return; + rawFrames = rawFrames.concat(frames); + if (rawFrames.length > RAW_CAP) rawFrames = rawFrames.slice(-RAW_CAP); + rawListeners.forEach((fn) => fn()); +} + +export function getRawFrames(): RawFrame[] { + return rawFrames; +} + +export function onRawFrames(fn: () => void): () => void { + rawListeners.push(fn); + return () => { + rawListeners = rawListeners.filter((f) => f !== fn); + }; +} + +// raw enable ref-count (so /stream only sends raw when a RawLog panel is open) +let rawWanted = 0; +let rawWantListeners: (() => void)[] = []; +export function wantRaw(on: boolean) { + rawWanted += on ? 1 : -1; + if (rawWanted < 0) rawWanted = 0; + rawWantListeners.forEach((fn) => fn()); +} +export function isRawWanted(): boolean { + return rawWanted > 0; +} +export function onRawWantChanged(fn: () => void): () => void { + rawWantListeners.push(fn); + return () => { + rawWantListeners = rawWantListeners.filter((f) => f !== fn); + }; +} diff --git a/damiao_motor/gui/webapp/src/lib/format.ts b/damiao_motor/gui/webapp/src/lib/format.ts new file mode 100644 index 0000000..9031762 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/format.ts @@ -0,0 +1,70 @@ +import type { SignalDescriptor } from "./types"; + +// Color per physical field; cmd vs fb share a hue, distinguished by dash + brightness. +const FIELD_COLORS: Record = { + pos: "#58a6ff", + vel: "#3fb950", + torque: "#d29922", + kp: "#bc8cff", + kd: "#f778ba", + vel_limit: "#56d4dd", + torque_limit: "#e3b341", + t_mos: "#ff7b72", + t_rotor: "#ffa657", + status_code: "#8b949e", +}; + +export function fieldColor(field: string): string { + return FIELD_COLORS[field] || "#8b949e"; +} + +export function signalColor(sig: { field: string; source: string }): string { + const base = fieldColor(sig.field); + return sig.source === "cmd" ? lighten(base, 0.15) : base; +} + +export function withAlpha(hex: string, a: number): string { + const c = hex.replace("#", ""); + const r = parseInt(c.slice(0, 2), 16); + const g = parseInt(c.slice(2, 4), 16); + const b = parseInt(c.slice(4, 6), 16); + return `rgba(${r},${g},${b},${a})`; +} + +/** Plot stroke style: actual = bold solid; command = fainter, thinner "ghost" of the same hue. */ +export function seriesStyle(sig: { field: string; source: string }): { stroke: string; width: number } { + const base = fieldColor(sig.field); + return sig.source === "cmd" + ? { stroke: withAlpha(base, 0.45), width: 1.25 } + : { stroke: base, width: 1.85 }; +} + +export function signalLabel(sig: SignalDescriptor): string { + return `m${sig.motorId} ${sig.source}.${sig.field}`; +} + +export function shortSignal(id: string): string { + // "bus:m1:cmd.pos" -> "m1 cmd.pos" + const parts = id.split(":"); + if (parts.length >= 3) return `${parts[1]} ${parts[2]}`; + return id; +} + +export function isCmd(id: string): boolean { + return id.includes(":cmd."); +} + +export const FIELD_ORDER = ["pos", "vel", "torque", "kp", "kd", "t_mos", "t_rotor"]; + +function lighten(hex: string, amount: number): string { + const c = hex.replace("#", ""); + const r = Math.min(255, Math.round(parseInt(c.slice(0, 2), 16) + 255 * amount)); + const g = Math.min(255, Math.round(parseInt(c.slice(2, 4), 16) + 255 * amount)); + const b = Math.min(255, Math.round(parseInt(c.slice(4, 6), 16) + 255 * amount)); + return `rgb(${r},${g},${b})`; +} + +export function fmt(v: number | null | undefined, digits = 3): string { + if (v == null || Number.isNaN(v)) return "—"; + return v.toFixed(digits); +} diff --git a/damiao_motor/gui/webapp/src/lib/store.ts b/damiao_motor/gui/webapp/src/lib/store.ts new file mode 100644 index 0000000..72dff97 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/store.ts @@ -0,0 +1,136 @@ +/** Low-frequency app state (registry, status, motor views, panel configs, layout). */ + +import { create } from "zustand"; +import type { MotorView, Pair, ServerStatus, SignalDescriptor } from "./types"; + +export interface PlotConfig { + signals: string[]; + duration: number; // seconds visible +} + +// Persistence helpers must be defined BEFORE the store, because the store initializer +// calls loadPlotConfigs() synchronously (a `const` declared later would be in the TDZ). +const PLOT_KEY = "damiao.monitor.plotConfigs"; +export function loadPlotConfigs(): Record { + try { + return JSON.parse(localStorage.getItem(PLOT_KEY) || "{}"); + } catch { + return {}; + } +} +export function persistPlotConfigs(cfgs: Record) { + try { + localStorage.setItem(PLOT_KEY, JSON.stringify(cfgs)); + } catch { + /* ignore quota */ + } +} + +export interface RegisterInfo { + rid: number; + variable: string; + description: string; + access: string; + range_str: string; + data_type: string; +} + +interface AppState { + connected: boolean; + status: ServerStatus | null; + signals: SignalDescriptor[]; + pairs: Pair[]; + motors: MotorView[]; + motorTypes: string[]; + // per-panel plot configs (signals shown), persisted alongside the dock layout + plotConfigs: Record; + + // control state + mode: "monitor" | "control"; + controlMotors: { id: number; motor_type: string }[]; + currentMotorId: number | null; + registerTable: Record; + + setConnected: (c: boolean) => void; + setStatus: (s: ServerStatus) => void; + setMeta: (signals: SignalDescriptor[], pairs: Pair[]) => void; + setMotors: (m: MotorView[]) => void; + setMotorTypes: (t: string[]) => void; + setMode: (m: "monitor" | "control") => void; + setControlMotors: (m: { id: number; motor_type: string }[]) => void; + setCurrentMotor: (id: number | null) => void; + setRegisterTable: (t: Record) => void; + + ensurePlot: (id: string) => void; + setPlotConfig: (id: string, cfg: Partial) => void; + addSignalToPlot: (id: string, signalId: string) => void; + removeSignalFromPlot: (id: string, signalId: string) => void; + dropPlot: (id: string) => void; +} + +export const useApp = create((set, get) => ({ + connected: false, + status: null, + signals: [], + pairs: [], + motors: [], + motorTypes: [], + plotConfigs: loadPlotConfigs(), // hydrate synchronously to avoid effect-ordering races + + mode: "monitor", + controlMotors: [], + currentMotorId: null, + registerTable: {}, + + setConnected: (c) => set({ connected: c }), + setStatus: (s) => set({ status: s }), + setMeta: (signals, pairs) => set({ signals, pairs }), + setMotors: (motors) => set({ motors }), + setMotorTypes: (motorTypes) => set({ motorTypes }), + setMode: (mode) => set({ mode }), + setControlMotors: (controlMotors) => set({ controlMotors }), + setCurrentMotor: (currentMotorId) => set({ currentMotorId }), + setRegisterTable: (registerTable) => set({ registerTable }), + + ensurePlot: (id) => + set((st) => + st.plotConfigs[id] + ? st + : { plotConfigs: { ...st.plotConfigs, [id]: { signals: [], duration: 10 } } } + ), + setPlotConfig: (id, cfg) => + set((st) => ({ + plotConfigs: { + ...st.plotConfigs, + [id]: { ...(st.plotConfigs[id] || { signals: [], duration: 10 }), ...cfg }, + }, + })), + addSignalToPlot: (id, signalId) => + set((st) => { + const cur = st.plotConfigs[id] || { signals: [], duration: 10 }; + if (cur.signals.includes(signalId)) return st; + return { + plotConfigs: { ...st.plotConfigs, [id]: { ...cur, signals: [...cur.signals, signalId] } }, + }; + }), + removeSignalFromPlot: (id, signalId) => + set((st) => { + const cur = st.plotConfigs[id]; + if (!cur) return st; + return { + plotConfigs: { + ...st.plotConfigs, + [id]: { ...cur, signals: cur.signals.filter((s) => s !== signalId) }, + }, + }; + }), + dropPlot: (id) => + set((st) => { + const next = { ...st.plotConfigs }; + delete next[id]; + return { plotConfigs: next }; + }), +})); + +// persist plot configs whenever they change (dock layout persisted by the Dock component) +useApp.subscribe((st) => persistPlotConfigs(st.plotConfigs)); diff --git a/damiao_motor/gui/webapp/src/lib/theme.ts b/damiao_motor/gui/webapp/src/lib/theme.ts new file mode 100644 index 0000000..0521731 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/theme.ts @@ -0,0 +1,26 @@ +/** Light/dark theme, default light, persisted. Applied via data-theme on . */ + +export type Theme = "light" | "dark"; +const KEY = "damiao.monitor.theme"; + +export function getTheme(): Theme { + const t = localStorage.getItem(KEY); + return t === "dark" ? "dark" : "light"; // default light +} + +export function applyTheme(t: Theme) { + document.documentElement.setAttribute("data-theme", t); +} + +export function setTheme(t: Theme) { + try { + localStorage.setItem(KEY, t); + } catch { + /* ignore */ + } + applyTheme(t); +} + +export function initTheme() { + applyTheme(getTheme()); +} diff --git a/damiao_motor/gui/webapp/src/lib/types.ts b/damiao_motor/gui/webapp/src/lib/types.ts new file mode 100644 index 0000000..f5e2194 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/types.ts @@ -0,0 +1,54 @@ +export interface SignalDescriptor { + id: string; + bus: string; + motorId: number; + source: "cmd" | "fb"; + field: string; + unit: string; + pairKey: string; +} + +export interface Pair { + pairKey: string; + cmd: string; + fb: string; +} + +export interface MotorView { + bus: string; + motorId: number; + mode: string | null; + status: string; + lastT: number; + cmd: Record; + fb: Record; +} + +export interface ServerStatus { + mode: "monitor" | "control"; + connected: boolean; + channel: string | null; + bustype: string; + error: string | null; + listenOnly: boolean; + feedbackOffset: number; + framesSeen: number; + decodeErrors: number; + registryVersion: number; + defaultMotorType: string; + demo?: boolean; +} + +export interface RawFrame { + seq: number; + t: number; + arb: number; + kind: string; + mode: string | null; + motorId: number; + note: string; + fields: Record; + raw: string; +} + +// Panel kinds are open-ended; see panels/registry.tsx for the registered set. diff --git a/damiao_motor/gui/webapp/src/lib/widgets.ts b/damiao_motor/gui/webapp/src/lib/widgets.ts new file mode 100644 index 0000000..b43c79b --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/widgets.ts @@ -0,0 +1,94 @@ +/** Free-form widget canvas state: a list of widgets with grid geometry, persisted. */ + +import { create } from "zustand"; + +export interface Widget { + id: string; + kind: string; // panel kind from panels/registry + x: number; + y: number; + w: number; + h: number; +} + +const KEY = "damiao.monitor.widgets.v3"; + +function load(): Widget[] | null { + try { + const raw = localStorage.getItem(KEY); + if (!raw) return null; + const arr = JSON.parse(raw); + return Array.isArray(arr) && arr.length ? arr : null; + } catch { + return null; + } +} + +function persist(widgets: Widget[]) { + try { + localStorage.setItem(KEY, JSON.stringify(widgets)); + } catch { + /* ignore quota */ + } +} + +const DEFAULT_WIDGETS: Widget[] = [ + { id: "connection-1", kind: "connection", x: 0, y: 0, w: 3, h: 4 }, + { id: "control-1", kind: "control", x: 0, y: 4, w: 3, h: 8 }, + { id: "plot-1", kind: "plot", x: 3, y: 0, w: 6, h: 6 }, + { id: "cards-1", kind: "cards", x: 9, y: 0, w: 3, h: 6 }, + { id: "table-1", kind: "table", x: 3, y: 6, w: 6, h: 6 }, + { id: "registers-1", kind: "registers", x: 9, y: 6, w: 3, h: 6 }, +]; + +let counter = 1; + +interface WidgetState { + widgets: Widget[]; + addWidget: (kind: string) => string; + removeWidget: (id: string) => void; + updateGeom: (geoms: { id: string; x: number; y: number; w: number; h: number }[]) => void; + resetWidgets: () => void; +} + +export const useWidgets = create((set, get) => ({ + widgets: load() || DEFAULT_WIDGETS, + + addWidget: (kind) => { + counter += 1; + const id = `${kind}-${Date.now().toString(36)}-${counter}`; + // place new widget at the bottom; GridStack will reflow/auto-position + const maxY = get().widgets.reduce((m, w) => Math.max(m, w.y + w.h), 0); + const widget: Widget = { id, kind, x: 0, y: maxY, w: 6, h: 5 }; + const next = [...get().widgets, widget]; + persist(next); + set({ widgets: next }); + return id; + }, + + removeWidget: (id) => { + const next = get().widgets.filter((w) => w.id !== id); + persist(next); + set({ widgets: next }); + }, + + updateGeom: (geoms) => { + const byId = new Map(geoms.map((g) => [g.id, g])); + const next = get().widgets.map((w) => { + const g = byId.get(w.id); + return g ? { ...w, x: g.x, y: g.y, w: g.w, h: g.h } : w; + }); + persist(next); + set({ widgets: next }); + }, + + resetWidgets: () => { + try { + localStorage.removeItem(KEY); + localStorage.removeItem("damiao.monitor.plotConfigs"); + } catch { + /* ignore */ + } + set({ widgets: DEFAULT_WIDGETS.map((w) => ({ ...w })) }); + }, +})); diff --git a/damiao_motor/gui/webapp/src/lib/ws.ts b/damiao_motor/gui/webapp/src/lib/ws.ts new file mode 100644 index 0000000..d3270b6 --- /dev/null +++ b/damiao_motor/gui/webapp/src/lib/ws.ts @@ -0,0 +1,117 @@ +/** WebSocket client: feeds the data store + zustand app state. Auto-reconnects. */ + +import { + appendBatch, + currentSubscriptions, + isRawWanted, + onRawWantChanged, + onSubscriptionsChanged, + pushRawFrames, +} from "./dataStore"; +import { useApp } from "./store"; +import type { MotorView, Pair, RawFrame, ServerStatus, SignalDescriptor } from "./types"; + +let ws: WebSocket | null = null; +let reconnectTimer: number | null = null; + +function wsUrl(): string { + const proto = location.protocol === "https:" ? "wss" : "ws"; + return `${proto}://${location.host}/api/monitor/stream`; +} + +function sendSubscribe() { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: "subscribe", signals: currentSubscriptions() })); + } +} +function sendRaw() { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: "raw", enabled: isRawWanted() })); + } +} + +export function connectWs() { + const sock = new WebSocket(wsUrl()); + ws = sock; + + sock.onopen = () => { + useApp.getState().setConnected(true); + sendSubscribe(); + sendRaw(); + }; + + sock.onclose = () => { + useApp.getState().setConnected(false); + if (reconnectTimer == null) { + reconnectTimer = window.setTimeout(() => { + reconnectTimer = null; + connectWs(); + }, 1000); + } + }; + + sock.onerror = () => sock.close(); + + sock.onmessage = (ev) => { + let msg: any; + try { + msg = JSON.parse(ev.data); + } catch { + return; + } + const app = useApp.getState(); + switch (msg.type) { + case "meta": + app.setMeta(msg.signals as SignalDescriptor[], msg.pairs as Pair[]); + app.setMotors(msg.motors as MotorView[]); + break; + case "motors": + app.setMotors(msg.motors as MotorView[]); + if (msg.status) app.setStatus(msg.status as ServerStatus); + break; + case "samples": + for (const [sid, pts] of Object.entries(msg.data as Record)) { + appendBatch(sid, pts); + } + break; + case "raw": + pushRawFrames(msg.frames as RawFrame[]); + break; + } + }; + + // re-send subscription set whenever panels change what they want (debounced) + let subTimer: number | null = null; + onSubscriptionsChanged(() => { + if (subTimer != null) return; + subTimer = window.setTimeout(() => { + subTimer = null; + sendSubscribe(); + }, 80); + }); + onRawWantChanged(sendRaw); +} + +export async function fetchSnapshot(ids: string[], n = 600): Promise> { + if (!ids.length) return {}; + const res = await fetch(`/api/monitor/snapshot?signals=${ids.join(",")}&n=${n}`); + return res.json(); +} + +export async function fetchMotorTypes(): Promise { + try { + const res = await fetch("/api/monitor/motor-types"); + const d = await res.json(); + return d.types || []; + } catch { + return []; + } +} + +export async function setMotorType(motorId: number, motorType: string) { + await fetch("/api/monitor/motor-type", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ motorId, motorType }), + }); +} diff --git a/damiao_motor/gui/webapp/src/main.tsx b/damiao_motor/gui/webapp/src/main.tsx new file mode 100644 index 0000000..ef6c43e --- /dev/null +++ b/damiao_motor/gui/webapp/src/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import { initTheme } from "./lib/theme"; +import "./index.css"; + +initTheme(); // apply persisted (or default light) theme before first paint + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/damiao_motor/gui/webapp/src/panels/CardsPanel.tsx b/damiao_motor/gui/webapp/src/panels/CardsPanel.tsx new file mode 100644 index 0000000..d6a3853 --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/CardsPanel.tsx @@ -0,0 +1,61 @@ +import { useApp } from "../lib/store"; +import { fmt } from "../lib/format"; +import { setMotorType } from "../lib/ws"; + +function Metric({ label, cmd, act, unit, digits = 2 }: { label: string; cmd?: number; act?: number; unit: string; digits?: number }) { + return ( +
+
{label} {unit}
+
+ {fmt(act, digits)} + {cmd !== undefined && ⌖ {fmt(cmd, digits)}} +
+
+ ); +} + +export default function CardsPanel() { + const motors = useApp((s) => s.motors); + const motorTypes = useApp((s) => s.motorTypes); + + return ( +
+ {motors.length === 0 &&
Waiting for traffic…
} +
+ {motors.map((m) => ( +
+
+ Motor {m.motorId} + + {m.status || "—"} + +
+
+ {m.mode || "—"} + {motorTypes.length > 0 && ( + + )} +
+ + + +
+ MOS {fmt(m.fb.t_mos, 1)}° + Rotor {fmt(m.fb.t_rotor, 1)}° +
+
+ ))} +
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/ConnectionPanel.tsx b/damiao_motor/gui/webapp/src/panels/ConnectionPanel.tsx new file mode 100644 index 0000000..b1d6448 --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/ConnectionPanel.tsx @@ -0,0 +1,134 @@ +import { useEffect, useState } from "react"; +import { useApp } from "../lib/store"; +import { api } from "../lib/control"; + +export default function ConnectionPanel() { + const mode = useApp((s) => s.mode); + const status = useApp((s) => s.status); + const controlMotors = useApp((s) => s.controlMotors); + const currentMotorId = useApp((s) => s.currentMotorId); + const setControlMotors = useApp((s) => s.setControlMotors); + const setCurrentMotor = useApp((s) => s.setCurrentMotor); + const motorTypes = useApp((s) => s.motorTypes); + + const [bustype, setBustype] = useState("socketcan"); + const [channel, setChannel] = useState("can0"); + const [bitrate, setBitrate] = useState(1000000); + const [ifaces, setIfaces] = useState([]); + const [motorType, setMotorType] = useState("DM4310"); + const [busy, setBusy] = useState(false); + const [err, setErr] = useState(null); + + const connected = !!status?.connected; + + useEffect(() => { + api.platform().then((p) => { + if (p?.success) { + setBustype(p.default_bustype); + setChannel(p.default_channel); + } + }); + }, []); + useEffect(() => { + api.canInterfaces(bustype).then((d) => setIfaces(d?.interfaces || [])); + }, [bustype]); + + const connect = async () => { + setBusy(true); + setErr(null); + const body: any = { channel, bustype }; + if (bustype === "gs_usb") body.bitrate = bitrate; + if (mode === "control") body.motor_type = motorType; + const r = await api.connect(body); + setBusy(false); + if (r.success) { + const motors = r.motors || []; + setControlMotors(motors); + if (motors.length) setCurrentMotor(motors[0].id); + } else { + setErr(r.error || "Connect failed"); + } + }; + const disconnect = async () => { + await api.disconnect(); + setControlMotors([]); + setCurrentMotor(null); + }; + const rescan = async () => { + setBusy(true); + const r = await api.scan(motorType); + setBusy(false); + if (r.success) { + setControlMotors(r.motors || []); + if ((r.motors || []).length) setCurrentMotor(r.motors[0].id); + } else setErr(r.error || "Scan failed"); + }; + + return ( +
+
+ + +
+
+ + setChannel(e.target.value)} disabled={connected} /> + + {ifaces.map((i) => +
+ {bustype === "gs_usb" && ( +
+ + setBitrate(Number(e.target.value))} disabled={connected} /> +
+ )} + {mode === "control" && ( +
+ + +
+ )} +
+ {!connected ? ( + + ) : ( + + )} + {mode === "control" && connected && ( + + )} +
+ + {mode === "control" && connected && ( +
+ + +
+ )} + {mode === "monitor" && ( +
+ Monitor mode: listening only. Switch to Control to drive motors. +
+ )} + {err &&
{err}
} +
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/ControlPanel.tsx b/damiao_motor/gui/webapp/src/panels/ControlPanel.tsx new file mode 100644 index 0000000..ef09d6c --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/ControlPanel.tsx @@ -0,0 +1,164 @@ +import { useEffect, useRef, useState } from "react"; +import { useApp } from "../lib/store"; +import { api } from "../lib/control"; + +type Mode = "MIT" | "POS_VEL" | "VEL" | "FORCE_POS"; + +export default function ControlPanel() { + const appMode = useApp((s) => s.mode); + const status = useApp((s) => s.status); + const id = useApp((s) => s.currentMotorId); + const motorTypes = useApp((s) => s.motorTypes); + const connected = !!status?.connected; + const active = appMode === "control" && connected && id != null; + + const [mode, setMode] = useState("MIT"); + const [pos, setPos] = useState(0); + const [vel, setVel] = useState(0); + const [kp, setKp] = useState(0); + const [kd, setKd] = useState(0); + const [tau, setTau] = useState(0); + const [vlim, setVlim] = useState(0); + const [tlim, setTlim] = useState(0); + const [continuous, setContinuous] = useState(false); + const [freq, setFreq] = useState(50); + const [running, setRunning] = useState(false); + const [msg, setMsg] = useState(null); + const timer = useRef(null); + + const body = () => ({ + control_mode: mode, + target_position: pos, + target_velocity: vel, + stiffness: kp, + damping: kd, + feedforward_torque: tau, + velocity_limit: vlim, + torque_limit_ratio: tlim, + }); + + const stop = () => { + if (timer.current != null) { + clearInterval(timer.current); + timer.current = null; + } + setRunning(false); + }; + + useEffect(() => stop, []); // cleanup on unmount + useEffect(() => { + stop(); + }, [id, appMode]); + + const sendOnce = async () => { + if (id == null) return; + const r = await api.command(id, body()); + if (!r.success) { + setMsg(r.error || "command failed"); + stop(); + } + }; + const onSend = () => { + if (!continuous) { + sendOnce(); + } else if (running) { + stop(); + } else { + setRunning(true); + setMsg(null); + sendOnce(); + const ms = 1000 / Math.max(1, Math.min(1000, freq)); + timer.current = window.setInterval(sendOnce, ms); + } + }; + + const act = async (fn: () => Promise, label: string) => { + const r = await fn(); + setMsg(r?.success ? `${label} ✓` : `${label} failed: ${r?.error || ""}`); + }; + + if (!active) { + return ( +
+
+ {appMode !== "control" + ? "Monitor mode — switch to Control to drive motors." + : !connected + ? "Connect to a bus (Connection widget)." + : "Select a motor in the Connection widget."} +
+
+ ); + } + + const showPos = mode !== "VEL"; + const showMit = mode === "MIT"; + const showForce = mode === "FORCE_POS"; + const velLabel = mode === "POS_VEL" || showForce ? "Vel limit" : "Velocity"; + + return ( +
+
+ + +
+ {showPos && ( +
+ setPos(+e.target.value)} />
+ )} +
+ (mode === "FORCE_POS" ? setVlim(+e.target.value) : setVel(+e.target.value))} />
+ {showMit && <> +
+ setKp(+e.target.value)} />
+
+ setKd(+e.target.value)} />
+
+ setTau(+e.target.value)} />
+ } + {showForce && ( +
+ setTlim(+e.target.value)} />
+ )} + +
+ + +
+ +
+ + + {continuous && ( + setFreq(+e.target.value)} title="Hz" /> + )} +
+ +
+ + + +
+
+ + +
+ {msg &&
{msg}
} +
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/PlotPanel.tsx b/damiao_motor/gui/webapp/src/panels/PlotPanel.tsx new file mode 100644 index 0000000..79040a7 --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/PlotPanel.tsx @@ -0,0 +1,201 @@ +import { useEffect, useMemo, useRef } from "react"; +import { useDroppable } from "@dnd-kit/core"; +import uPlot from "uplot"; +import "uplot/dist/uPlot.min.css"; + +import { useApp } from "../lib/store"; +import { + appendBatch, + readSeries, + subscribeSignal, + unsubscribeSignal, +} from "../lib/dataStore"; +import { fetchSnapshot } from "../lib/ws"; +import { isCmd, shortSignal, seriesStyle } from "../lib/format"; + +const MAX_X = 2000; // cap aligned x points per frame + +/** Forward-fill align several signals onto the union of their timestamps. */ +function buildAligned(ids: string[], sinceT: number): uPlot.AlignedData { + const series = ids.map((id) => readSeries(id, sinceT)); + const tset = new Set(); + for (const s of series) for (let i = 0; i < s.t.length; i++) tset.add(s.t[i]); + let xs = Array.from(tset).sort((a, b) => a - b); + if (xs.length > MAX_X) { + const stride = Math.ceil(xs.length / MAX_X); + xs = xs.filter((_, i) => i % stride === 0); + } + const cols: (number | null)[][] = [xs]; + for (const s of series) { + const col: (number | null)[] = new Array(xs.length).fill(null); + let j = 0; + let last: number | null = null; + for (let i = 0; i < xs.length; i++) { + while (j < s.t.length && s.t[j] <= xs[i]) { + last = s.v[j]; + j++; + } + col[i] = last; + } + cols.push(col); + } + return cols as unknown as uPlot.AlignedData; +} + +export default function PlotPanel({ panelId }: { panelId: string }) { + const ensurePlot = useApp((s) => s.ensurePlot); + const removeSignalFromPlot = useApp((s) => s.removeSignalFromPlot); + const setPlotConfig = useApp((s) => s.setPlotConfig); + const cfg = useApp((s) => s.plotConfigs[panelId]); + const signalsMeta = useApp((s) => s.signals); + + useEffect(() => { + ensurePlot(panelId); + }, [panelId, ensurePlot]); + + const signals = cfg?.signals ?? []; + const duration = cfg?.duration ?? 10; + const sigKey = signals.join("|"); + + const { setNodeRef, isOver } = useDroppable({ id: `plot:${panelId}`, data: { panelId } }); + const hostRef = useRef(null); + const plotRef = useRef(null); + const maxTRef = useRef(0); + + // (re)create the uPlot instance whenever the set of signals changes + useEffect(() => { + if (!hostRef.current) return; + const el = hostRef.current; + + const descById = new Map(signalsMeta.map((s) => [s.id, s])); + const seriesCfg: uPlot.Series[] = [ + { label: "t" }, + ...signals.map((id) => { + const d = descById.get(id); + const style = d ? seriesStyle(d) : { stroke: "#8b949e", width: 1.5 }; + return { + label: shortSignal(id), + stroke: style.stroke, + width: style.width, + points: { show: false }, + } as uPlot.Series; + }), + ]; + + const opts: uPlot.Options = { + width: el.clientWidth || 400, + height: el.clientHeight || 220, + legend: { show: false }, + series: seriesCfg, + cursor: { y: false, points: { show: true } }, + scales: { x: { time: false } }, + axes: [ + { + stroke: "#8b949e", + grid: { stroke: "rgba(139,148,158,0.12)" }, + ticks: { stroke: "rgba(139,148,158,0.2)" }, + values: (_u, vals) => vals.map((v) => (v - maxTRef.current).toFixed(1) + "s"), + }, + { + stroke: "#8b949e", + grid: { stroke: "rgba(139,148,158,0.12)" }, + ticks: { stroke: "rgba(139,148,158,0.2)" }, + }, + ], + }; + + const plot = new uPlot(opts, [[], ...signals.map(() => [])] as unknown as uPlot.AlignedData, el); + plotRef.current = plot; + + const ro = new ResizeObserver(() => { + plot.setSize({ width: el.clientWidth, height: el.clientHeight }); + }); + ro.observe(el); + + return () => { + ro.disconnect(); + plot.destroy(); + plotRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sigKey, signalsMeta.length]); + + // subscribe to streams + backfill history when the signal set changes + useEffect(() => { + if (!signals.length) return; + signals.forEach(subscribeSignal); + let cancelled = false; + fetchSnapshot(signals, 1200).then((snap) => { + if (cancelled) return; + for (const [id, pts] of Object.entries(snap)) appendBatch(id, pts); + }); + return () => { + cancelled = true; + signals.forEach(unsubscribeSignal); + }; + }, [sigKey]); + + // rAF render loop: pull from the ring buffers and push into uPlot + useEffect(() => { + let raf = 0; + const tick = () => { + const plot = plotRef.current; + if (plot && signals.length) { + // find latest t across signals to anchor the window + let maxT = 0; + for (const id of signals) { + const s = readSeries(id); + if (s.t.length) maxT = Math.max(maxT, s.t[s.t.length - 1]); + } + maxTRef.current = maxT; + const data = buildAligned(signals, maxT - duration); + plot.setData(data, false); + plot.setScale("x", { min: maxT - duration, max: maxT }); + } + raf = requestAnimationFrame(tick); + }; + raf = requestAnimationFrame(tick); + return () => cancelAnimationFrame(raf); + }, [sigKey, duration]); + + const descById = useMemo(() => new Map(signalsMeta.map((s) => [s.id, s])), [signalsMeta]); + + return ( +
+
+ window + +
+ {signals.map((id) => { + const d = descById.get(id); + const style = d ? seriesStyle(d) : { stroke: "#555" }; + return ( + + + {shortSignal(id)} + + + ); + })} +
+
+
+ {signals.length === 0 && ( +
Drag signals here to plot — drop cmd onto fb to overlay
+ )} +
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/RawLogPanel.tsx b/damiao_motor/gui/webapp/src/panels/RawLogPanel.tsx new file mode 100644 index 0000000..e5a1afb --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/RawLogPanel.tsx @@ -0,0 +1,111 @@ +import { useEffect, useRef, useState } from "react"; +import { useVirtualizer } from "@tanstack/react-virtual"; + +import { getRawFrames, onRawFrames, wantRaw } from "../lib/dataStore"; +import type { RawFrame } from "../lib/types"; + +const SHORT: Record = { + pos: "p", + vel: "v", + torque: "τ", + kp: "kp", + kd: "kd", + vel_limit: "vlim", + torque_limit: "τlim", + t_mos: "Tm", + t_rotor: "Tr", +}; +const SUMMARY_ORDER = ["pos", "vel", "torque", "kp", "kd", "t_mos", "t_rotor"]; + +function fieldsSummary(f: RawFrame): string { + const parts: string[] = []; + for (const k of SUMMARY_ORDER) { + if (k in f.fields) parts.push(`${SHORT[k] || k} ${f.fields[k].toFixed(2)}`); + } + return parts.join(" ") || f.note || ""; +} + +function fmtTime(t: number): string { + const d = new Date(t * 1000); + const hh = String(d.getHours()).padStart(2, "0"); + const mm = String(d.getMinutes()).padStart(2, "0"); + const ss = String(d.getSeconds()).padStart(2, "0"); + const ms = String(Math.floor((t % 1) * 1000)).padStart(3, "0"); + return `${hh}:${mm}:${ss}.${ms}`; +} + +export default function RawLogPanel() { + const [, force] = useState(0); + const [paused, setPaused] = useState(false); + const parentRef = useRef(null); + const framesRef = useRef([]); + + useEffect(() => { + wantRaw(true); + const off = onRawFrames(() => { + if (!paused) { + framesRef.current = getRawFrames(); + force((x) => x + 1); + } + }); + return () => { + wantRaw(false); + off(); + }; + }, [paused]); + + const frames = framesRef.current; + const rowVirt = useVirtualizer({ + count: frames.length, + getScrollElement: () => parentRef.current, + estimateSize: () => 22, + overscan: 12, + }); + + // auto-scroll to bottom unless paused + useEffect(() => { + if (!paused && frames.length) rowVirt.scrollToIndex(frames.length - 1); + }, [frames.length, paused, rowVirt]); + + return ( +
+
+ + {frames.length} frames +
+
+ {/* sticky header lives in the same scroll container as the rows, sharing the + exact grid + padding so columns line up perfectly */} +
+ time + arb + motor + kind + decoded + raw +
+
+ {rowVirt.getVirtualItems().map((vi) => { + const f = frames[vi.index]; + return ( +
+ {fmtTime(f.t)} + 0x{f.arb.toString(16).toUpperCase()} + m{f.motorId} + {f.mode || f.kind} + {fieldsSummary(f)} + {f.raw} +
+ ); + })} +
+
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/RegisterPanel.tsx b/damiao_motor/gui/webapp/src/panels/RegisterPanel.tsx new file mode 100644 index 0000000..04c94c1 --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/RegisterPanel.tsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from "react"; +import { useApp } from "../lib/store"; +import { api } from "../lib/control"; + +const CTRL_MODES: Record = { 1: "MIT", 2: "POS_VEL", 3: "VEL", 4: "FORCE_POS" }; +const BAUDS: Record = { 0: "125K", 1: "200K", 2: "250K", 3: "500K", 4: "1M" }; + +export default function RegisterPanel() { + const appMode = useApp((s) => s.mode); + const status = useApp((s) => s.status); + const id = useApp((s) => s.currentMotorId); + const regTable = useApp((s) => s.registerTable); + const setCurrentMotor = useApp((s) => s.setCurrentMotor); + const connected = !!status?.connected; + const active = appMode === "control" && connected && id != null; + + const [values, setValues] = useState>({}); + const [edits, setEdits] = useState>({}); + const [msg, setMsg] = useState(null); + const [loading, setLoading] = useState(false); + + const load = async () => { + if (id == null) return; + setLoading(true); + const r = await api.getRegisters(id); + setLoading(false); + if (r.success) { + setValues(r.registers || {}); + setEdits({}); + } else setMsg(r.error || "read failed"); + }; + useEffect(() => { + if (active) load(); + else setValues({}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id, active]); + + if (!active) { + return
+ {appMode !== "control" ? "Monitor mode — registers unavailable." : "Connect + select a motor."} +
; + } + + const parseFor = (rid: number, raw: string, dtype: string): number => { + if (rid === 7 || rid === 8) { + const s = raw.trim(); + return s.toLowerCase().startsWith("0x") ? parseInt(s, 16) : (parseInt(s, 16) || parseInt(s, 10)); + } + return dtype === "float" ? parseFloat(raw) : parseInt(raw, 10); + }; + + const write = async (rid: number) => { + const info = regTable[rid]; + const dtype = info?.data_type || "float"; + let val: number; + if (rid === 10 || rid === 35) val = parseInt(edits[rid], 10); + else if (rid === 9) val = parseFloat(edits[rid]); + else val = parseFor(rid, edits[rid] ?? "", dtype); + if (Number.isNaN(val)) { setMsg("invalid value"); return; } + const r = await api.setRegister(id!, rid, val); + if (r.success) { + if (r.updated_ids?.motor_id != null) setCurrentMotor(r.updated_ids.motor_id); + setMsg(`reg ${rid} written ✓`); + setTimeout(load, 100); + } else setMsg(r.error || "write failed"); + }; + + const display = (rid: number, v: any): string => { + if (rid === 9) return `${v} ms`; + if (rid === 7 || rid === 8) return `0x${Number(v).toString(16).toUpperCase()} (${v})`; + if (rid === 10) return CTRL_MODES[Number(v)] || String(v); + if (rid === 35) return BAUDS[Number(v)] || String(v); + const info = regTable[rid]; + return info?.data_type === "float" ? Number(v).toFixed(4) : String(v); + }; + + const rids = Object.keys(values).map(Number).sort((a, b) => a - b); + + return ( +
+
+ + {msg && {msg}} +
+
+ + + + {rids.map((rid) => { + const info = regTable[rid]; + const ro = info?.access === "RO"; + return ( + + + + + + ); + })} + +
RegisterValue
{info?.description || `reg ${rid}`} + {ro ? ( + display(rid, values[rid]) + ) : rid === 10 || rid === 35 ? ( + + ) : ( + setEdits({ ...edits, [rid]: e.target.value })} + /> + )} + {!ro && }
+
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/TablePanel.tsx b/damiao_motor/gui/webapp/src/panels/TablePanel.tsx new file mode 100644 index 0000000..8f6bdcc --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/TablePanel.tsx @@ -0,0 +1,67 @@ +import { useApp } from "../lib/store"; +import { fmt } from "../lib/format"; + +const CMD_COLS: [string, string][] = [ + ["pos", "cmd p"], + ["vel", "cmd v"], + ["kp", "kp"], + ["kd", "kd"], + ["torque", "cmd τ"], +]; +const FB_COLS: [string, string][] = [ + ["pos", "act p"], + ["vel", "act v"], + ["torque", "act τ"], + ["t_mos", "Tmos"], + ["t_rotor", "Trot"], +]; + +export default function TablePanel() { + const motors = useApp((s) => s.motors); + + return ( +
+ + + + + + + {CMD_COLS.map(([k, l]) => ( + + ))} + {FB_COLS.map(([k, l]) => ( + + ))} + + + + {motors.length === 0 && ( + + + + )} + {motors.map((m) => ( + + + + + {CMD_COLS.map(([k]) => ( + + ))} + {FB_COLS.map(([k]) => ( + + ))} + + ))} + +
MotorModeStatus{l}{l}
+ Waiting for traffic… +
m{m.motorId}{m.mode || "—"} + + {m.status || "—"} + + {fmt(m.cmd[k], k === "kp" ? 0 : 3)}{fmt(m.fb[k], k.startsWith("t_") ? 1 : 3)}
+
+ ); +} diff --git a/damiao_motor/gui/webapp/src/panels/registry.tsx b/damiao_motor/gui/webapp/src/panels/registry.tsx new file mode 100644 index 0000000..268d273 --- /dev/null +++ b/damiao_motor/gui/webapp/src/panels/registry.tsx @@ -0,0 +1,80 @@ +/** + * Panel registry — the single source of truth for panel types. + * + * To add a new view, add ONE entry here: its kind, title, icon, and how to render it. + * The dock component map, the toolbar "add panel" buttons, the default layout, and panel + * titling are all derived from this list, so nothing else needs editing. + */ + +import PlotPanel from "./PlotPanel"; +import TablePanel from "./TablePanel"; +import CardsPanel from "./CardsPanel"; +import RawLogPanel from "./RawLogPanel"; +import ConnectionPanel from "./ConnectionPanel"; +import ControlPanel from "./ControlPanel"; +import RegisterPanel from "./RegisterPanel"; + +export interface PanelDef { + kind: string; + title: string; + icon: string; + description: string; + /** Render the panel body given its dockview panel id. */ + render: (panelId: string) => JSX.Element; +} + +export const PANELS: PanelDef[] = [ + { + kind: "connection", + title: "Connection", + icon: "⇄", + description: "Connect to a CAN bus, scan, and select a motor.", + render: () => , + }, + { + kind: "control", + title: "Motor Control", + icon: "◉", + description: "Drive the selected motor (MIT/POS_VEL/VEL/FORCE_POS), enable, zero, store.", + render: () => , + }, + { + kind: "registers", + title: "Registers", + icon: "≡", + description: "Read/write the selected motor's registers.", + render: () => , + }, + { + kind: "plot", + title: "Plot", + icon: "〜", + description: "Time-series chart; drag signals onto it (cmd over fb to overlay).", + render: (id) => , + }, + { + kind: "table", + title: "Motor Table", + icon: "▦", + description: "One row per motor: commanded vs actual.", + render: () => , + }, + { + kind: "cards", + title: "Motor Cards", + icon: "▢", + description: "Per-motor cards/gauges with big readouts.", + render: () => , + }, + { + kind: "rawlog", + title: "Raw CAN Log", + icon: "≣", + description: "Scrolling decoded frame log.", + render: () => , + }, +]; + +export const PANEL_BY_KIND: Record = Object.fromEntries( + PANELS.map((p) => [p.kind, p]) +); diff --git a/damiao_motor/gui/webapp/tsconfig.json b/damiao_motor/gui/webapp/tsconfig.json new file mode 100644 index 0000000..c1183c9 --- /dev/null +++ b/damiao_motor/gui/webapp/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2021", + "useDefineForClassFields": true, + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/damiao_motor/gui/webapp/tsconfig.tsbuildinfo b/damiao_motor/gui/webapp/tsconfig.tsbuildinfo new file mode 100644 index 0000000..4599721 --- /dev/null +++ b/damiao_motor/gui/webapp/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/main.tsx","./src/components/canvas.tsx","./src/components/signalchip.tsx","./src/components/signalsidebar.tsx","./src/components/toolbar.tsx","./src/lib/control.ts","./src/lib/datastore.ts","./src/lib/format.ts","./src/lib/store.ts","./src/lib/theme.ts","./src/lib/types.ts","./src/lib/widgets.ts","./src/lib/ws.ts","./src/panels/cardspanel.tsx","./src/panels/connectionpanel.tsx","./src/panels/controlpanel.tsx","./src/panels/plotpanel.tsx","./src/panels/rawlogpanel.tsx","./src/panels/registerpanel.tsx","./src/panels/tablepanel.tsx","./src/panels/registry.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/damiao_motor/gui/webapp/vite.config.ts b/damiao_motor/gui/webapp/vite.config.ts new file mode 100644 index 0000000..6c2d08f --- /dev/null +++ b/damiao_motor/gui/webapp/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// Built assets use relative paths so Flask can serve them from any mount point. +// In dev, proxy the REST API and the WebSocket stream to the Python monitor server. +const API_TARGET = process.env.MONITOR_API || "http://127.0.0.1:5001"; + +export default defineConfig({ + plugins: [react()], + base: "./", + build: { + outDir: "dist", + emptyOutDir: true, + }, + server: { + port: 5173, + proxy: { + "/api/monitor/stream": { target: API_TARGET.replace("http", "ws"), ws: true }, + "/api": { target: API_TARGET, changeOrigin: true }, + }, + }, +}); diff --git a/damiao_motor/monitor/README.md b/damiao_motor/monitor/README.md new file mode 100644 index 0000000..05852f5 --- /dev/null +++ b/damiao_motor/monitor/README.md @@ -0,0 +1,91 @@ +# DaMiao Passive Monitor + +A **listen-only** realtime dashboard. While another controller drives the motors on a CAN +bus, the monitor decodes **both** the commands that controller is sending **and** the +motors' feedback, and plots/tables them live. It never transmits on the bus. + +## Run + +```bash +# Listen on a socketcan bus that another controller is already driving +damiao monitor --channel can_arm_l + +# Try it with synthetic traffic — no CAN hardware needed +damiao monitor --demo + +# then open http://127.0.0.1:5001 +``` + +Key options: `--channel`, `--bustype` (default `socketcan`), `--bitrate` (e.g. for gs_usb), +`--feedback-offset` (feedback arb id = motor id + offset; default **16** = the I2RT `p16` +scheme), `--motor-type` (default scaling, default `DM4310`), `--demo`, `--port`. + +## Using the dashboard + +- **Free-form widget canvas**: each panel is a widget you can **drag by its header** and + **resize from any edge/corner**, placed anywhere on the grid. Add widgets from the + toolbar (**Plot / Motor Table / Motor Cards / Raw CAN Log**), remove with the **×** on + the widget header. Layout + plot contents persist across reloads (**Reset** restores the + default layout). +- **Drag** a signal from the left sidebar onto a **Plot** widget to chart it. Drop a `cmd.*` + signal onto the plot already showing its `fb.*` to **overlay** them (dashed = command, + solid = feedback). +- Per-motor **motor-type** can be overridden in the cards (rescales decode). + +## How it stays passive + +- The `monitor` package never imports or calls anything that transmits; it only ever calls + `bus.recv`. (`tests/test_monitor.py` asserts this.) +- On socketcan it also sets `CAN_RAW_LISTEN_ONLY` on the socket (best-effort defense in + depth). On a separate socketcan socket it sees all bus traffic without stealing frames + from the running controller. + +## Dev workflow (changing the UI) + +The dashboard is a Vite/React/TS app in `gui/webapp`. The built bundle in +`gui/webapp/dist/` is committed so end users need no Node toolchain. To develop: + +```bash +cd damiao_motor/gui/webapp +npm install +npm run dev # Vite dev server on :5173, proxies /api + WS to the monitor +# in another shell: +damiao monitor --demo # or --channel +# open the Vite URL (http://localhost:5173) + +npm run build # rebuild dist/ before committing UI changes +``` + +## Test checklist + +### A. Offline (no hardware — runs anywhere) +1. `pytest tests/test_monitor.py` — command/feedback decode round-trips + the + never-transmit assertion (10 tests). +2. `python -m build` then confirm the wheel ships the UI + package: + `unzip -l dist/*.whl | grep -E "webapp/dist/index.html|monitor/server.py"`. +3. `damiao monitor --demo` → open the URL: signals appear, drag `cmd.pos`+`fb.pos` onto a + plot and see the overlay track; cards + table update; add a Raw CAN Log panel and watch + frames stream; reload and confirm layout/plots persist. + +### B. On xdof_linearbot (real CAN) +4. **Idle bus, no crash:** with no controller running, + `damiao monitor --channel can_arm_l --host 0.0.0.0` → dashboard loads, top bar shows + `listen-only`, signal list empty (idle bus invents nothing). +5. **Passivity (do this any time it runs):** in another shell + `watch -n1 'ip -s link show can_arm_l | sed -n 5,6p'` → TX packets stay **0** while the + monitor runs. (Verified during development: opened the bus, `listen_only_applied=True`, + TX stayed 0.) +6. **Live decode:** start the real controller (FlowBase / LinearRail desktop launcher) or + a benign sender → on the monitor: + - signals auto-appear per motor; `cmd`↔`fb` pairs link; + - drag `cmd.pos` onto the `fb.pos` plot → overlay tracks (dashed cmd vs solid actual); + - Motor Table shows live commanded + actual columns; Cards update; Raw CAN Log streams + decoded MIT/POS_VEL/feedback frames. +7. **Right scaling:** if a motor reads with the wrong range (e.g. a base `DM4310V` uses + ±π rad), set its type in the card dropdown and confirm values correct. +8. **Feedback scheme:** if no feedback signals appear but commands do, the bus may not use + the `p16` (+16) feedback-id scheme — rerun with the correct `--feedback-offset`. +9. **Smoothness:** with all arm motors streaming at control rate, plots stay smooth and the + time window scrolls without growing lag. +10. **Coexistence:** the active controller is unaffected (the monitor only reads), and + `damiao gui` still works as before on its own run. diff --git a/damiao_motor/monitor/__init__.py b/damiao_motor/monitor/__init__.py new file mode 100644 index 0000000..5dab150 --- /dev/null +++ b/damiao_motor/monitor/__init__.py @@ -0,0 +1,18 @@ +"""Passive (listen-only) monitoring for DaMiao motors. + +This subpackage decodes both command and feedback frames observed on a CAN bus while +another controller drives the motors, and never transmits anything itself. +""" + +from damiao_motor.monitor.decode import DecodedFrame, decode_frame, resolve_limits +from damiao_motor.monitor.listener import PassiveCanListener +from damiao_motor.monitor.store import SignalDescriptor, SignalStore + +__all__ = [ + "DecodedFrame", + "decode_frame", + "resolve_limits", + "PassiveCanListener", + "SignalStore", + "SignalDescriptor", +] diff --git a/damiao_motor/monitor/control.py b/damiao_motor/monitor/control.py new file mode 100644 index 0000000..5bd8e92 --- /dev/null +++ b/damiao_motor/monitor/control.py @@ -0,0 +1,297 @@ +"""Active control service for the unified Control/Monitor UI. + +Wraps a :class:`~damiao_motor.core.controller.DaMiaoController` and, on every command and +state read, pushes the commanded values and the motor feedback into the shared +:class:`~damiao_motor.monitor.store.SignalStore` so the same realtime plots/table/cards +visualize what *we* are driving. This is offset-agnostic (the controller routes feedback +by the logical id in ``data[0]``), unlike the passive listener used in monitor mode. + +This module CAN transmit — it is only ever instantiated in Control mode. +""" + +from __future__ import annotations + +import time +from typing import Any, Dict, List, Optional + +from damiao_motor.core.controller import DaMiaoController +from damiao_motor.core.motor import MOTOR_TYPE_PRESETS, REGISTER_TABLE +from damiao_motor.monitor.decode import KIND_COMMAND, KIND_FEEDBACK, DecodedFrame +from damiao_motor.monitor.store import SignalStore + +TIMEOUT_REGISTER_ID = 9 +TIMEOUT_UNITS_PER_MS = 20.0 + + +def _core_motor_type(name: str) -> str: + """Map a (possibly extended, e.g. 'DM4310') motor-type name to a core preset name + the DaMiaoController understands ('4310'). Falls back to '4310'.""" + if name in MOTOR_TYPE_PRESETS: + return name + if name.startswith("DM") and name[2:] in MOTOR_TYPE_PRESETS: + return name[2:] + return "4310" + + +# command-mode -> the cmd field names the store/plots expect (match decode.py) +_CONTROL_MODES = {"MIT", "POS_VEL", "VEL", "FORCE_POS"} + + +class ControlService: + def __init__(self, store: SignalStore, raw_push=None) -> None: + self.store = store + self._raw_push = raw_push + self.controller: Optional[DaMiaoController] = None + self.channel: Optional[str] = None + self.bustype: str = "socketcan" + self.bitrate: Optional[int] = None + self.connected = False + self.error: Optional[str] = None + + # ------------------------------------------------------------- lifecycle + def connect( + self, channel: str, bustype: str = "socketcan", bitrate: Optional[int] = None + ) -> None: + self.disconnect() + self.controller = DaMiaoController( + channel=channel, bustype=bustype, bitrate=bitrate + ) + self.channel, self.bustype, self.bitrate = channel, bustype, bitrate + self.connected = True + self.error = None + + def disconnect(self) -> None: + if self.controller is not None: + try: + self.controller.shutdown() + except Exception: + pass + self.controller = None + self.connected = False + + def _require(self): + if self.controller is None: + raise RuntimeError("Not connected") + return self.controller + + # ---------------------------------------------------------------- scan + def scan(self, motor_type: str, settle: float = 0.5) -> List[Dict[str, Any]]: + c = self._require() + core_type = _core_motor_type(motor_type) + c.motors = {} + c._motors_by_feedback = {} + c.flush_bus() + for motor_id in range(0x01, 0x11): + try: + m = c.add_motor( + motor_id=motor_id, feedback_id=0x00, motor_type=core_type + ) + m.send_cmd_mit(0.0, 0.0, 0.0, 0.0, 0.0) + except ValueError: + pass + except Exception: + pass + found: List[Dict[str, Any]] = [] + responded = set() + t0 = time.perf_counter() + while time.perf_counter() - t0 < settle: + c.poll_feedback() + for mid, m in c.motors.items(): + if ( + m.state + and m.state.get("can_id") is not None + and mid not in responded + ): + responded.add(mid) + found.append( + { + "id": mid, + "arb_id": m.state.get("arbitration_id") or 0, + "motor_type": m.motor_type, + } + ) + self._push_feedback(mid, m.get_states()) + time.sleep(0.01) + # keep only responders + c.motors = {f["id"]: c.motors[f["id"]] for f in found} + return found + + def motors(self) -> List[Dict[str, Any]]: + c = self._require() + return [ + {"id": mid, "motor_type": m.motor_type} + for mid, m in sorted(c.motors.items()) + ] + + # ---------------------------------------------------------- store feed + def _push_command(self, motor_id: int, mode: str, fields: Dict[str, float]) -> None: + fr = DecodedFrame( + t=time.time(), + arbitration_id=motor_id, + kind=KIND_COMMAND, + motor_id=motor_id, + raw=b"", + mode=mode, + fields=fields, + ) + self.store.ingest(fr) + if self._raw_push: + self._raw_push(fr) + + def _push_feedback(self, motor_id: int, state: Dict[str, Any]) -> None: + if not state: + return + fields = { + "pos": float(state.get("pos", 0.0)), + "vel": float(state.get("vel", 0.0)), + "torque": float(state.get("torq", 0.0)), + "t_mos": float(state.get("t_mos", 0.0)), + "t_rotor": float(state.get("t_rotor", 0.0)), + "status_code": float(state.get("status_code", 0)), + } + fr = DecodedFrame( + t=time.time(), + arbitration_id=motor_id + 16, + kind=KIND_FEEDBACK, + motor_id=motor_id, + raw=b"", + fields=fields, + note=str(state.get("status", "")), + ) + self.store.ingest(fr) + if self._raw_push: + self._raw_push(fr) + + # ------------------------------------------------------------- actions + def enable(self, motor_id: int) -> None: + self._require().motors[motor_id].enable() + + def disable(self, motor_id: int) -> None: + m = self._require().motors[motor_id] + m.set_zero_command() + m.disable() + + def set_zero(self, motor_id: int) -> None: + self._require().motors[motor_id].set_zero_position() + + def clear_error(self, motor_id: int) -> None: + self._require().motors[motor_id].clear_error() + + def store_parameters(self, motor_id: int) -> None: + self._require().motors[motor_id].store_parameters() + + def set_motor_type(self, motor_id: int, motor_type: str) -> None: + self._require().motors[motor_id].set_motor_type(_core_motor_type(motor_type)) + + def command(self, motor_id: int, data: Dict[str, Any]) -> Dict[str, Any]: + c = self._require() + m = c.motors[motor_id] + mode = data.get("control_mode", "MIT") + pos = float(data.get("target_position", 0.0)) + vel = float(data.get("target_velocity", 0.0)) + kp = float(data.get("stiffness", 0.0)) + kd = float(data.get("damping", 0.0)) + tau = float(data.get("feedforward_torque", 0.0)) + vlim = float(data.get("velocity_limit", 0.0)) + tlim = float(data.get("torque_limit_ratio", 0.0)) + + if mode == "MIT": + m.send_cmd_mit(pos, vel, kp, kd, tau) + self._push_command( + motor_id, + "MIT", + {"pos": pos, "vel": vel, "kp": kp, "kd": kd, "torque": tau}, + ) + elif mode == "POS_VEL": + m.send_cmd_pos_vel(pos, vel) + self._push_command(motor_id, "POS_VEL", {"pos": pos, "vel_limit": vel}) + elif mode == "VEL": + m.send_cmd_vel(vel) + self._push_command(motor_id, "VEL", {"vel": vel}) + elif mode == "FORCE_POS": + m.send_cmd_force_pos(pos, vlim, tlim) + self._push_command( + motor_id, + "FORCE_POS", + {"pos": pos, "vel_limit": vlim, "torque_limit_ratio": tlim}, + ) + else: + raise ValueError(f"Unknown control_mode: {mode}") + + c.poll_feedback() + state = m.get_states() + self._push_feedback(motor_id, state) + return state + + def get_state(self, motor_id: int) -> Dict[str, Any]: + c = self._require() + c.poll_feedback() + state = c.motors[motor_id].get_states() + self._push_feedback(motor_id, state) + return state + + # ------------------------------------------------------------ registers + def get_registers(self, motor_id: int) -> Dict[str, Any]: + m = self._require().motors[motor_id] + regs = m.read_all_registers(timeout=0.05) + clean: Dict[int, Any] = {} + for rid, value in regs.items(): + if isinstance(value, str) and value.startswith("ERROR"): + continue + if rid == TIMEOUT_REGISTER_ID: + clean[rid] = float(value) / TIMEOUT_UNITS_PER_MS + else: + clean[rid] = value + return {"registers": clean, "motor_type": m.motor_type} + + def set_register(self, motor_id: int, rid: int, value: Any) -> Dict[str, Any]: + c = self._require() + m = c.motors[motor_id] + updated_ids: Dict[str, int] = {} + + if rid == TIMEOUT_REGISTER_ID: + units = int(round(float(value) * TIMEOUT_UNITS_PER_MS)) + if units < 0: + raise ValueError("Timeout must be >= 0 ms") + m.write_register(rid, units) + else: + m.write_register(rid, value) + + if rid == 7: # MST_ID / feedback id + new_fb = int(value) + old_fb = m.feedback_id + m.feedback_id = new_fb + if old_fb in c._motors_by_feedback: + del c._motors_by_feedback[old_fb] + c._motors_by_feedback[new_fb] = m + updated_ids["feedback_id"] = new_fb + elif rid == 8: # ESC_ID / receive (motor) id + new_id = int(value) + old_id = m.motor_id + m.motor_id = new_id + if old_id in c.motors: + del c.motors[old_id] + c.motors[new_id] = m + updated_ids["motor_id"] = new_id + + if rid in (7, 8): + try: + m.store_parameters() + except Exception: + pass + + return {"updated_ids": updated_ids} + + @staticmethod + def register_table() -> List[Dict[str, Any]]: + return [ + { + "rid": r.rid, + "variable": r.variable, + "description": r.description, + "access": r.access, + "range_str": r.range_str, + "data_type": r.data_type, + } + for r in REGISTER_TABLE.values() + ] diff --git a/damiao_motor/monitor/decode.py b/damiao_motor/monitor/decode.py new file mode 100644 index 0000000..d1f4333 --- /dev/null +++ b/damiao_motor/monitor/decode.py @@ -0,0 +1,322 @@ +"""Passive decode of DaMiao CAN frames — both commands and feedback. + +This module reverses the frame encoders in :mod:`damiao_motor.core.motor` so that a +listen-only observer can reconstruct what *another* controller is commanding on the +bus, alongside the motors' feedback. It never transmits anything. + +Frame layouts (little detail recap, see core/motor.py for the encoders): + +* MIT command arb = motor_id 16b pos | 12b vel | 12b kp | 12b kd | 12b torque +* POS_VEL command arb = 0x100 + motor_id (target_pos, vel_limit) +* VEL command arb = 0x200 + motor_id (target_vel) + 4 pad +* FORCE_POS command arb = 0x300 + motor_id (pos, vel*100, ratio*10000) +* special command arb = motor_id FF*7 + {FC enable, FD disable, FE zero, FB clear} +* feedback arb = motor_id + offset D0=(status<<4)|can_id, packed pos/vel/torque, D6/D7 temps +* register reply D1<=0x0F, D2==0x33, D3=rid + +Command and feedback share the low arbitration-id space, so they are disambiguated by +the feedback-id ``offset`` (e.g. the I2RT ``p16`` scheme uses ``motor_id + 16``) together +with the structural check ``data[0] & 0x0F == arb - offset``. +""" + +from __future__ import annotations + +import struct +from dataclasses import dataclass, field +from typing import Dict, Optional + +from damiao_motor.core.motor import ( + KD_MAX, + KD_MIN, + KP_MAX, + KP_MIN, + MOTOR_TYPE_PRESETS, + _STATE_NAME_MAP, + _decode_status_name, + is_register_reply, + uint_to_float, +) + +# Arbitration-id bases for the non-MIT control modes. +POS_VEL_BASE = 0x100 +VEL_BASE = 0x200 +FORCE_POS_BASE = 0x300 +REGISTER_ARB = 0x7FF + +# Default feedback-id offset: the I2RT "p16" receive mode (feedback arb = motor_id + 16), +# which is also the common DaMiao default. Configurable per listener. +DEFAULT_FEEDBACK_OFFSET = 16 + +# Valid DM status nibbles (used as a structural gate when classifying feedback frames). +KNOWN_STATUS = frozenset(_STATE_NAME_MAP.keys()) + +# Special single-byte command suffixes (data = FF*7 + suffix). +_SPECIAL_SUFFIX = { + 0xFC: "enable", + 0xFD: "disable", + 0xFE: "set_zero", + 0xFB: "clear_error", +} + +# Extended motor-type presets. We reuse the core presets (keyed "4310", "4340", ...) and +# add the "DM"-prefixed names plus the reduced-range variants used by third-party stacks +# (e.g. DM4310V / DM_FLOW_WHEEL use +/-pi rad position), so we can decode their traffic +# correctly without touching the core table. +_EXTRA_LIMIT_PARAM = { + # name: (pmax, vmax, tmax) -> symmetric +/- ranges + "DM4310": (12.5, 30, 10), + "DM4310V": (3.1415926, 30, 10), + "DM_FLOW_WHEEL": (3.1415926, 30, 10), + "DMH6215": (3.1415926, 30, 10), + "DMH6215MIT": (12.5, 45, 10), + "DM4340": (12.5, 10, 28), + "DM6248": (12.5, 20, 120), + "DM8009": (12.5, 45, 54), + "DM3507": (12.5, 50, 5), +} + + +def _preset_from_param(pmax: float, vmax: float, tmax: float) -> Dict[str, float]: + return { + "p_min": -pmax, + "p_max": pmax, + "v_min": -vmax, + "v_max": vmax, + "t_min": -tmax, + "t_max": tmax, + } + + +# Merged lookup: core presets first, then the extended/DM-prefixed names. +MONITOR_MOTOR_PRESETS: Dict[str, Dict[str, float]] = { + **MOTOR_TYPE_PRESETS, + **{name: _preset_from_param(*p) for name, p in _EXTRA_LIMIT_PARAM.items()}, +} + +DEFAULT_MOTOR_TYPE = "DM4310" + + +def resolve_limits(motor_type: str) -> Dict[str, float]: + """Resolve P/V/T limits for a motor type, tolerant of the ``DM`` name prefix. + + Falls back to :data:`DEFAULT_MOTOR_TYPE` for unknown names so a single unexpected + motor never breaks passive decoding of the rest of the bus. + """ + if motor_type in MONITOR_MOTOR_PRESETS: + return MONITOR_MOTOR_PRESETS[motor_type] + # tolerate "DM4310" <-> "4310" + alt = motor_type[2:] if motor_type.startswith("DM") else f"DM{motor_type}" + if alt in MONITOR_MOTOR_PRESETS: + return MONITOR_MOTOR_PRESETS[alt] + return MONITOR_MOTOR_PRESETS[DEFAULT_MOTOR_TYPE] + + +# Frame kinds. +KIND_COMMAND = "command" +KIND_FEEDBACK = "feedback" +KIND_SPECIAL = "special" +KIND_REGISTER = "register" +KIND_UNKNOWN = "unknown" + + +@dataclass +class DecodedFrame: + """A single passively-decoded CAN frame.""" + + t: float + arbitration_id: int + kind: str + motor_id: int + raw: bytes + mode: Optional[str] = None # MIT / POS_VEL / VEL / FORCE_POS for commands + fields: Dict[str, float] = field(default_factory=dict) + note: str = "" + + +def _u16_be(data: bytes, i: int) -> int: + return (data[i] << 8) | data[i + 1] + + +def _decode_mit(data: bytes, lim: Dict[str, float]) -> Dict[str, float]: + pos_u = (data[0] << 8) | data[1] + vel_u = (data[2] << 4) | (data[3] >> 4) + kp_u = ((data[3] & 0xF) << 8) | data[4] + kd_u = (data[5] << 4) | (data[6] >> 4) + torq_u = ((data[6] & 0xF) << 8) | data[7] + return { + "pos": uint_to_float(pos_u, lim["p_min"], lim["p_max"], 16), + "vel": uint_to_float(vel_u, lim["v_min"], lim["v_max"], 12), + "kp": uint_to_float(kp_u, KP_MIN, KP_MAX, 12), + "kd": uint_to_float(kd_u, KD_MIN, KD_MAX, 12), + "torque": uint_to_float(torq_u, lim["t_min"], lim["t_max"], 12), + } + + +def _decode_feedback(data: bytes, lim: Dict[str, float]) -> Dict[str, float]: + status = data[0] >> 4 + pos_int = (data[1] << 8) | data[2] + vel_int = (data[3] << 4) | (data[4] >> 4) + torq_int = ((data[4] & 0xF) << 8) | data[5] + return { + "status_code": float(status), + "pos": uint_to_float(pos_int, lim["p_min"], lim["p_max"], 16), + "vel": uint_to_float(vel_int, lim["v_min"], lim["v_max"], 12), + "torque": uint_to_float(torq_int, lim["t_min"], lim["t_max"], 12), + "t_mos": float(data[6]), + "t_rotor": float(data[7]), + } + + +def _looks_like_feedback(arb: int, data: bytes, offset: int) -> Optional[int]: + """Return the motor id if the frame looks like a feedback frame, else None. + + Feedback arbitration id = motor_id + offset, with the motor's CAN id in the low + nibble of ``data[0]``. We deliberately do NOT require the status nibble to be a known + code: real motors report states outside the documented set (observed: status 3 on + linearbot joints 4-7), and the arb-range + can-id-nibble match is already unambiguous + for the standard schemes (feedback ids offset+1..offset+15 don't overlap MIT command + ids 1..15 for offset >= 16). + """ + mid = arb - offset + if 1 <= mid <= 15 and (data[0] & 0x0F) == mid: + return mid + return None + + +def decode_frame( + arbitration_id: int, + data: bytes, + t: float, + motor_types: Optional[Dict[int, str]] = None, + default_motor_type: str = DEFAULT_MOTOR_TYPE, + feedback_offset: int = DEFAULT_FEEDBACK_OFFSET, +) -> Optional[DecodedFrame]: + """Classify and decode one observed CAN frame. + + Args: + arbitration_id: 11-bit CAN id. + data: frame payload (must be 8 bytes to decode motor frames). + t: observation timestamp (seconds). + motor_types: optional per-motor-id motor type used to scale pos/vel/torque. + default_motor_type: fallback motor type when an id is not in ``motor_types``. + feedback_offset: feedback arb = motor_id + offset (16 for the I2RT p16 scheme). + + Returns: + A :class:`DecodedFrame`, or ``None`` if the frame can't be interpreted. + """ + motor_types = motor_types or {} + if len(data) != 8: + return None + + def lim_for(mid: int) -> Dict[str, float]: + return resolve_limits(motor_types.get(mid, default_motor_type)) + + # 1) Register reply (motor -> bus). Structural test, arb-independent. + if is_register_reply(data): + motor_id = data[0] | (data[1] << 8) + return DecodedFrame( + t=t, + arbitration_id=arbitration_id, + kind=KIND_REGISTER, + motor_id=motor_id, + raw=bytes(data), + fields={"rid": float(data[3])}, + note="register reply", + ) + + # 2) Special commands (FF*7 + suffix). + if data[:7] == b"\xff\xff\xff\xff\xff\xff\xff" and data[7] in _SPECIAL_SUFFIX: + return DecodedFrame( + t=t, + arbitration_id=arbitration_id, + kind=KIND_SPECIAL, + motor_id=arbitration_id, + raw=bytes(data), + note=_SPECIAL_SUFFIX[data[7]], + ) + + # 3) Non-MIT command modes by arbitration-id window. + if POS_VEL_BASE <= arbitration_id < POS_VEL_BASE + 0x100: + mid = arbitration_id - POS_VEL_BASE + pos, vel_limit = struct.unpack(" bus), via offset + structural check. + mid = _looks_like_feedback(arbitration_id, data, feedback_offset) + if mid is not None: + lim = lim_for(mid) + fb = _decode_feedback(data, lim) + status_name = _decode_status_name(int(fb["status_code"])) + return DecodedFrame( + t, + arbitration_id, + KIND_FEEDBACK, + mid, + bytes(data), + fields=fb, + note=status_name, + ) + + # 5) Otherwise treat a low-id frame as an MIT command. + if 1 <= arbitration_id < POS_VEL_BASE: + lim = lim_for(arbitration_id) + return DecodedFrame( + t, + arbitration_id, + KIND_COMMAND, + arbitration_id, + bytes(data), + mode="MIT", + fields=_decode_mit(data, lim), + ) + + # 6) Register command space / anything else. + if arbitration_id == REGISTER_ARB: + return DecodedFrame( + t, + arbitration_id, + KIND_REGISTER, + data[0] | (data[1] << 8), + bytes(data), + note="register cmd", + ) + + return DecodedFrame(t, arbitration_id, KIND_UNKNOWN, arbitration_id, bytes(data)) diff --git a/damiao_motor/monitor/demo.py b/damiao_motor/monitor/demo.py new file mode 100644 index 0000000..a95e2b0 --- /dev/null +++ b/damiao_motor/monitor/demo.py @@ -0,0 +1,115 @@ +"""Synthetic traffic source for the passive monitor. + +Generates plausible command + feedback :class:`DecodedFrame` streams for a few motors +without any CAN hardware, so the dashboard can be developed, demoed, and screenshotted +anywhere. Feedback tracks the command with a small lag and noise, as a real motor would. +""" + +from __future__ import annotations + +import math +import threading +import time +from typing import Callable, List + +from damiao_motor.monitor.decode import ( + KIND_COMMAND, + KIND_FEEDBACK, + DecodedFrame, +) + +FrameCallback = Callable[[DecodedFrame], None] + + +class DemoSource: + def __init__( + self, + on_frame: FrameCallback, + bus_name: str = "demo", + motor_ids: List[int] = (1, 2, 3), + rate_hz: float = 100.0, + ) -> None: + self.on_frame = on_frame + self.bus_name = bus_name + self.motor_ids = list(motor_ids) + self.rate_hz = rate_hz + self._running = False + self._thread = None + # crude per-motor first-order lag state for feedback + self._fb_pos = {m: 0.0 for m in self.motor_ids} + + def start(self) -> None: + if self._running: + return + self._running = True + self._thread = threading.Thread( + target=self._loop, name="demo-source", daemon=True + ) + self._thread.start() + + def stop(self) -> None: + self._running = False + if self._thread is not None: + self._thread.join(timeout=0.5) + self._thread = None + + def _loop(self) -> None: + period = 1.0 / self.rate_hz + t0 = time.time() + while self._running: + now = time.time() + t = now - t0 + for i, mid in enumerate(self.motor_ids): + phase = i * 0.7 + freq = 0.25 + 0.15 * i + cmd_pos = 1.5 * math.sin(2 * math.pi * freq * t + phase) + cmd_vel = ( + 1.5 * 2 * math.pi * freq * math.cos(2 * math.pi * freq * t + phase) + ) + kp, kd = 60.0, 1.5 + # feedback lags the command and carries noise + load torque + lag = 0.15 + self._fb_pos[mid] += (cmd_pos - self._fb_pos[mid]) * lag + jitter = 0.01 * math.sin(37 * t + mid) + fb_pos = self._fb_pos[mid] + jitter + fb_vel = cmd_vel * 0.92 + 0.05 * math.sin(53 * t + mid) + fb_torq = kp * (cmd_pos - fb_pos) + 0.2 * math.sin(11 * t + mid) + t_mos = 32 + 3 * math.sin(0.1 * t + mid) + t_rotor = 35 + 4 * math.sin(0.08 * t + mid) + + self.on_frame( + DecodedFrame( + t=now, + arbitration_id=mid, + kind=KIND_COMMAND, + motor_id=mid, + raw=b"\x00" * 8, + mode="MIT", + fields={ + "pos": cmd_pos, + "vel": cmd_vel, + "torque": 0.0, + "kp": kp, + "kd": kd, + }, + ) + ) + self.on_frame( + DecodedFrame( + t=now, + arbitration_id=mid + 16, + kind=KIND_FEEDBACK, + motor_id=mid, + raw=b"\x00" * 8, + note="ENABLED", + fields={ + "pos": fb_pos, + "vel": fb_vel, + "torque": fb_torq, + "t_mos": t_mos, + "t_rotor": t_rotor, + "status_code": 1.0, + }, + ) + ) + time.sleep(period) diff --git a/damiao_motor/monitor/listener.py b/damiao_motor/monitor/listener.py new file mode 100644 index 0000000..0e59eb9 --- /dev/null +++ b/damiao_motor/monitor/listener.py @@ -0,0 +1,147 @@ +"""Listen-only CAN reader for passive monitoring. + +:class:`PassiveCanListener` opens its **own** ``can.Bus`` and decodes every frame it +sees, without ever transmitting. It is intended to run alongside another controller that +is actively driving the motors: + +* On ``socketcan`` every opened socket receives its own copy of the bus RX, so a second + listener does not steal frames from the running controller. +* ``receive_own_messages=False`` is requested, and ``CAN_RAW_LISTEN_ONLY`` is set on the + raw socket on a best-effort basis (python-can does not expose it) as defense-in-depth. + +The hard guarantee that we never perturb the bus is *structural*: this module imports +nothing that sends and never calls ``bus.send``. +""" + +from __future__ import annotations + +import threading +import time +from typing import Callable, Dict, Optional + +import can + +from damiao_motor.monitor.decode import ( + DEFAULT_FEEDBACK_OFFSET, + DEFAULT_MOTOR_TYPE, + DecodedFrame, + decode_frame, +) + +FrameCallback = Callable[[DecodedFrame], None] + + +def _try_set_listen_only(bus: "can.BusABC") -> bool: + """Best-effort: put the underlying socketcan raw socket into listen-only mode. + + Returns True if the option was applied. Never raises; on any failure the listener + still never transmits (it simply may ACK frames at the hardware level). + """ + sock = getattr(bus, "socket", None) + if sock is None: + return False + try: + import socket as _socket + + # CAN_RAW_LISTEN_ONLY is not always present in the socket module; fall back to + # the known constant value (6) used by the Linux SocketCAN raw protocol. + opt = getattr(_socket, "CAN_RAW_LISTEN_ONLY", 6) + level = getattr(_socket, "SOL_CAN_RAW", 101) + sock.setsockopt(level, opt, 1) + return True + except Exception: + return False + + +class PassiveCanListener: + def __init__( + self, + channel: str, + bustype: str = "socketcan", + bitrate: Optional[int] = None, + feedback_offset: int = DEFAULT_FEEDBACK_OFFSET, + motor_types: Optional[Dict[int, str]] = None, + default_motor_type: str = DEFAULT_MOTOR_TYPE, + on_frame: Optional[FrameCallback] = None, + ) -> None: + self.channel = channel + self.bustype = bustype + self.bitrate = bitrate + self.feedback_offset = feedback_offset + self.motor_types = dict(motor_types or {}) + self.default_motor_type = default_motor_type + self.on_frame = on_frame + + self.bus: Optional[can.BusABC] = None + self.listen_only_applied = False + self._thread: Optional[threading.Thread] = None + self._running = False + # diagnostics + self.frames_seen = 0 + self.decode_errors = 0 + + # ------------------------------------------------------------------- bus + def open(self) -> None: + bus_kwargs: Dict[str, object] = { + "channel": self.channel, + "interface": self.bustype, + "receive_own_messages": False, + } + if self.bitrate is not None: + bus_kwargs["bitrate"] = self.bitrate + self.bus = can.interface.Bus(**bus_kwargs) + self.listen_only_applied = _try_set_listen_only(self.bus) + + def start(self) -> None: + if self._running: + return + if self.bus is None: + self.open() + self._running = True + self._thread = threading.Thread( + target=self._loop, name=f"passive-listener-{self.channel}", daemon=True + ) + self._thread.start() + + def stop(self) -> None: + self._running = False + if self._thread is not None: + self._thread.join(timeout=0.5) + self._thread = None + if self.bus is not None: + try: + self.bus.shutdown() + except Exception: + pass + self.bus = None + + def set_motor_type(self, motor_id: int, motor_type: str) -> None: + self.motor_types[motor_id] = motor_type + + # ------------------------------------------------------------------ loop + def _loop(self) -> None: + assert self.bus is not None + while self._running: + try: + msg = self.bus.recv(timeout=0.1) + except Exception: + # bus closed / transient error; brief backoff then re-check _running + time.sleep(0.01) + continue + if msg is None: + continue + self.frames_seen += 1 + try: + frame = decode_frame( + msg.arbitration_id, + bytes(msg.data), + t=msg.timestamp or time.time(), + motor_types=self.motor_types, + default_motor_type=self.default_motor_type, + feedback_offset=self.feedback_offset, + ) + except Exception: + self.decode_errors += 1 + continue + if frame is not None and self.on_frame is not None: + self.on_frame(frame) diff --git a/damiao_motor/monitor/server.py b/damiao_motor/monitor/server.py new file mode 100644 index 0000000..2af774b --- /dev/null +++ b/damiao_motor/monitor/server.py @@ -0,0 +1,527 @@ +"""Unified Flask server for the DaMiao Studio UI (Control + Monitor). + +One app, one shared signal store, two modes: + +* **monitor** — passive listen-only: a :class:`PassiveCanListener` decodes another + controller's commands + the motors' feedback into the store. Never transmits. +* **control** — active: a :class:`ControlService` (DaMiaoController) connects/scans and + drives motors; every command + feedback is pushed into the same store, so the realtime + plots/table/cards show what we're driving. + +Visualization endpoints (signals / snapshot / WS stream) read the shared store and work in +both modes. Control endpoints are gated to control mode. +""" + +from __future__ import annotations + +import json +import logging +import os +import sys +import time +from collections import deque +from typing import Any, Deque, Dict, List, Optional, Tuple + +from flask import Flask, jsonify, request, send_from_directory +from flask_sock import Sock + +from damiao_motor.monitor.control import ControlService +from damiao_motor.monitor.decode import DEFAULT_FEEDBACK_OFFSET, DEFAULT_MOTOR_TYPE +from damiao_motor.monitor.listener import PassiveCanListener +from damiao_motor.monitor.service import _frame_to_log +from damiao_motor.monitor.store import SignalStore + +_WEBAPP_DIST = os.path.normpath( + os.path.join(os.path.dirname(__file__), "..", "gui", "webapp", "dist") +) +_MAX_POINTS_PER_TICK = 240 + +_DEV_PLACEHOLDER = """ +DaMiao Studio +

DaMiao Studio

The dashboard bundle has not been built yet.

+
cd damiao_motor/gui/webapp && npm install && npm run build
+

The REST API is live at /api/status.

""" + + +class Studio: + """Holds the shared store + the active mode's data source.""" + + def __init__( + self, + mode: str = "monitor", + feedback_offset: int = DEFAULT_FEEDBACK_OFFSET, + default_motor_type: str = DEFAULT_MOTOR_TYPE, + raw_log_size: int = 4000, + ) -> None: + self.mode = mode # 'monitor' | 'control' + self.feedback_offset = feedback_offset + self.default_motor_type = default_motor_type + self.store = SignalStore(bus_name="bus") + self._raw: Deque[Dict[str, Any]] = deque(maxlen=raw_log_size) + self._raw_seq = 0 + self.listener: Optional[PassiveCanListener] = None + self._demo_source = None + self.control = ControlService(self.store, raw_push=self._raw_push) + self.channel: Optional[str] = None + self.bustype: str = "socketcan" + self.demo = False + self.error: Optional[str] = None + + # ------------------------------------------------------------- raw log + def _raw_push(self, frame) -> None: + self._raw_seq += 1 + self._raw.append(_frame_to_log(self._raw_seq, frame)) + + def raw_since( + self, since_seq: int, limit: int = 400 + ) -> Tuple[int, List[Dict[str, Any]]]: + if not self._raw: + return since_seq, [] + items = [r for r in self._raw if r["seq"] > since_seq] + if len(items) > limit: + items = items[-limit:] + return (items[-1]["seq"] if items else since_seq), items + + # ------------------------------------------------------------- sources + def _on_passive_frame(self, frame) -> None: + self.store.ingest(frame) + self._raw_push(frame) + + def connect( + self, + channel: str, + bustype: str, + bitrate: Optional[int], + motor_type: Optional[str] = None, + feedback_offset: Optional[int] = None, + ) -> Dict[str, Any]: + self.disconnect() + self.error = None + self.channel, self.bustype = channel, bustype + if feedback_offset is not None: + self.feedback_offset = feedback_offset + if self.mode == "control": + self.control.connect(channel, bustype, bitrate) + found = self.control.scan(motor_type or self.default_motor_type) + return {"motors": found} + else: + self.listener = PassiveCanListener( + channel=channel, + bustype=bustype, + bitrate=bitrate, + feedback_offset=self.feedback_offset, + default_motor_type=motor_type or self.default_motor_type, + on_frame=self._on_passive_frame, + ) + self.listener.start() + return {"motors": []} + + def disconnect(self) -> None: + if self.listener is not None: + self.listener.stop() + self.listener = None + if self._demo_source is not None: + self._demo_source.stop() + self._demo_source = None + self.control.disconnect() + + def start_demo(self) -> None: + from damiao_motor.monitor.demo import DemoSource + + self.demo = True + self.channel = "demo" + self._demo_source = DemoSource(on_frame=self._on_passive_frame, bus_name="bus") + self._demo_source.start() + + def set_mode(self, mode: str) -> None: + if mode not in ("monitor", "control"): + raise ValueError("mode must be 'monitor' or 'control'") + self.disconnect() + self.mode = mode + self.demo = False + + # ------------------------------------------------------------- readouts + def status(self) -> Dict[str, Any]: + connected = ( + (self.mode == "control" and self.control.connected) + or (self.listener is not None) + or (self._demo_source is not None) + ) + return { + "mode": self.mode, + "connected": connected, + "channel": self.channel, + "bustype": self.bustype, + "demo": self.demo, + "listenOnly": bool(self.listener and self.listener.listen_only_applied), + "framesSeen": self.listener.frames_seen if self.listener else self._raw_seq, + "decodeErrors": self.listener.decode_errors if self.listener else 0, + "feedbackOffset": self.feedback_offset, + "defaultMotorType": self.default_motor_type, + "registryVersion": self.store.registry_version, + "error": self.control.error or self.error, + } + + def signals(self) -> Dict[str, Any]: + return { + "signals": self.store.list_signals(), + "pairs": self.store.pairs(), + "motors": self.store.motor_views(), + "version": self.store.registry_version, + } + + def snapshot(self, ids: List[str], n: int) -> Dict[str, List[Tuple[float, float]]]: + return {sid: self.store.series_last_n(sid, n) for sid in ids} + + +def _platform_defaults() -> Dict[str, Any]: + is_mac = sys.platform == "darwin" + return { + "platform": sys.platform, + "default_bustype": "gs_usb" if is_mac else "socketcan", + "default_channel": "0" if is_mac else "can0", + } + + +def create_app(studio: Studio) -> Flask: + app = Flask(__name__) + sock = Sock(app) + + def control_guard(): + if studio.mode != "control": + return jsonify({"success": False, "error": "Not in control mode"}), 409 + if not studio.control.connected: + return jsonify({"success": False, "error": "Not connected"}), 400 + return None + + # ----------------------------------------------------------- common + @app.route("/api/status") + def status(): + return jsonify(studio.status()) + + @app.route("/api/mode", methods=["POST"]) + def set_mode(): + data = request.get_json(force=True, silent=True) or {} + try: + studio.set_mode(str(data.get("mode"))) + except ValueError as e: + return jsonify({"success": False, "error": str(e)}), 400 + return jsonify({"success": True, "mode": studio.mode}) + + @app.route("/api/connect", methods=["POST"]) + def connect(): + data = request.get_json(force=True, silent=True) or {} + channel = data.get("channel", "can0") + bustype = data.get("bustype", "socketcan") + bitrate = data.get("bitrate") + bitrate = int(bitrate) if bitrate not in (None, "") else None + try: + res = studio.connect( + channel, + bustype, + bitrate, + motor_type=data.get("motor_type"), + feedback_offset=data.get("feedback_offset"), + ) + return jsonify({"success": True, **res}) + except Exception as e: + studio.error = str(e) + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/disconnect", methods=["POST"]) + def disconnect(): + studio.disconnect() + return jsonify({"success": True}) + + @app.route("/api/platform") + def platform(): + return jsonify({"success": True, **_platform_defaults()}) + + @app.route("/api/motor-types") + def motor_types(): + from damiao_motor.monitor.decode import MONITOR_MOTOR_PRESETS + + return jsonify({"types": sorted(MONITOR_MOTOR_PRESETS.keys())}) + + @app.route("/api/register-table") + def register_table(): + return jsonify({"success": True, "registers": ControlService.register_table()}) + + @app.route("/api/can-interfaces") + def can_interfaces(): + bustype = request.args.get("bustype", "socketcan") + interfaces: List[str] = [] + if bustype != "gs_usb": + try: + net = "/sys/class/net" + if os.path.isdir(net): + interfaces = sorted( + n for n in os.listdir(net) if n.startswith("can") + ) + except OSError: + pass + return jsonify({"success": True, "interfaces": interfaces}) + + # ----------------------------------------------------- visualization + @app.route("/api/monitor/signals") + def signals(): + return jsonify(studio.signals()) + + @app.route("/api/monitor/snapshot") + def snapshot(): + ids = [s for s in request.args.get("signals", "").split(",") if s] + n = int(request.args.get("n", 600)) + return jsonify(studio.snapshot(ids, n)) + + @sock.route("/api/monitor/stream") + def stream(ws): + subscribed: Dict[str, float] = {} + rate = 30.0 + period = 1.0 / rate + raw_enabled = False + raw_cursor = 0 + last_version = -1 + + def drain(): + nonlocal rate, period, raw_enabled + while True: + msg = ws.receive(timeout=0) + if msg is None: + break + try: + cmd = json.loads(msg) + except (ValueError, TypeError): + continue + t = cmd.get("type") + if t == "subscribe": + now = time.time() + new = { + sid: subscribed.get(sid, now) for sid in cmd.get("signals", []) + } + subscribed.clear() + subscribed.update(new) + elif t == "rate": + try: + rate = max(1.0, min(120.0, float(cmd.get("value", 30)))) + period = 1.0 / rate + except (ValueError, TypeError): + pass + elif t == "raw": + raw_enabled = bool(cmd.get("enabled", False)) + + try: + while True: + drain() + sig = studio.signals() + if sig["version"] != last_version: + last_version = sig["version"] + ws.send(json.dumps({"type": "meta", **sig})) + ws.send( + json.dumps( + { + "type": "motors", + "motors": sig["motors"], + "status": studio.status(), + } + ) + ) + batch = {} + for sid, cursor in list(subscribed.items()): + pts = studio.store.series_since(sid, cursor) + if not pts: + continue + if len(pts) > _MAX_POINTS_PER_TICK: + stride = len(pts) // _MAX_POINTS_PER_TICK + 1 + pts = pts[::stride] + [pts[-1]] + batch[sid] = pts + subscribed[sid] = pts[-1][0] + if batch: + ws.send(json.dumps({"type": "samples", "data": batch})) + if raw_enabled: + raw_cursor, items = studio.raw_since(raw_cursor, limit=300) + if items: + ws.send(json.dumps({"type": "raw", "frames": items})) + time.sleep(period) + except Exception: + return + + # ---------------------------------------------------------- control + @app.route("/api/control/scan", methods=["POST"]) + def control_scan(): + g = control_guard() + if g: + return g + data = request.get_json(force=True, silent=True) or {} + try: + found = studio.control.scan( + data.get("motor_type") or studio.default_motor_type + ) + return jsonify({"success": True, "motors": found}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/control/motors") + def control_motors(): + g = control_guard() + if g: + return g + return jsonify({"success": True, "motors": studio.control.motors()}) + + def _simple_action(motor_id, fn): + g = control_guard() + if g: + return g + try: + fn(motor_id) + return jsonify({"success": True}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/control/motors//enable", methods=["POST"]) + def c_enable(mid): + return _simple_action(mid, studio.control.enable) + + @app.route("/api/control/motors//disable", methods=["POST"]) + def c_disable(mid): + return _simple_action(mid, studio.control.disable) + + @app.route("/api/control/motors//set-zero", methods=["POST"]) + def c_zero(mid): + return _simple_action(mid, studio.control.set_zero) + + @app.route("/api/control/motors//clear-error", methods=["POST"]) + def c_clear(mid): + return _simple_action(mid, studio.control.clear_error) + + @app.route("/api/control/motors//store-parameters", methods=["POST"]) + def c_store(mid): + return _simple_action(mid, studio.control.store_parameters) + + @app.route("/api/control/motors//command", methods=["POST"]) + def c_command(mid): + g = control_guard() + if g: + return g + data = request.get_json(force=True, silent=True) or {} + try: + state = studio.control.command(mid, data) + return jsonify({"success": True, "state": state}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/control/motors//state") + def c_state(mid): + g = control_guard() + if g: + return g + try: + return jsonify({"success": True, "state": studio.control.get_state(mid)}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/control/motors//registers") + def c_get_regs(mid): + g = control_guard() + if g: + return g + try: + return jsonify({"success": True, **studio.control.get_registers(mid)}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/control/motors//registers/", methods=["PUT"]) + def c_set_reg(mid, rid): + g = control_guard() + if g: + return g + data = request.get_json(force=True, silent=True) or {} + if "value" not in data: + return jsonify({"success": False, "error": "value required"}), 400 + try: + res = studio.control.set_register(mid, rid, data["value"]) + return jsonify({"success": True, **res}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route("/api/control/motors//motor-type", methods=["PUT"]) + def c_motor_type(mid): + g = control_guard() + if g: + return g + data = request.get_json(force=True, silent=True) or {} + mt = data.get("motor_type") + if not mt: + return jsonify({"success": False, "error": "motor_type required"}), 400 + try: + studio.control.set_motor_type(mid, mt) + return jsonify({"success": True}) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + # --------------------------------------------------------------- SPA + @app.route("/") + def index(): + idx = os.path.join(_WEBAPP_DIST, "index.html") + return ( + send_from_directory(_WEBAPP_DIST, "index.html") + if os.path.exists(idx) + else _DEV_PLACEHOLDER + ) + + @app.route("/") + def spa(path): + if path.startswith("api/"): + return jsonify({"error": "not found"}), 404 + full = os.path.join(_WEBAPP_DIST, path) + if os.path.exists(full) and os.path.isfile(full): + return send_from_directory(_WEBAPP_DIST, path) + idx = os.path.join(_WEBAPP_DIST, "index.html") + return ( + send_from_directory(_WEBAPP_DIST, "index.html") + if os.path.exists(idx) + else _DEV_PLACEHOLDER + ) + + return app + + +def run_server( + host: str = "127.0.0.1", + port: int = 5001, + mode: str = "monitor", + channel: str = "can0", + bustype: str = "socketcan", + bitrate: Optional[int] = None, + feedback_offset: int = 16, + default_motor_type: str = "DM4310", + debug: bool = False, + demo: bool = False, +) -> None: + """Start the unified DaMiao Studio server (blocking).""" + studio = Studio( + mode=mode, + feedback_offset=feedback_offset, + default_motor_type=default_motor_type, + ) + + if demo: + studio.mode = "monitor" + studio.start_demo() + elif mode == "monitor": + try: + studio.connect(channel, bustype, bitrate) + except Exception as e: + studio.error = str(e) + + app = create_app(studio) + + print(f"Starting DaMiao Studio ({'demo' if demo else mode} mode)...") + print(f" open http://{host}:{port}") + if not debug: + logging.getLogger("werkzeug").setLevel(logging.ERROR) + try: + app.run(host=host, port=port, debug=debug, threaded=True) + finally: + studio.disconnect() diff --git a/damiao_motor/monitor/service.py b/damiao_motor/monitor/service.py new file mode 100644 index 0000000..6e3e600 --- /dev/null +++ b/damiao_motor/monitor/service.py @@ -0,0 +1,155 @@ +"""Monitor service: wires a listen-only listener to a signal store + raw-frame log. + +Owns the lifecycle of a :class:`~damiao_motor.monitor.listener.PassiveCanListener` for a +single CAN bus and exposes thread-safe snapshots for the HTTP/WS layer in +:mod:`damiao_motor.monitor.server`. +""" + +from __future__ import annotations + +import threading +from collections import deque +from typing import Deque, Dict, List, Optional, Tuple + +from damiao_motor.monitor.decode import ( + DEFAULT_FEEDBACK_OFFSET, + DEFAULT_MOTOR_TYPE, + MONITOR_MOTOR_PRESETS, + DecodedFrame, +) +from damiao_motor.monitor.listener import PassiveCanListener +from damiao_motor.monitor.store import SignalStore + + +def _frame_to_log(seq: int, f: DecodedFrame) -> Dict[str, object]: + return { + "seq": seq, + "t": round(f.t, 6), + "arb": f.arbitration_id, + "kind": f.kind, + "mode": f.mode, + "motorId": f.motor_id, + "note": f.note, + "fields": {k: round(v, 4) for k, v in f.fields.items()}, + "raw": f.raw.hex(), + } + + +class MonitorService: + def __init__( + self, + channel: str, + bustype: str = "socketcan", + bitrate: Optional[int] = None, + feedback_offset: int = DEFAULT_FEEDBACK_OFFSET, + motor_types: Optional[Dict[int, str]] = None, + default_motor_type: str = DEFAULT_MOTOR_TYPE, + raw_log_size: int = 4000, + buffer_len: int = 6000, + demo: bool = False, + ) -> None: + self.channel = channel + self.bustype = bustype + self.bitrate = bitrate + self.default_motor_type = default_motor_type + self.demo = demo + self.store = SignalStore(bus_name=channel, maxlen=buffer_len) + + self._raw_lock = threading.Lock() + self._raw_log: Deque[Dict[str, object]] = deque(maxlen=raw_log_size) + self._raw_seq = 0 + + self.error: Optional[str] = None + self.started = False + + self.listener = PassiveCanListener( + channel=channel, + bustype=bustype, + bitrate=bitrate, + feedback_offset=feedback_offset, + motor_types=motor_types, + default_motor_type=default_motor_type, + on_frame=self._on_frame, + ) + self._demo_source = None + if demo: + from damiao_motor.monitor.demo import DemoSource + + self.store.bus_name = "demo" + self._demo_source = DemoSource(on_frame=self._on_frame, bus_name="demo") + + # ---------------------------------------------------------------- lifecycle + def start(self) -> None: + try: + if self._demo_source is not None: + self._demo_source.start() + else: + self.listener.start() + self.started = True + self.error = None + except Exception as exc: # surface to the UI rather than crashing the server + self.error = str(exc) + self.started = False + + def stop(self) -> None: + if self._demo_source is not None: + self._demo_source.stop() + else: + self.listener.stop() + self.started = False + + # ------------------------------------------------------------------ ingest + def _on_frame(self, frame: DecodedFrame) -> None: + self.store.ingest(frame) + with self._raw_lock: + self._raw_seq += 1 + self._raw_log.append(_frame_to_log(self._raw_seq, frame)) + + # --------------------------------------------------------------- accessors + def status(self) -> Dict[str, object]: + return { + "channel": self.channel, + "bustype": self.bustype, + "bitrate": self.bitrate, + "started": self.started, + "error": self.error, + "listenOnly": self.listener.listen_only_applied, + "feedbackOffset": self.listener.feedback_offset, + "framesSeen": self.listener.frames_seen, + "decodeErrors": self.listener.decode_errors, + "registryVersion": self.store.registry_version, + "defaultMotorType": self.default_motor_type, + "demo": self.demo, + } + + def signals(self) -> Dict[str, object]: + return { + "signals": self.store.list_signals(), + "pairs": self.store.pairs(), + "motors": self.store.motor_views(), + "version": self.store.registry_version, + } + + def snapshot( + self, signal_ids: List[str], n: int + ) -> Dict[str, List[Tuple[float, float]]]: + return {sid: self.store.series_last_n(sid, n) for sid in signal_ids} + + def raw_since( + self, since_seq: int, limit: int = 500 + ) -> Tuple[int, List[Dict[str, object]]]: + with self._raw_lock: + if not self._raw_log: + return since_seq, [] + items = [r for r in self._raw_log if r["seq"] > since_seq] + if len(items) > limit: + items = items[-limit:] + new_seq = items[-1]["seq"] if items else since_seq + return new_seq, items + + def set_motor_type(self, motor_id: int, motor_type: str) -> None: + self.listener.set_motor_type(motor_id, motor_type) + + @staticmethod + def available_motor_types() -> List[str]: + return sorted(MONITOR_MOTOR_PRESETS.keys()) diff --git a/damiao_motor/monitor/store.py b/damiao_motor/monitor/store.py new file mode 100644 index 0000000..5c4a32c --- /dev/null +++ b/damiao_motor/monitor/store.py @@ -0,0 +1,210 @@ +"""In-memory store for passively-observed signals. + +Ingests :class:`~damiao_motor.monitor.decode.DecodedFrame` objects and maintains: + +* an auto-learned **signal registry** (one entry per motor/source/field seen), +* a fixed-size **ring buffer** of ``(t, value)`` samples per signal, +* **cmd <-> feedback pairing** so the UI can overlay commanded vs. actual. + +Signal id format: ``"{bus}:m{motor_id}:{source}.{field}"`` e.g. ``"can_arm_l:m1:cmd.pos"``. +The ``pairKey`` ``"{bus}:m{motor_id}:{field}"`` links the cmd and feedback variants of the +same physical quantity. + +A single writer (the listener thread) calls :meth:`ingest`; readers (HTTP/WS handlers) +call the snapshot methods. All access is guarded by one lock. +""" + +from __future__ import annotations + +import threading +from collections import deque +from dataclasses import dataclass, field +from typing import Deque, Dict, List, Optional, Tuple + +from damiao_motor.monitor.decode import ( + DecodedFrame, + KIND_COMMAND, + KIND_FEEDBACK, +) + +# Per-source field whitelist -> the numeric channels worth plotting/tabulating. +_COMMAND_FIELDS = ("pos", "vel", "torque", "kp", "kd", "vel_limit", "torque_limit") +_FEEDBACK_FIELDS = ("pos", "vel", "torque", "t_mos", "t_rotor", "status_code") + +_UNITS = { + "pos": "rad", + "vel": "rad/s", + "torque": "Nm", + "kp": "", + "kd": "", + "vel_limit": "rad/s", + "torque_limit": "Nm", + "t_mos": "°C", + "t_rotor": "°C", + "status_code": "", +} + + +@dataclass +class SignalDescriptor: + """Metadata for one observable signal channel.""" + + id: str + bus: str + motor_id: int + source: str # "cmd" | "fb" + field: str + unit: str + pair_key: str + + def to_dict(self) -> Dict[str, object]: + return { + "id": self.id, + "bus": self.bus, + "motorId": self.motor_id, + "source": self.source, + "field": self.field, + "unit": self.unit, + "pairKey": self.pair_key, + } + + +@dataclass +class _Series: + desc: SignalDescriptor + buf: Deque[Tuple[float, float]] + last_value: float = 0.0 + last_t: float = 0.0 + count: int = 0 + + +@dataclass +class MotorView: + """Aggregated latest cmd + feedback for one motor, for the table/cards views.""" + + bus: str + motor_id: int + cmd: Dict[str, float] = field(default_factory=dict) + fb: Dict[str, float] = field(default_factory=dict) + mode: Optional[str] = None + status: str = "" + last_t: float = 0.0 + + +class SignalStore: + def __init__(self, bus_name: str, maxlen: int = 6000) -> None: + self.bus_name = bus_name + self._maxlen = maxlen + self._lock = threading.Lock() + self._series: Dict[str, _Series] = {} + self._motors: Dict[int, MotorView] = {} + # monotonically bumped whenever the registry (set of signals) changes, + # so clients can cheaply detect "new signals appeared". + self.registry_version = 0 + + # ------------------------------------------------------------------ ingest + def ingest(self, frame: DecodedFrame) -> None: + if frame.kind == KIND_COMMAND: + source, fields = "cmd", _COMMAND_FIELDS + elif frame.kind == KIND_FEEDBACK: + source, fields = "fb", _FEEDBACK_FIELDS + else: + return # special/register/unknown frames don't produce plottable signals + + with self._lock: + mv = self._motors.get(frame.motor_id) + if mv is None: + mv = MotorView(bus=self.bus_name, motor_id=frame.motor_id) + self._motors[frame.motor_id] = mv + mv.last_t = frame.t + if frame.kind == KIND_COMMAND: + mv.mode = frame.mode + mv.cmd = dict(frame.fields) + else: + mv.fb = dict(frame.fields) + mv.status = frame.note + + for fname in fields: + if fname not in frame.fields: + continue + value = float(frame.fields[fname]) + sid = f"{self.bus_name}:m{frame.motor_id}:{source}.{fname}" + series = self._series.get(sid) + if series is None: + series = _Series( + desc=SignalDescriptor( + id=sid, + bus=self.bus_name, + motor_id=frame.motor_id, + source=source, + field=fname, + unit=_UNITS.get(fname, ""), + pair_key=f"{self.bus_name}:m{frame.motor_id}:{fname}", + ), + buf=deque(maxlen=self._maxlen), + ) + self._series[sid] = series + self.registry_version += 1 + series.buf.append((frame.t, value)) + series.last_value = value + series.last_t = frame.t + series.count += 1 + + # --------------------------------------------------------------- snapshots + def list_signals(self) -> List[Dict[str, object]]: + with self._lock: + return [s.desc.to_dict() for s in self._series.values()] + + def pairs(self) -> List[Dict[str, object]]: + """Return cmd/feedback pairings that share a pairKey.""" + with self._lock: + by_pair: Dict[str, Dict[str, str]] = {} + for s in self._series.values(): + entry = by_pair.setdefault(s.desc.pair_key, {}) + entry[s.desc.source] = s.desc.id + return [ + {"pairKey": k, "cmd": v.get("cmd"), "fb": v.get("fb")} + for k, v in by_pair.items() + if "cmd" in v and "fb" in v + ] + + def series_since(self, signal_id: str, since_t: float) -> List[Tuple[float, float]]: + with self._lock: + s = self._series.get(signal_id) + if s is None: + return [] + return [pt for pt in s.buf if pt[0] > since_t] + + def series_last_n(self, signal_id: str, n: int) -> List[Tuple[float, float]]: + with self._lock: + s = self._series.get(signal_id) + if s is None: + return [] + if n <= 0 or n >= len(s.buf): + return list(s.buf) + return list(s.buf)[-n:] + + def latest(self, signal_ids: List[str]) -> Dict[str, Optional[float]]: + with self._lock: + return { + sid: (self._series[sid].last_value if sid in self._series else None) + for sid in signal_ids + } + + def motor_views(self) -> List[Dict[str, object]]: + with self._lock: + out = [] + for mid in sorted(self._motors): + mv = self._motors[mid] + out.append( + { + "bus": mv.bus, + "motorId": mv.motor_id, + "mode": mv.mode, + "status": mv.status, + "lastT": mv.last_t, + "cmd": mv.cmd, + "fb": mv.fb, + } + ) + return out diff --git a/pyproject.toml b/pyproject.toml index 7ce9beb..160fb03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ keywords = ["damiao", "motor", "can", "robotics"] dependencies = [ "python-can>=4.3,<5.0", "flask>=3.0,<4.0", + "flask-sock>=0.7", "gs_usb>=0.3; sys_platform == 'darwin'", "pyusb>=1.2; sys_platform == 'darwin'", ] @@ -24,8 +25,8 @@ dependencies = [ Homepage = "https://github.com/jia-xie/python-damiao-driver" Repository = "https://github.com/jia-xie/python-damiao-driver" -[tool.setuptools] -packages = ["damiao_motor"] +[tool.setuptools.packages.find] +include = ["damiao_motor*"] [tool.setuptools_scm] write_to = "damiao_motor/_version.py" @@ -39,7 +40,14 @@ dev = [ ] [tool.setuptools.package-data] -damiao_motor = ["gui/templates/*.html", "gui/static/css/*.css", "gui/static/js/*.js"] +damiao_motor = [ + "gui/templates/*.html", + "gui/static/css/*.css", + "gui/static/js/*.js", + "gui/webapp/dist/*", + "gui/webapp/dist/**/*", + "gui/webapp/dist/assets/*", +] damiao_motor_docs = ["docs/assets/*.css"] [project.scripts] diff --git a/tests/test_monitor.py b/tests/test_monitor.py new file mode 100644 index 0000000..6cebb8f --- /dev/null +++ b/tests/test_monitor.py @@ -0,0 +1,230 @@ +"""Tests for the passive monitor: decode round-trips and the never-transmit guarantee.""" + +import struct +import time + +import can +import pytest + +from damiao_motor.core.motor import ( + DaMiaoMotor, + float_to_uint, +) +from damiao_motor.monitor.decode import ( + KIND_COMMAND, + KIND_FEEDBACK, + KIND_SPECIAL, + decode_frame, + resolve_limits, +) +from damiao_motor.monitor.listener import PassiveCanListener +from damiao_motor.monitor.store import SignalStore + +MOTOR_ID = 3 +MOTOR_TYPE = "4310" + + +def _motor(): + # bus is unused by the encode_* helpers, so None is fine here. + return DaMiaoMotor( + motor_id=MOTOR_ID, feedback_id=MOTOR_ID + 16, bus=None, motor_type=MOTOR_TYPE + ) + + +def _make_feedback_frame(motor_id, status, pos, vel, torq, t_mos, t_rotor, lim): + pos_u = float_to_uint(pos, lim["p_min"], lim["p_max"], 16) + vel_u = float_to_uint(vel, lim["v_min"], lim["v_max"], 12) + torq_u = float_to_uint(torq, lim["t_min"], lim["t_max"], 12) + return bytes( + [ + (status << 4) | (motor_id & 0x0F), + (pos_u >> 8) & 0xFF, + pos_u & 0xFF, + (vel_u >> 4) & 0xFF, + ((vel_u & 0xF) << 4) | ((torq_u >> 8) & 0xF), + torq_u & 0xFF, + t_mos & 0xFF, + t_rotor & 0xFF, + ] + ) + + +# --------------------------------------------------------------------- decode +def test_mit_command_roundtrip(): + m = _motor() + data = m.encode_cmd_msg(pos=1.25, vel=-2.0, torq=0.5, kp=40.0, kd=1.5) + frame = decode_frame(MOTOR_ID, data, t=0.0, motor_types={MOTOR_ID: MOTOR_TYPE}) + assert frame.kind == KIND_COMMAND and frame.mode == "MIT" + assert frame.motor_id == MOTOR_ID + assert frame.fields["pos"] == pytest.approx(1.25, abs=1e-3) + assert frame.fields["vel"] == pytest.approx(-2.0, abs=2e-2) + assert frame.fields["kp"] == pytest.approx(40.0, abs=0.2) + assert frame.fields["kd"] == pytest.approx(1.5, abs=1e-2) + assert frame.fields["torque"] == pytest.approx(0.5, abs=1e-2) + + +def test_pos_vel_command_roundtrip(): + data = struct.pack(" feedback arb 20, status nibble 3 + data = _make_feedback_frame(4, 3, 1.0, 0.0, 0.0, 30, 30, lim) + frame = decode_frame(4 + 16, data, t=0.0, motor_types={4: MOTOR_TYPE}) + assert frame.kind == KIND_FEEDBACK + assert frame.motor_id == 4 + assert frame.fields["pos"] == pytest.approx(1.0, abs=1e-3) + assert "UNKNOWN" in frame.note # status 3 surfaced as UNKNOWN(3), not dropped + + +def test_special_command(): + frame = decode_frame(MOTOR_ID, bytes([0xFF] * 7 + [0xFC]), t=0.0) + assert frame.kind == KIND_SPECIAL and frame.note == "enable" + + +def test_mit_and_feedback_not_confused(): + """A MIT command to motor 3 (arb 3) and feedback from motor 3 (arb 19) classify distinctly.""" + m = _motor() + cmd = decode_frame( + MOTOR_ID, + m.encode_cmd_msg(0.0, 0.0, 0.0, 10.0, 1.0), + t=0.0, + motor_types={MOTOR_ID: MOTOR_TYPE}, + ) + lim = resolve_limits(MOTOR_TYPE) + fb = decode_frame( + MOTOR_ID + 16, + _make_feedback_frame(MOTOR_ID, 1, 0.0, 0.0, 0.0, 30, 30, lim), + t=0.0, + motor_types={MOTOR_ID: MOTOR_TYPE}, + ) + assert cmd.kind == KIND_COMMAND and cmd.mode == "MIT" + assert fb.kind == KIND_FEEDBACK + + +# ---------------------------------------------------------------------- store +def test_store_ingest_and_pairing(): + store = SignalStore("can_test") + m = _motor() + lim = resolve_limits(MOTOR_TYPE) + cmd = decode_frame( + MOTOR_ID, + m.encode_cmd_msg(1.0, 0.0, 0.0, 10.0, 1.0), + t=1.0, + motor_types={MOTOR_ID: MOTOR_TYPE}, + ) + fb = decode_frame( + MOTOR_ID + 16, + _make_feedback_frame(MOTOR_ID, 1, 0.9, 0.0, 0.0, 30, 30, lim), + t=1.0, + motor_types={MOTOR_ID: MOTOR_TYPE}, + ) + store.ingest(cmd) + store.ingest(fb) + + ids = {s["id"] for s in store.list_signals()} + assert "can_test:m3:cmd.pos" in ids + assert "can_test:m3:fb.pos" in ids + + pairs = store.pairs() + pos_pair = next(p for p in pairs if p["pairKey"] == "can_test:m3:pos") + assert pos_pair["cmd"] == "can_test:m3:cmd.pos" + assert pos_pair["fb"] == "can_test:m3:fb.pos" + + views = store.motor_views() + assert views[0]["motorId"] == MOTOR_ID + assert views[0]["cmd"]["pos"] == pytest.approx(1.0, abs=1e-3) + + +# ------------------------------------------------------------------- listener +class _FakeBus: + """Minimal can.Bus stand-in that records any send attempt (there must be none).""" + + def __init__(self, messages): + self._messages = list(messages) + self.send_called = False + self.socket = None + + def recv(self, timeout=0.0): + if self._messages: + return self._messages.pop(0) + time.sleep(0.005) + return None + + def send(self, *a, **k): # pragma: no cover - must never be hit + self.send_called = True + raise AssertionError("PassiveCanListener must never transmit") + + def shutdown(self): + pass + + +def test_listener_never_transmits_and_decodes(): + lim = resolve_limits(MOTOR_TYPE) + msgs = [ + can.Message( + arbitration_id=MOTOR_ID + 16, + data=_make_feedback_frame(MOTOR_ID, 1, 1.0, 2.0, 0.5, 30, 31, lim), + timestamp=1.0, + is_extended_id=False, + ), + ] + received = [] + listener = PassiveCanListener(channel="vcan_test", on_frame=received.append) + listener.bus = _FakeBus(msgs) # inject; start() won't re-open since bus is set + listener.start() + deadline = time.time() + 1.0 + while not received and time.time() < deadline: + time.sleep(0.01) + listener.stop() + + assert received, "listener should have decoded the injected feedback frame" + assert received[0].kind == KIND_FEEDBACK + assert received[0].motor_id == MOTOR_ID + + +def test_listener_source_has_no_send_call(): + """Belt-and-suspenders: the listener module never references bus.send.""" + import damiao_motor.monitor.listener as lst + + with open(lst.__file__) as f: + src = f.read() + assert ".send(" not in src