TableControlPage-DUmfP8p3.js 26 KB

1
  1. import{j as e,r as n}from"./react-DnK_pEU5.js";import{p as $e,q as Ce,r as Ge,v as ze,w as Be,x as be,A as Ke,y as Ve,z as F,E as qe,F as Ue,G as We,H as Ye,c as Xe,u as ee,S as me,B as j,k as ue,J as G,l as pe,m as xe,n as fe,K as je,o as ge,L as ye,I as ve,d as H,t as m}from"./index-B3c97KGp.js";import{C as z,a as B,b as K,c as V,d as q}from"./card-DweoKf_j.js";import{A as te,a as se}from"./alert-CjHh5uAz.js";import{R as Je,S as Qe,a as Ze,b as et,c as tt,d as st}from"./select-CmzQQBWZ.js";import{u as rt,C as at}from"./ConnectionBanner-CCnOHE5N.js";import"./dnd-Bgdma8TD.js";import"./motion-8nKk6_RD.js";import"./index-Sj6DXm4R.js";var nt=Symbol("radix.slottable");function it(r){const a=({children:t})=>e.jsx(e.Fragment,{children:t});return a.displayName=`${r}.Slottable`,a.__radixId=nt,a}var[Y]=$e("Tooltip",[Ce]),X=Ce(),we="TooltipProvider",ot=700,re="tooltip.open",[lt,ne]=Y(we),Ne=r=>{const{__scopeTooltip:a,delayDuration:t=ot,skipDelayDuration:o=300,disableHoverableContent:l=!1,children:d}=r,i=n.useRef(!0),v=n.useRef(!1),c=n.useRef(0);return n.useEffect(()=>{const h=c.current;return()=>window.clearTimeout(h)},[]),e.jsx(lt,{scope:a,isOpenDelayedRef:i,delayDuration:t,onOpen:n.useCallback(()=>{window.clearTimeout(c.current),i.current=!1},[]),onClose:n.useCallback(()=>{window.clearTimeout(c.current),c.current=window.setTimeout(()=>i.current=!0,o)},[o]),isPointerInTransitRef:v,onPointerInTransitChange:n.useCallback(h=>{v.current=h},[]),disableHoverableContent:l,children:d})};Ne.displayName=we;var U="Tooltip",[ct,J]=Y(U),Te=r=>{const{__scopeTooltip:a,children:t,open:o,defaultOpen:l,onOpenChange:d,disableHoverableContent:i,delayDuration:v}=r,c=ne(U,r.__scopeTooltip),h=X(a),[u,x]=n.useState(null),C=Ge(),g=n.useRef(0),w=i??c.disableHoverableContent,f=v??c.delayDuration,N=n.useRef(!1),[b,_]=ze({prop:o,defaultProp:l??!1,onChange:T=>{T?(c.onOpen(),document.dispatchEvent(new CustomEvent(re))):c.onClose(),d?.(T)},caller:U}),E=n.useMemo(()=>b?N.current?"delayed-open":"instant-open":"closed",[b]),L=n.useCallback(()=>{window.clearTimeout(g.current),g.current=0,N.current=!1,_(!0)},[_]),R=n.useCallback(()=>{window.clearTimeout(g.current),g.current=0,_(!1)},[_]),I=n.useCallback(()=>{window.clearTimeout(g.current),g.current=window.setTimeout(()=>{N.current=!0,_(!0),g.current=0},f)},[f,_]);return n.useEffect(()=>()=>{g.current&&(window.clearTimeout(g.current),g.current=0)},[]),e.jsx(Be,{...h,children:e.jsx(ct,{scope:a,contentId:C,open:b,stateAttribute:E,trigger:u,onTriggerChange:x,onTriggerEnter:n.useCallback(()=>{c.isOpenDelayedRef.current?I():L()},[c.isOpenDelayedRef,I,L]),onTriggerLeave:n.useCallback(()=>{w?R():(window.clearTimeout(g.current),g.current=0)},[R,w]),onOpen:L,onClose:R,disableHoverableContent:w,children:t})})};Te.displayName=U;var ae="TooltipTrigger",_e=n.forwardRef((r,a)=>{const{__scopeTooltip:t,...o}=r,l=J(ae,t),d=ne(ae,t),i=X(t),v=n.useRef(null),c=be(a,v,l.onTriggerChange),h=n.useRef(!1),u=n.useRef(!1),x=n.useCallback(()=>h.current=!1,[]);return n.useEffect(()=>()=>document.removeEventListener("pointerup",x),[x]),e.jsx(Ke,{asChild:!0,...i,children:e.jsx(Ve.button,{"aria-describedby":l.open?l.contentId:void 0,"data-state":l.stateAttribute,...o,ref:c,onPointerMove:F(r.onPointerMove,C=>{C.pointerType!=="touch"&&!u.current&&!d.isPointerInTransitRef.current&&(l.onTriggerEnter(),u.current=!0)}),onPointerLeave:F(r.onPointerLeave,()=>{l.onTriggerLeave(),u.current=!1}),onPointerDown:F(r.onPointerDown,()=>{l.open&&l.onClose(),h.current=!0,document.addEventListener("pointerup",x,{once:!0})}),onFocus:F(r.onFocus,()=>{h.current||l.onOpen()}),onBlur:F(r.onBlur,l.onClose),onClick:F(r.onClick,l.onClose)})})});_e.displayName=ae;var dt="TooltipPortal",[Mt,ht]=Y(dt,{forceMount:void 0}),$="TooltipContent",Se=n.forwardRef((r,a)=>{const t=ht($,r.__scopeTooltip),{forceMount:o=t.forceMount,side:l="top",...d}=r,i=J($,r.__scopeTooltip);return e.jsx(qe,{present:o||i.open,children:i.disableHoverableContent?e.jsx(Ee,{side:l,...d,ref:a}):e.jsx(mt,{side:l,...d,ref:a})})}),mt=n.forwardRef((r,a)=>{const t=J($,r.__scopeTooltip),o=ne($,r.__scopeTooltip),l=n.useRef(null),d=be(a,l),[i,v]=n.useState(null),{trigger:c,onClose:h}=t,u=l.current,{onPointerInTransitChange:x}=o,C=n.useCallback(()=>{v(null),x(!1)},[x]),g=n.useCallback((w,f)=>{const N=w.currentTarget,b={x:w.clientX,y:w.clientY},_=jt(b,N.getBoundingClientRect()),E=gt(b,_),L=yt(f.getBoundingClientRect()),R=Ct([...E,...L]);v(R),x(!0)},[x]);return n.useEffect(()=>()=>C(),[C]),n.useEffect(()=>{if(c&&u){const w=N=>g(N,u),f=N=>g(N,c);return c.addEventListener("pointerleave",w),u.addEventListener("pointerleave",f),()=>{c.removeEventListener("pointerleave",w),u.removeEventListener("pointerleave",f)}}},[c,u,g,C]),n.useEffect(()=>{if(i){const w=f=>{const N=f.target,b={x:f.clientX,y:f.clientY},_=c?.contains(N)||u?.contains(N),E=!vt(b,i);_?C():E&&(C(),h())};return document.addEventListener("pointermove",w),()=>document.removeEventListener("pointermove",w)}},[c,u,i,h,C]),e.jsx(Ee,{...r,ref:d})}),[ut,pt]=Y(U,{isInside:!1}),xt=it("TooltipContent"),Ee=n.forwardRef((r,a)=>{const{__scopeTooltip:t,children:o,"aria-label":l,onEscapeKeyDown:d,onPointerDownOutside:i,...v}=r,c=J($,t),h=X(t),{onClose:u}=c;return n.useEffect(()=>(document.addEventListener(re,u),()=>document.removeEventListener(re,u)),[u]),n.useEffect(()=>{if(c.trigger){const x=C=>{C.target?.contains(c.trigger)&&u()};return window.addEventListener("scroll",x,{capture:!0}),()=>window.removeEventListener("scroll",x,{capture:!0})}},[c.trigger,u]),e.jsx(Ue,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:d,onPointerDownOutside:i,onFocusOutside:x=>x.preventDefault(),onDismiss:u,children:e.jsxs(We,{"data-state":c.stateAttribute,...h,...v,ref:a,style:{...v.style,"--radix-tooltip-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-tooltip-content-available-width":"var(--radix-popper-available-width)","--radix-tooltip-content-available-height":"var(--radix-popper-available-height)","--radix-tooltip-trigger-width":"var(--radix-popper-anchor-width)","--radix-tooltip-trigger-height":"var(--radix-popper-anchor-height)"},children:[e.jsx(xt,{children:o}),e.jsx(ut,{scope:t,isInside:!0,children:e.jsx(Je,{id:c.contentId,role:"tooltip",children:l||o})})]})})});Se.displayName=$;var Re="TooltipArrow",ft=n.forwardRef((r,a)=>{const{__scopeTooltip:t,...o}=r,l=X(t);return pt(Re,t).isInside?null:e.jsx(Ye,{...l,...o,ref:a})});ft.displayName=Re;function jt(r,a){const t=Math.abs(a.top-r.y),o=Math.abs(a.bottom-r.y),l=Math.abs(a.right-r.x),d=Math.abs(a.left-r.x);switch(Math.min(t,o,l,d)){case d:return"left";case l:return"right";case t:return"top";case o:return"bottom";default:throw new Error("unreachable")}}function gt(r,a,t=5){const o=[];switch(a){case"top":o.push({x:r.x-t,y:r.y+t},{x:r.x+t,y:r.y+t});break;case"bottom":o.push({x:r.x-t,y:r.y-t},{x:r.x+t,y:r.y-t});break;case"left":o.push({x:r.x+t,y:r.y-t},{x:r.x+t,y:r.y+t});break;case"right":o.push({x:r.x-t,y:r.y-t},{x:r.x-t,y:r.y+t});break}return o}function yt(r){const{top:a,right:t,bottom:o,left:l}=r;return[{x:l,y:a},{x:t,y:a},{x:t,y:o},{x:l,y:o}]}function vt(r,a){const{x:t,y:o}=r;let l=!1;for(let d=0,i=a.length-1;d<a.length;i=d++){const v=a[d],c=a[i],h=v.x,u=v.y,x=c.x,C=c.y;u>o!=C>o&&t<(x-h)*(o-u)/(C-u)+h&&(l=!l)}return l}function Ct(r){const a=r.slice();return a.sort((t,o)=>t.x<o.x?-1:t.x>o.x?1:t.y<o.y?-1:t.y>o.y?1:0),bt(a)}function bt(r){if(r.length<=1)return r.slice();const a=[];for(let o=0;o<r.length;o++){const l=r[o];for(;a.length>=2;){const d=a[a.length-1],i=a[a.length-2];if((d.x-i.x)*(l.y-i.y)>=(d.y-i.y)*(l.x-i.x))a.pop();else break}a.push(l)}a.pop();const t=[];for(let o=r.length-1;o>=0;o--){const l=r[o];for(;t.length>=2;){const d=t[t.length-1],i=t[t.length-2];if((d.x-i.x)*(l.y-i.y)>=(d.y-i.y)*(l.x-i.x))t.pop();else break}t.push(l)}return t.pop(),a.length===1&&t.length===1&&a[0].x===t[0].x&&a[0].y===t[0].y?a:a.concat(t)}var wt=Ne,Nt=Te,Tt=_e,Pe=Se;const _t=wt,D=Nt,A=Tt,P=n.forwardRef(({className:r,sideOffset:a=4,...t},o)=>e.jsx(Pe,{ref:o,sideOffset:a,className:Xe("z-[100] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",r),...t}));P.displayName=Pe.displayName;function Ht(){const[r,a]=n.useState(""),[t,o]=n.useState(null),[l,d]=n.useState(0),[i,v]=n.useState(null),c=rt(),h=c?void 0:"Table not connected",u=ee(s=>s.status?.speed??null),x=ee(s=>s.status?.current_theta??null),C=ee(s=>(s.status?.is_running||s.status?.is_paused)??!1);n.useEffect(()=>{u!==null&&o(u)},[u]),n.useEffect(()=>{x!==null&&d(x)},[x]);const[g,w]=n.useState([]),[f,N]=n.useState(""),[b,_]=n.useState(!1),[E,L]=n.useState(""),[R,I]=n.useState([]),[T,O]=n.useState(!1),W=n.useRef(null),ie=n.useRef(null),k=async(s,p,y)=>{v(s);try{const M=await H.post(p,y);if(M.success!==!1)return{success:!0,data:M};throw new Error(M.detail||"Action failed")}catch(M){throw console.error(`Error with ${s}:`,M),M}finally{v(null)}},Q=s=>C?(m.error(`Cannot ${s} while a pattern is running. Stop the pattern first.`,{action:{label:"Stop",onClick:()=>oe()}}),!0):!1,ke=async()=>{try{await k("home","/send_home"),m.success("Moving to home position...")}catch{m.error("Failed to move to home position")}},oe=async()=>{try{await k("stop","/stop_execution"),m.success("Execution stopped")}catch{try{await k("stop","/force_stop"),m.success("Force stopped")}catch{m.error("Failed to stop execution")}}},De=async()=>{try{await k("reset","/soft_reset"),m.success("Reset sent. Please home the table.")}catch{m.error("Failed to send reset command")}},Ae=async()=>{if(!Q("move to center"))try{await k("center","/move_to_center"),m.success("Moving to center...")}catch{m.error("Failed to move to center")}},Le=async()=>{if(!Q("move to perimeter"))try{await k("perimeter","/move_to_perimeter"),m.success("Moving to perimeter...")}catch{m.error("Failed to move to perimeter")}},le=async()=>{const s=parseFloat(r);if(isNaN(s)){m.error("Please enter a valid speed value");return}if(s<10){m.error("Speed must be at least 10 mm/s");return}try{await k("speed","/set_speed",{speed:s}),o(s),m.success(`Speed set to ${s} mm/s`),a("")}catch{m.error("Failed to set speed")}},Z=async(s,p)=>{try{await k(s,"/run_theta_rho",{file_name:s,pre_execution:"none"}),m.success(`Running ${p}...`)}catch(y){y instanceof Error&&y.message.includes("409")?m.error("Another pattern is already running"):m.error(`Failed to run ${p}`)}},ce=async s=>{if(!Q("align"))try{const p=s*(Math.PI/180),y=l+p;await k("rotate","/send_coordinate",{theta:y,rho:1}),d(y),m.info(`Rotated ${s}°`)}catch{m.error("Failed to rotate")}},de=async()=>{try{const s=await H.get("/list_serial_ports");w(Array.isArray(s)?s:[])}catch{m.error("Failed to fetch serial ports")}},Oe=async()=>{try{const s=await H.get("/list_serial_ports"),p=Array.isArray(s)?s:[],y=await H.get("/serial_status");y.connected&&y.port&&(p.includes(y.port)?N(y.port):console.warn(`Port ${y.port} from status not in available ports, ignoring`))}catch{}},Me=async(s=!1)=>{if(!f){s||m.error("Please select a serial port");return}O(!0);try{await H.post("/api/debug-serial/open",{port:f}),_(!0),S("resp",`Connected to ${f}`),s||m.success(`Connected to ${f}`)}catch(p){const y=p instanceof Error?p.message:"Unknown error";S("error",`Failed to connect: ${y}`),s||m.error("Failed to connect to serial port")}finally{O(!1)}},He=async()=>{O(!0);try{await H.post("/api/debug-serial/close",{port:f}),_(!1),S("resp","Disconnected"),m.success("Disconnected from serial port")}catch{m.error("Failed to disconnect")}finally{O(!1)}},S=(s,p)=>{const y=new Date().toLocaleTimeString();I(M=>[...M.slice(-200),{type:s,text:p,time:y}]),setTimeout(()=>{W.current&&(W.current.scrollTop=W.current.scrollHeight)},10)},he=async()=>{if(!E.trim()||!b||T)return;const s=E.trim();L(""),O(!0),S("cmd",s);try{const p=await H.post("/api/debug-serial/send",{port:f,command:s});p.responses?p.responses.length>0?p.responses.forEach(y=>S("resp",y)):S("resp","(no response)"):p.detail&&S("error",p.detail||"Command failed")}catch(p){S("error",`Error: ${p}`)}finally{O(!1),setTimeout(()=>ie.current?.focus(),0)}},Ie=async()=>{if(!(!b||T)){O(!0),S("cmd","[Soft Reset]");try{const s=await H.post("/api/debug-serial/send",{port:f,command:""});s.responses&&s.responses.length>0?s.responses.forEach(p=>S("resp",p)):S("resp","Reset sent"),m.success("Reset command sent")}catch(s){S("error",`Reset failed: ${s}`),m.error("Failed to send reset")}finally{O(!1)}}},Fe=s=>{s.key==="Enter"&&!s.shiftKey&&(s.preventDefault(),T||he())};return n.useEffect(()=>{de(),Oe()},[]),e.jsx(_t,{children:e.jsxs("div",{className:"flex flex-col w-full max-w-5xl mx-auto gap-6 py-3 sm:py-6 px-0 sm:px-4",children:[e.jsxs("div",{className:"space-y-0.5 sm:space-y-1 pl-1",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-tight",children:"Table Control"}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Manual controls for your sand table"})]}),e.jsx(at,{}),e.jsx(me,{}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-6",children:[e.jsxs(z,{className:"transition-all duration-200 hover:shadow-md hover:border-primary/20",children:[e.jsxs(B,{className:"pb-3",children:[e.jsx(K,{className:"text-lg",children:"Primary Actions"}),e.jsx(V,{children:"Calibrate or stop the table"})]}),e.jsx(q,{children:e.jsxs("div",{className:"grid grid-cols-3 gap-3",children:[e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:ke,disabled:i==="home"||!c,title:h,variant:"primary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="home"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"home"}),e.jsx("span",{className:"text-xs",children:"Home"})]})}),e.jsx(P,{children:"Return to home position"})]}),e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:oe,disabled:i==="stop",variant:"destructive",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="stop"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"stop_circle"}),e.jsx("span",{className:"text-xs",children:"Stop"})]})}),e.jsx(P,{children:"Gracefully stop"})]}),e.jsxs(ue,{children:[e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsx(G,{asChild:!0,children:e.jsxs(j,{disabled:i==="reset"||!c,title:h,variant:"secondary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="reset"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"restart_alt"}),e.jsx("span",{className:"text-xs",children:"Reset"})]})})}),e.jsx(P,{children:"Send soft reset to controller"})]}),e.jsxs(pe,{className:"sm:max-w-md",children:[e.jsxs(xe,{children:[e.jsx(fe,{children:"Reset Controller?"}),e.jsx(je,{children:"This will send a soft reset to the controller."})]}),e.jsxs(te,{className:"flex items-center border-amber-500/50",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-amber-500 text-base mr-2 shrink-0",children:"warning"}),e.jsx(se,{className:"text-amber-600 dark:text-amber-400",children:"Homing is required after resetting. The table will lose its position reference."})]}),e.jsxs(ge,{className:"gap-2 sm:gap-0",children:[e.jsx(G,{asChild:!0,children:e.jsx(j,{variant:"outline",children:"Cancel"})}),e.jsx(G,{asChild:!0,children:e.jsx(j,{variant:"destructive",onClick:De,children:"Reset Controller"})})]})]})]})]})})]}),e.jsxs(z,{className:"transition-all duration-200 hover:shadow-md hover:border-primary/20",children:[e.jsx(B,{className:"pb-3",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx(K,{className:"text-lg",children:"Speed"}),e.jsx(V,{children:"Ball movement speed"})]}),e.jsx(ye,{variant:"secondary",className:"font-mono",children:t!==null?`${t} mm/s`:"-- mm/s"})]})}),e.jsxs(q,{children:[e.jsxs("div",{className:"flex gap-2",children:[e.jsx(ve,{type:"number",value:r,onChange:s=>a(s.target.value),placeholder:"mm/s",min:"10",max:"2000",step:"1",className:"flex-1",onKeyDown:s=>s.key==="Enter"&&le()}),e.jsxs(j,{onClick:le,disabled:i==="speed"||!r||!c,title:h,className:"gap-2",children:[i==="speed"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined",children:"check"}),"Set"]})]}),e.jsx("p",{className:"mt-2 text-xs text-muted-foreground",children:"10–2000 mm/s"})]})]}),e.jsxs(z,{className:"transition-all duration-200 hover:shadow-md hover:border-primary/20",children:[e.jsxs(B,{className:"pb-3",children:[e.jsx(K,{className:"text-lg",children:"Position"}),e.jsx(V,{children:"Move ball to a specific location"})]}),e.jsx(q,{children:e.jsxs("div",{className:"grid grid-cols-3 gap-3",children:[e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:Ae,disabled:i==="center"||!c,title:h,variant:"secondary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="center"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"center_focus_strong"}),e.jsx("span",{className:"text-xs",children:"Center"})]})}),e.jsx(P,{children:"Move ball to center"})]}),e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:Le,disabled:i==="perimeter"||!c,title:h,variant:"secondary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="perimeter"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"trip_origin"}),e.jsx("span",{className:"text-xs",children:"Perimeter"})]})}),e.jsx(P,{children:"Move ball to edge"})]}),e.jsxs(ue,{children:[e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsx(G,{asChild:!0,children:e.jsxs(j,{variant:"secondary",disabled:!c,title:h,className:"h-16 gap-1 flex-col items-center justify-center",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"screen_rotation"}),e.jsx("span",{className:"text-xs",children:"Align"})]})})}),e.jsx(P,{children:"Align pattern orientation"})]}),e.jsxs(pe,{className:"sm:max-w-md",children:[e.jsxs(xe,{children:[e.jsx(fe,{children:"Pattern Orientation Alignment"}),e.jsx(je,{children:"Follow these steps to align your patterns with their previews"})]}),e.jsxs("div",{className:"space-y-4 py-4",children:[e.jsx("ol",{className:"space-y-3 text-sm",children:['Home the table then select move to perimeter. Look at your pattern preview and decide where the "bottom" should be.',"Manually move the radial arm or use the rotation buttons below to point 90° to the right of where you want the pattern bottom.",'Click the "Home" button to establish this as the reference position.',"All patterns will now be oriented according to their previews!"].map((s,p)=>e.jsxs("li",{className:"flex gap-3",children:[e.jsx(ye,{variant:"secondary",className:"h-6 w-6 shrink-0 items-center justify-center rounded-full p-0",children:p+1}),e.jsx("span",{className:"text-muted-foreground",children:s})]},p))}),e.jsx(me,{}),e.jsxs(te,{className:"flex items-start border-amber-500/50",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-amber-500 text-base mr-2 shrink-0",children:"warning"}),e.jsx(se,{className:"text-amber-600 dark:text-amber-400",children:"Only perform this when you want to change the orientation reference."})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-sm font-medium text-center",children:"Fine Adjustment"}),e.jsxs("div",{className:"flex justify-center gap-2",children:[e.jsxs(j,{variant:"secondary",onClick:()=>ce(-10),disabled:i==="rotate"||!c,title:h,children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons text-lg mr-1",children:"rotate_left"}),"CCW 10°"]}),e.jsxs(j,{variant:"secondary",onClick:()=>ce(10),disabled:i==="rotate"||!c,title:h,children:["CW 10°",e.jsx("span",{"aria-hidden":"true",className:"material-icons text-lg ml-1",children:"rotate_right"})]})]}),e.jsx("p",{className:"text-xs text-muted-foreground text-center",children:"Each click rotates 10 degrees"})]})]}),e.jsx(ge,{children:e.jsx(G,{asChild:!0,children:e.jsx(j,{children:"Got it"})})})]})]})]})})]}),e.jsxs(z,{className:"transition-all duration-200 hover:shadow-md hover:border-primary/20",children:[e.jsxs(B,{className:"pb-3",children:[e.jsx(K,{className:"text-lg",children:"Clear Sand"}),e.jsx(V,{children:"Erase current pattern from the table"})]}),e.jsx(q,{children:e.jsxs("div",{className:"grid grid-cols-3 gap-3",children:[e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:()=>Z("clear_from_in.thr","clear from center"),disabled:i==="clear_from_in.thr"||!c,title:h,variant:"secondary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="clear_from_in.thr"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"center_focus_strong"}),e.jsx("span",{className:"text-xs",children:"Clear Center"})]})}),e.jsx(P,{children:"Spiral outward from center"})]}),e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:()=>Z("clear_from_out.thr","clear from perimeter"),disabled:i==="clear_from_out.thr"||!c,title:h,variant:"secondary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="clear_from_out.thr"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"all_out"}),e.jsx("span",{className:"text-xs",children:"Clear Edge"})]})}),e.jsx(P,{children:"Spiral inward from edge"})]}),e.jsxs(D,{children:[e.jsx(A,{asChild:!0,children:e.jsxs(j,{onClick:()=>Z("clear_sideway.thr","clear sideways"),disabled:i==="clear_sideway.thr"||!c,title:h,variant:"secondary",className:"h-16 gap-1 flex-col items-center justify-center",children:[i==="clear_sideway.thr"?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin text-2xl",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-2xl",children:"swap_horiz"}),e.jsx("span",{className:"text-xs",children:"Clear Sideways"})]})}),e.jsx(P,{children:"Clear with side-to-side motion"})]})]})})]})]}),e.jsxs(z,{className:"transition-all duration-200 hover:shadow-md hover:border-primary/20",children:[e.jsxs(B,{className:"pb-3 space-y-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-2",children:[e.jsxs("div",{className:"min-w-0 space-y-2",children:[e.jsxs(K,{className:"text-lg flex items-center gap-2",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-xl",children:"terminal"}),"Serial Terminal"]}),e.jsx(V,{className:"hidden sm:block",children:"Send raw commands to the table controller"}),e.jsxs(te,{className:"flex items-center border-amber-500/50 py-2",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined text-amber-500 text-base mr-2 shrink-0",children:"warning"}),e.jsx(se,{className:"text-xs text-amber-600 dark:text-amber-400",children:"Do not use while a pattern is running. This will interfere with the main connection."})]})]}),e.jsx("div",{className:"hidden sm:flex items-center gap-1",children:R.length>0&&e.jsx(j,{variant:"ghost",size:"icon",onClick:()=>I([]),title:"Clear history","aria-label":"Clear history",children:e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined",children:"delete_sweep"})})})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs(Qe,{value:f,onValueChange:N,onOpenChange:s=>s&&de(),disabled:b||T,children:[e.jsx(Ze,{className:"h-9 flex-1 min-w-[180px] max-w-[280px]",children:e.jsx(et,{placeholder:"Select port..."})}),e.jsx(tt,{children:g.map(s=>e.jsx(st,{value:s,children:s},s))})]}),b?e.jsxs(e.Fragment,{children:[e.jsxs(j,{size:"sm",variant:"destructive",onClick:He,disabled:T,title:"Disconnect",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined sm:mr-1",children:"power_off"}),e.jsx("span",{className:"hidden sm:inline",children:"Disconnect"})]}),e.jsxs(j,{size:"sm",variant:"secondary",onClick:Ie,disabled:T,title:"Send soft reset to controller",children:[e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined sm:mr-1",children:"restart_alt"}),e.jsx("span",{className:"hidden sm:inline",children:"Reset"})]})]}):e.jsxs(j,{size:"sm",onClick:()=>Me(),disabled:!f||T,title:"Connect",children:[T?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin sm:mr-1",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined sm:mr-1",children:"power"}),e.jsx("span",{className:"hidden sm:inline",children:"Connect"})]}),R.length>0&&e.jsx(j,{variant:"ghost",size:"icon",className:"sm:hidden",onClick:()=>I([]),title:"Clear history","aria-label":"Clear history",children:e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined",children:"delete"})})]})]}),e.jsxs(q,{children:[e.jsx("div",{ref:W,className:"bg-black/90 rounded-md p-3 h-48 overflow-y-auto font-mono text-sm mb-3",children:R.length>0?R.map((s,p)=>e.jsxs("div",{className:`${s.type==="cmd"?"text-cyan-400":s.type==="error"?"text-red-400":"text-green-400"}`,children:[e.jsx("span",{className:"text-gray-500 text-xs mr-2",children:s.time}),s.type==="cmd"?"> ":"",s.text]},p)):e.jsx("div",{className:"text-gray-500 italic",children:b?"Ready. Enter a command below (e.g., $, $$, ?, $H)":"Connect to a serial port to send commands"})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(ve,{ref:ie,value:E,onChange:s=>L(s.target.value),onKeyDown:Fe,disabled:!b,readOnly:T,placeholder:b?"Enter command (e.g., $, $$, ?, $H)":"Connect to send commands",className:"font-mono text-base h-11"}),e.jsx(j,{onClick:he,disabled:!b||!E.trim()||T,"aria-label":"Send command",className:"h-11 px-6",children:T?e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined animate-spin",children:"sync"}):e.jsx("span",{"aria-hidden":"true",className:"material-icons-outlined",children:"send"})})]})]})]})]})})}export{Ht as TableControlPage};