@adcops/autocore-react 3.3.112 → 3.3.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/components/ams/AmsProvider.d.ts.map +1 -1
  2. package/dist/components/ams/AmsProvider.js +1 -1
  3. package/dist/components/ams-editor/AssetFieldArray.d.ts +21 -0
  4. package/dist/components/ams-editor/AssetFieldArray.d.ts.map +1 -0
  5. package/dist/components/ams-editor/AssetFieldArray.js +1 -0
  6. package/dist/components/ams-editor/AssetTypeEditor.css +122 -0
  7. package/dist/components/ams-editor/AssetTypeEditor.d.ts +26 -0
  8. package/dist/components/ams-editor/AssetTypeEditor.d.ts.map +1 -0
  9. package/dist/components/ams-editor/AssetTypeEditor.js +1 -0
  10. package/dist/components/ams-editor/AssetTypeFormEditor.d.ts +21 -0
  11. package/dist/components/ams-editor/AssetTypeFormEditor.d.ts.map +1 -0
  12. package/dist/components/ams-editor/AssetTypeFormEditor.js +1 -0
  13. package/dist/components/ams-editor/SubLocationsEditor.d.ts +17 -0
  14. package/dist/components/ams-editor/SubLocationsEditor.d.ts.map +1 -0
  15. package/dist/components/ams-editor/SubLocationsEditor.js +1 -0
  16. package/dist/components/ams-editor/index.d.ts +7 -0
  17. package/dist/components/ams-editor/index.d.ts.map +1 -0
  18. package/dist/components/ams-editor/index.js +1 -0
  19. package/dist/components/ams-editor/types.d.ts +34 -0
  20. package/dist/components/ams-editor/types.d.ts.map +1 -0
  21. package/dist/components/ams-editor/types.js +1 -0
  22. package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
  23. package/dist/components/tis/ResultHistoryTable.js +1 -1
  24. package/dist/components/tis/ScienceTable.d.ts +15 -0
  25. package/dist/components/tis/ScienceTable.d.ts.map +1 -1
  26. package/dist/components/tis/ScienceTable.js +1 -1
  27. package/dist/components/tis/TestDataView.d.ts +10 -4
  28. package/dist/components/tis/TestDataView.d.ts.map +1 -1
  29. package/dist/components/tis/TestDataView.js +1 -1
  30. package/dist/components/tis-editor/TisConfigEditor.d.ts +1 -1
  31. package/dist/components/tis-editor/editor/SaveDiffDialog.d.ts.map +1 -1
  32. package/dist/components/tis-editor/editor/SaveDiffDialog.js +1 -1
  33. package/dist/hooks/useAmsSchemaConfig.d.ts +39 -0
  34. package/dist/hooks/useAmsSchemaConfig.d.ts.map +1 -0
  35. package/dist/hooks/useAmsSchemaConfig.js +1 -0
  36. package/dist/hooks/useTisConfig.d.ts +1 -1
  37. package/package.json +110 -110
  38. package/src/components/ams/AmsProvider.tsx +20 -15
  39. package/src/components/ams-editor/AssetFieldArray.tsx +103 -0
  40. package/src/components/ams-editor/AssetTypeEditor.css +122 -0
  41. package/src/components/ams-editor/AssetTypeEditor.tsx +295 -0
  42. package/src/components/ams-editor/AssetTypeFormEditor.tsx +178 -0
  43. package/src/components/ams-editor/SubLocationsEditor.tsx +151 -0
  44. package/src/components/ams-editor/index.ts +16 -0
  45. package/src/components/ams-editor/types.ts +40 -0
  46. package/src/components/tis/ResultHistoryTable.tsx +141 -1
  47. package/src/components/tis/ScienceTable.tsx +71 -12
  48. package/src/components/tis/TestDataView.tsx +65 -6
  49. package/src/components/tis-editor/TisConfigEditor.tsx +1 -1
  50. package/src/components/tis-editor/editor/SaveDiffDialog.tsx +4 -3
  51. package/src/hooks/useAmsSchemaConfig.ts +142 -0
  52. package/src/hooks/useTisConfig.ts +1 -1
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Column}from"primereact/column";import{DataTable}from"primereact/datatable";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{TabView,TabPanel}from"primereact/tabview";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import annotationPlugin from"chartjs-plugin-annotation";import{Line}from"react-chartjs-2";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";import{ScienceTable}from"./ScienceTable";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin,annotationPlugin);export const TestDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,a=e.methodId??t.selection.methodId,n=e.runId??t.selection.runId,l=e.schema??(a?t.schemas[a]:void 0),{throttleMs:o=100,cycleTableHeight:s="400px",chartHeight:i="320px"}=e,{invoke:c,subscribe:d,unsubscribe:u}=useContext(EventEmitterContext),[m,p]=useState(null),[y,f]=useState([]),[h,g]=useState({}),[_,x]=useState({}),[b,j]=useState(!1),[v,w]=useState(!1),C=useRef(null),[S,T]=useState(null),[R,M]=useState(null),[L,I]=useState(!1),[E,O]=useState(null),[A,D]=useState(null),[N,k]=useState(!1),[F,H]=useState([]),[z,P]=useState(null),B=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[V,$]=useState(B.length>0?B[0].name:null);useEffect(()=>{if(0===B.length)return;null!==V&&B.some(e=>e.name===V)||$(B[0].name)},[B,V]);const G=B.find(e=>e.name===V)?.view,W="raw_trace"===G?.type,q=useRef([]),U=useRef(null),J=useRef(null),Y=()=>{J.current||(J.current=setTimeout(()=>{if(J.current=null,q.current.length>0){const e=q.current;q.current=[],f(t=>[...e.slice().reverse(),...t])}U.current&&(g(U.current),U.current=null)},o))};useEffect(()=>{if(!r||!a||!n)return p(null),f([]),void g({});let e=!1;return(async()=>{try{const t=await c("tis.read_test",MessageType.Request,{project_id:r,method_id:a,run_id:n});!e&&t?.success&&(p(t.data),g(t.data.results??{}));const l=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&l?.success&&f(l.data.cycles??[])}catch(e){}})(),()=>{e=!0}},[r,a,n,c]),useEffect(()=>{const e=e=>e?.project_id===r&&e?.method_id===a&&e?.run_id===n,t=d("tis.cycle_added",t=>{e(t)&&t.cycle&&(q.current.push(t.cycle),Y())}),l=d("tis.results_updated",t=>{e(t)&&(U.current=t.results??{},Y())});return()=>{u(t),u(l),J.current&&(clearTimeout(J.current),J.current=null)}},[r,a,n,o]);const Z="string"==typeof m?.sample_id&&m.sample_id?m.sample_id:"string"==typeof m?.config?.sample_id?m.config.sample_id:"";useEffect(()=>{if(!r||!a||!Z)return void x({});let e=!1;(async()=>{try{const t=await c("tis.sample_summary",MessageType.Request,{project_id:r,method_id:a,sample_id:Z});!e&&t?.success&&x(t.data?.summary??{})}catch(e){}})();const t=d("tis.sample_summary_updated",e=>{e?.project_id===r&&e?.method_id===a&&e?.sample_id===Z&&x(e.summary??{})});return()=>{e=!0,u(t)}},[r,a,Z,c,d,u]);const K=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:G?.source??"raw",enabled:W}),Q=useMemo(()=>{if(!G)return null;if("cycle_scatter"===G.type){const e=G.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:G.y.map((e,r)=>({label:e.label??e.field,data:t.map(t=>t[e.field]),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(r),backgroundColor:palette(r),tension:.1,pointRadius:2}))}}if("raw_trace"===G.type){if(!K.raw)return null;const e=G.x.column;if(!e)return null;const t=K.raw[e]??[],r=!1!==G.smooth;return{datasets:G.y.map((e,a)=>{const n=K.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,G.smoothWindow):n.map((e,r)=>({x:t[r],y:e}));return{label:e.label??e.column,data:l,yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(a),backgroundColor:palette(a),pointRadius:0,borderWidth:1.5,showLine:!0,...r?{cubicInterpolationMode:"monotone"}:{}}})}}return null},[G,y,K.raw]),X=G?.y.some(e=>"right"===e.y_axis)??!1,ee=useMemo(()=>{if(!W)return[];const e=K.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[W,K.envelope]),te=useMemo(()=>[...G?.regions??[],...ee],[G,ee]),re=useMemo(()=>{const e="raw_trace"===G?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!G?.x.label,text:G?.x.label}}:{title:{display:!!G?.x.label,text:G?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(G)}},...X?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(G)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(te)}}}},[G,X,te]),ae=l?.raw_data?.blob_name??"trace",ne=useRef(""),le=useCallback(async()=>{if(!r||!a||!n)return[];try{const e=await c("tis.list_raw",MessageType.Request,{project_id:r,method_id:a,run_id:n});if(!e?.success)return[];const t=e.data?.cycles??[];return t.filter(e=>e?.name===ae&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ae,c]),oe=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ae}|${e??"latest"}`;if(ne.current===t)return;ne.current=t;const l={project_id:r,method_id:a,run_id:n,name:ae};null!=e&&(l.cycle_index=e),I(!0),M(null),T(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?T(e.data??{}):M(e?.error_message??"No raw data on disk for this run.")}catch(e){M(String(e?.message??e))}finally{I(!1)}k(!0),D(null),O(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?O(e.data??{}):D(e?.error_message??"No filtered data on disk for this run.")}catch(e){D(String(e?.message??e))}finally{k(!1)}},[r,a,n,ae,c]);useEffect(()=>{ne.current="",H([]),P(null)},[r,a,n,ae]);return useEffect(()=>{b&&null!=z&&oe(z)},[b,z,oe]),r&&a&&n&&l?_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[_jsx(Header,{meta:m,config:m?.config,runId:n,projectId:r,methodId:a,canViewRaw:!!l.raw_data,onViewRaw:async()=>{j(!0);let e=F;0===e.length&&(e=await le(),H(e));const t=e.length>0?e[e.length-1]:null,r=z??t;z!==r&&P(r),await oe(r)},onShowConfig:()=>w(!0)}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",marginBottom:"0.5rem",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:V,options:B.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>$(e.value),placeholder:0===B.length?"No view defined":"Select a view",disabled:0===B.length}),_jsx("h3",{style:{margin:0},children:G?.title??""}),W&&K.cycles.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"chart-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"chart-cycle-picker",value:K.selectedCycle,options:K.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>K.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",K.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>C.current?.resetZoom?.(),disabled:!Q,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[W&&K.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),W&&K.error&&_jsx(ChartOverlay,{children:K.error}),Q&&_jsx(Line,{ref:C,data:Q,options:re})]})]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD,t=l.cycle_fields.map(e=>({field:e.name,label:e.name,units:e.units,body:t=>formatCell(t[e.name],e.type,e.scale)}));return _jsx(ScienceTable,{title:`Cycle Data (${y.length})`,columns:t,rows:y,scrollable:e,scrollHeight:e?s:void 0,emptyMessage:"No cycles yet."})})(),(()=>{const e={...h,..._},t=Object.keys(e).length>0,r=l.results_fields.map(e=>({field:e.name,label:e.name,units:e.units,body:t=>formatCell(t[e.name],e.type,e.scale)}));return _jsx(ScienceTable,{title:"Results",columns:r,rows:t?[e]:[],emptyMessage:"No results yet."})})(),l.raw_data&&_jsxs(Dialog,{visible:b,onHide:()=>j(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:F,selected:z,onChange:P}),_jsx(RawEnvelopeHeader,{envelope:S}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(S),loading:L,error:R,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(E),loading:N,error:A,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:v,onHide:()=>w(!1),header:"Test Configuration",style:{width:"min(640px, 90vw)"},modal:!0,children:_jsx(ConfigList,{config:m?.config})})]}):_jsx("div",{className:"p-card",style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No test selected. Pick a row from the History tab or start a run."})};const Header=({meta:e,config:t,runId:r,projectId:a,methodId:n,canViewRaw:l,onViewRaw:o,onShowConfig:s})=>{const i="string"==typeof e?.sample_id&&e.sample_id||"string"==typeof e?.config?.sample_id&&e.config.sample_id||"",c=t&&"object"==typeof t&&Object.entries(t).some(([e])=>"sample_id"!==e);return _jsx("div",{className:"p-card",style:{padding:"1rem"},children:_jsxs("div",{className:"flex",style:{justifyContent:"space-between",alignItems:"flex-start",gap:"1rem"},children:[_jsxs("div",{children:[_jsxs("h2",{style:{margin:0,display:"flex",alignItems:"center",gap:"0.5rem"},children:[i||r,c&&_jsx(Button,{icon:"pi pi-info-circle",type:"button",rounded:!0,text:!0,onClick:s,tooltip:"Show test configuration",tooltipOptions:{position:"top"},style:{width:"2rem",height:"2rem",padding:0},"aria-label":"Show test configuration"})]}),_jsxs("div",{style:{color:"var(--text-secondary-color)",fontSize:"0.85em"},children:["project: ",a," · method: ",n," · run: ",r,e?.start_time&&_jsxs(_Fragment,{children:[" · started: ",new Date(e.start_time).toLocaleString()]})]})]}),l&&_jsx(Button,{icon:"pi pi-table",label:"View Raw Data",onClick:o,outlined:!0})]})})},ConfigList=({config:e})=>{const t=e&&"object"==typeof e?Object.entries(e).filter(([e])=>"sample_id"!==e):[];return 0===t.length?_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No configuration recorded for this run."}):_jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",fontSize:"0.95em"},children:t.map(([e,t])=>_jsxs(React.Fragment,{children:[_jsx("div",{style:{color:"var(--text-secondary-color)"},children:e}),_jsx("div",{children:formatCell(t,"string")})]},e))})},unwrapEnvelope=e=>e&&"object"==typeof e&&"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e,CyclePickerBar=({cycles:e,selected:t,onChange:r})=>e.length<=1?null:_jsxs("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.25rem 0.5rem 0.5rem"},children:[_jsx("label",{htmlFor:"raw-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"raw-cycle-picker",value:t,options:e.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>r(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["(",e.length," cycles recorded)"]})]}),RawEnvelopeHeader=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,a=e.context;if(null==t&&!r&&!a)return null;const n=e=>{if(!e||"object"!=typeof e)return null;const t=Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e);return 0===t.length?null:t.map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e))};return _jsxs("div",{style:{padding:"0.5rem",borderBottom:"1px solid var(--surface-border)",fontSize:"0.9rem"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(r)}),a&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(a)})]})},DataBlobTable=({blob:e,loading:t,error:r,rawData:a,emptyMessage:n})=>{if(t)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"Loading…"});if(r)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??r});if(!e||"object"!=typeof e)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No data."});const l=Object.keys(a.columns??{}),o=Object.keys(e).filter(t=>Array.isArray(e[t])),s=[];for(const t of l)Array.isArray(e[t])&&s.push(t);for(const e of o)s.includes(e)||s.push(e);if(0===s.length)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??"No columnar data in this blob."});const i=s.reduce((t,r)=>Math.min(t,e[r].length),Number.POSITIVE_INFINITY),c=Number.isFinite(i)?i:0,d=Array.from({length:c},(t,r)=>{const a={__i:r};for(const t of s)a[t]=e[t][r];return a}),u=e=>{const t=a.units?.[e];return t?`${e} [${t}]`:e};return _jsxs(DataTable,{value:d,scrollable:!0,scrollHeight:"60vh",virtualScrollerOptions:{itemSize:32},emptyMessage:n??"No data.",size:"small",stripedRows:!0,children:[_jsx(Column,{field:"__i",header:"#",style:{width:"5rem",textAlign:"right"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"}}),s.map(e=>_jsx(Column,{field:e,header:u(e),style:{minWidth:"8rem"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},body:t=>formatNumeric(t[e])},e))]})},formatNumeric=e=>null==e?"":"number"==typeof e&&Number.isFinite(e)?Number.parseFloat(e.toPrecision(6)).toString():String(e),CYCLE_VIRTUAL_THRESHOLD=30,CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],SG_ORDER=3,SMOOTH_TARGET_POINTS=800;function invertMatrix(e){const t=e.length,r=e.map((e,r)=>[...e,...Array.from({length:t},(e,t)=>r===t?1:0)]);for(let e=0;e<t;e++){let a=e;for(let n=e+1;n<t;n++)Math.abs(r[n][e])>Math.abs(r[a][e])&&(a=n);if(Math.abs(r[a][e])<1e-12)return null;[r[e],r[a]]=[r[a],r[e]];const n=r[e][e];for(let a=0;a<2*t;a++)r[e][a]/=n;for(let a=0;a<t;a++){if(a===e)continue;const n=r[a][e];if(0!==n)for(let l=0;l<2*t;l++)r[a][l]-=n*r[e][l]}}return r.map(e=>e.slice(t))}function sgWeights(e,t){const r=2*e+1,a=Math.min(t,r-1),n=[];for(let t=-e;t<=e;t++){const e=[];let r=1;for(let n=0;n<=a;n++)e.push(r),r*=t;n.push(e)}const l=a+1,o=Array.from({length:l},()=>new Array(l).fill(0));for(let e=0;e<l;e++)for(let t=0;t<l;t++){let a=0;for(let l=0;l<r;l++)a+=n[l][e]*n[l][t];o[e][t]=a}const s=invertMatrix(o);if(!s)return new Array(r).fill(1/r);const i=new Array(r);for(let e=0;e<r;e++){let t=0;for(let r=0;r<l;r++)t+=s[0][r]*n[e][r];i[e]=t}return i}function savitzkyGolay(e,t){const r=e.length;if(0===r||t<1)return e.slice();const a=new Map,n=e=>{let t=a.get(e);return t||(t=sgWeights(e,3),a.set(e,t)),t},l=new Array(r);for(let a=0;a<r;a++){const o=Math.min(t,a,r-1-a);if(o<1){l[a]=e[a];continue}const s=n(o);let i=0;for(let t=-o;t<=o;t++)i+=s[t+o]*e[a+t];l[a]=i}return l}function smoothTraceForDisplay(e,t,r){const a=Math.min(e.length,t.length);if(0===a)return[];const n=r&&r>1?Math.max(1,Math.floor((Math.min(r,a)-1)/2)):Math.max(2,Math.min(40,Math.round(a/250))),l=savitzkyGolay(t.slice(0,a),n),o=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),s=[];for(let t=0;t<a;t+=o)s.push({x:e[t],y:l[t]});return(a-1)%o!=0&&s.push({x:e[a-1],y:l[a-1]}),s}const DEFAULT_REGION_FILL="rgba(78, 168, 222, 0.15)",REGION_LABEL_COLOR="rgba(226, 232, 240, 0.85)";function buildRegionAnnotations(e){if(!e||0===e.length)return{};const t={};return e.forEach((e,r)=>{const a=!!e.borderColor;t[`region-${r}`]={type:"box",xScaleID:"x",xMin:e.xMin,xMax:e.xMax,backgroundColor:e.color??DEFAULT_REGION_FILL,borderColor:a?e.borderColor:"transparent",borderWidth:a?1:0,drawTime:"beforeDatasetsDraw",...e.label?{label:{display:!0,content:e.label,position:{x:"center",y:"start"},color:REGION_LABEL_COLOR,font:{size:11}}}:{}}}),t}const ChartOverlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),seriesLabel=e=>e.label??e.field??e.column??"",leftAxisLabel=e=>e?.y.filter(e=>"right"!==e.y_axis).map(seriesLabel).join(" / ")??"",rightAxisLabel=e=>e?.y.filter(e=>"right"===e.y_axis).map(seriesLabel).join(" / ")??"",formatCell=(e,t,r)=>null==e?"":(r&&1!==r&&"number"==typeof e&&Number.isFinite(e)&&(e*=r),"f32"===t||"f64"===t?"number"==typeof e?e.toFixed(4):String(e):"object"==typeof e?JSON.stringify(e):String(e));
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Column}from"primereact/column";import{DataTable}from"primereact/datatable";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{TabView,TabPanel}from"primereact/tabview";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import annotationPlugin from"chartjs-plugin-annotation";import{Line}from"react-chartjs-2";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";import{ScienceTable}from"./ScienceTable";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin,annotationPlugin);export const TestDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,a=e.methodId??t.selection.methodId,n=e.runId??t.selection.runId,l=e.schema??(a?t.schemas[a]:void 0),{throttleMs:o=100,cycleTableHeight:s="400px",chartHeight:i="320px"}=e,{invoke:c,subscribe:d,unsubscribe:u}=useContext(EventEmitterContext),[m,p]=useState(null),[y,f]=useState([]),[h,g]=useState({}),[_,x]=useState({}),[b,j]=useState(!1),[v,w]=useState(!1),C=useCallback(async(e,t,l)=>{const o=e?.cycle_index;if(r&&a&&n&&o)try{const e=await c("tis.update_cycle",MessageType.Request,{project_id:r,method_id:a,run_id:n,cycle_index:o,fields:{[t]:l}});if(!1===e?.success)return;f(e=>e.map(e=>e?.cycle_index===o?{...e,[t]:l}:e))}catch(e){}},[c,r,a,n]),S=useRef(null),[T,R]=useState(null),[M,L]=useState(null),[E,I]=useState(!1),[O,A]=useState(null),[D,N]=useState(null),[k,F]=useState(!1),[H,z]=useState([]),[P,B]=useState(null),V=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[$,G]=useState(V.length>0?V[0].name:null);useEffect(()=>{if(0===V.length)return;null!==$&&V.some(e=>e.name===$)||G(V[0].name)},[V,$]);const W=V.find(e=>e.name===$)?.view,q="raw_trace"===W?.type,U=useRef([]),J=useRef(null),Y=useRef(null),Z=()=>{Y.current||(Y.current=setTimeout(()=>{if(Y.current=null,U.current.length>0){const e=U.current;U.current=[],f(t=>[...e.slice().reverse(),...t])}J.current&&(g(J.current),J.current=null)},o))};useEffect(()=>{if(!r||!a||!n)return p(null),f([]),void g({});let e=!1;return(async()=>{try{const t=await c("tis.read_test",MessageType.Request,{project_id:r,method_id:a,run_id:n});!e&&t?.success&&(p(t.data),g(t.data.results??{}));const l=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&l?.success&&f(l.data.cycles??[])}catch(e){}})(),()=>{e=!0}},[r,a,n,c]),useEffect(()=>{const e=e=>e?.project_id===r&&e?.method_id===a&&e?.run_id===n,t=d("tis.cycle_added",t=>{e(t)&&t.cycle&&(U.current.push(t.cycle),Z())}),l=d("tis.results_updated",t=>{e(t)&&(J.current=t.results??{},Z())});return()=>{u(t),u(l),Y.current&&(clearTimeout(Y.current),Y.current=null)}},[r,a,n,o]);const K="string"==typeof m?.sample_id&&m.sample_id?m.sample_id:"string"==typeof m?.config?.sample_id?m.config.sample_id:"";useEffect(()=>{if(!r||!a||!K)return void x({});let e=!1;(async()=>{try{const t=await c("tis.sample_summary",MessageType.Request,{project_id:r,method_id:a,sample_id:K});!e&&t?.success&&x(t.data?.summary??{})}catch(e){}})();const t=d("tis.sample_summary_updated",e=>{e?.project_id===r&&e?.method_id===a&&e?.sample_id===K&&x(e.summary??{})});return()=>{e=!0,u(t)}},[r,a,K,c,d,u]);const Q=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:W?.source??"raw",enabled:q}),X=useMemo(()=>{if(!W)return null;if("cycle_scatter"===W.type){const e=W.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:W.y.map((e,r)=>({label:e.label??e.field,data:t.map(t=>t[e.field]),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(r),backgroundColor:palette(r),tension:.1,pointRadius:2}))}}if("raw_trace"===W.type){if(!Q.raw)return null;const e=W.x.column;if(!e)return null;const t=Q.raw[e]??[],r=!1!==W.smooth;return{datasets:W.y.map((e,a)=>{const n=Q.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,W.smoothWindow):n.map((e,r)=>({x:t[r],y:e}));return{label:e.label??e.column,data:l,yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(a),backgroundColor:palette(a),pointRadius:0,borderWidth:1.5,showLine:!0,...r?{cubicInterpolationMode:"monotone"}:{}}})}}return null},[W,y,Q.raw]),ee=W?.y.some(e=>"right"===e.y_axis)??!1,te=useMemo(()=>{if(!q)return[];const e=Q.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[q,Q.envelope]),re=useMemo(()=>[...W?.regions??[],...te],[W,te]),ae=useMemo(()=>{const e="raw_trace"===W?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!W?.x.label,text:W?.x.label}}:{title:{display:!!W?.x.label,text:W?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(W)}},...ee?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(W)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(re)}}}},[W,ee,re]),ne=l?.raw_data?.blob_name??"trace",le=useRef(""),oe=useCallback(async()=>{if(!r||!a||!n)return[];try{const e=await c("tis.list_raw",MessageType.Request,{project_id:r,method_id:a,run_id:n});if(!e?.success)return[];const t=e.data?.cycles??[];return t.filter(e=>e?.name===ne&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ne,c]),se=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ne}|${e??"latest"}`;if(le.current===t)return;le.current=t;const l={project_id:r,method_id:a,run_id:n,name:ne};null!=e&&(l.cycle_index=e),I(!0),L(null),R(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?R(e.data??{}):L(e?.error_message??"No raw data on disk for this run.")}catch(e){L(String(e?.message??e))}finally{I(!1)}F(!0),N(null),A(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?A(e.data??{}):N(e?.error_message??"No filtered data on disk for this run.")}catch(e){N(String(e?.message??e))}finally{F(!1)}},[r,a,n,ne,c]);useEffect(()=>{le.current="",z([]),B(null)},[r,a,n,ne]);return useEffect(()=>{b&&null!=P&&se(P)},[b,P,se]),r&&a&&n&&l?_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[_jsx(Header,{meta:m,config:m?.config,runId:n,projectId:r,methodId:a,canViewRaw:!!l.raw_data,onViewRaw:async()=>{j(!0);let e=H;0===e.length&&(e=await oe(),z(e));const t=e.length>0?e[e.length-1]:null,r=P??t;P!==r&&B(r),await se(r)},onShowConfig:()=>w(!0)}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",marginBottom:"0.5rem",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:$,options:V.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>G(e.value),placeholder:0===V.length?"No view defined":"Select a view",disabled:0===V.length}),_jsx("h3",{style:{margin:0},children:W?.title??""}),q&&Q.cycles.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"chart-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"chart-cycle-picker",value:Q.selectedCycle,options:Q.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>Q.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",Q.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>S.current?.resetZoom?.(),disabled:!X,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[q&&Q.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),q&&Q.error&&_jsx(ChartOverlay,{children:Q.error}),X&&_jsx(Line,{ref:S,data:X,options:ae})]})]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD,t=(l.cycle_fields??[]).map(e=>({field:e.name,label:e.name,units:e.units,editable:e.editable,body:t=>formatCell(t[e.name],e.type,e.scale)}));return _jsx(ScienceTable,{title:`Cycle Data (${y.length})`,columns:t,rows:y,scrollable:e,scrollHeight:e?s:void 0,emptyMessage:"No cycles yet.",onCellEdit:C})})(),(()=>{const e={...h,..._},t=Object.keys(e).length>0,r=(l.results_fields??[]).map(e=>({field:e.name,label:e.name,units:e.units,body:t=>formatCell(t[e.name],e.type,e.scale)}));return _jsx(ScienceTable,{title:"Results",columns:r,rows:t?[e]:[],emptyMessage:"No results yet."})})(),l.raw_data&&_jsxs(Dialog,{visible:b,onHide:()=>j(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:H,selected:P,onChange:B}),_jsx(RawEnvelopeHeader,{envelope:T}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(T),loading:E,error:M,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(O),loading:k,error:D,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:v,onHide:()=>w(!1),header:"Test Configuration",style:{width:"min(640px, 90vw)"},modal:!0,children:_jsx(ConfigList,{config:m?.config})})]}):_jsx("div",{className:"p-card",style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No test selected. Pick a row from the History tab or start a run."})};const Header=({meta:e,config:t,runId:r,projectId:a,methodId:n,canViewRaw:l,onViewRaw:o,onShowConfig:s})=>{const i="string"==typeof e?.sample_id&&e.sample_id||"string"==typeof e?.config?.sample_id&&e.config.sample_id||"",c=t&&"object"==typeof t&&Object.entries(t).some(([e])=>"sample_id"!==e);return _jsx("div",{className:"p-card",style:{padding:"1rem"},children:_jsxs("div",{className:"flex",style:{justifyContent:"space-between",alignItems:"flex-start",gap:"1rem"},children:[_jsxs("div",{children:[_jsxs("h2",{style:{margin:0,display:"flex",alignItems:"center",gap:"0.5rem"},children:[i||r,c&&_jsx(Button,{icon:"pi pi-info-circle",type:"button",rounded:!0,text:!0,onClick:s,tooltip:"Show test configuration",tooltipOptions:{position:"top"},style:{width:"2rem",height:"2rem",padding:0},"aria-label":"Show test configuration"})]}),_jsxs("div",{style:{color:"var(--text-secondary-color)",fontSize:"0.85em"},children:["project: ",a," · method: ",n," · run: ",r,e?.start_time&&_jsxs(_Fragment,{children:[" · started: ",new Date(e.start_time).toLocaleString()]})]})]}),l&&_jsx(Button,{icon:"pi pi-table",label:"View Raw Data",onClick:o,outlined:!0})]})})},ConfigList=({config:e})=>{const t=e&&"object"==typeof e?Object.entries(e).filter(([e])=>"sample_id"!==e):[];return 0===t.length?_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No configuration recorded for this run."}):_jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",fontSize:"0.95em"},children:t.map(([e,t])=>_jsxs(React.Fragment,{children:[_jsx("div",{style:{color:"var(--text-secondary-color)"},children:e}),_jsx("div",{children:formatCell(t,"string")})]},e))})},unwrapEnvelope=e=>e&&"object"==typeof e&&"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e,CyclePickerBar=({cycles:e,selected:t,onChange:r})=>e.length<=1?null:_jsxs("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.25rem 0.5rem 0.5rem"},children:[_jsx("label",{htmlFor:"raw-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"raw-cycle-picker",value:t,options:e.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>r(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["(",e.length," cycles recorded)"]})]}),RawEnvelopeHeader=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,a=e.context;if(null==t&&!r&&!a)return null;const n=e=>{if(!e||"object"!=typeof e)return null;const t=Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e);return 0===t.length?null:t.map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e))};return _jsxs("div",{style:{padding:"0.5rem",borderBottom:"1px solid var(--surface-border)",fontSize:"0.9rem"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(r)}),a&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(a)})]})},DataBlobTable=({blob:e,loading:t,error:r,rawData:a,emptyMessage:n})=>{if(t)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"Loading…"});if(r)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??r});if(!e||"object"!=typeof e)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No data."});const l=Object.keys(a.columns??{}),o=Object.keys(e).filter(t=>Array.isArray(e[t])),s=[];for(const t of l)Array.isArray(e[t])&&s.push(t);for(const e of o)s.includes(e)||s.push(e);if(0===s.length)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??"No columnar data in this blob."});const i=s.reduce((t,r)=>Math.min(t,e[r].length),Number.POSITIVE_INFINITY),c=Number.isFinite(i)?i:0,d=Array.from({length:c},(t,r)=>{const a={__i:r};for(const t of s)a[t]=e[t][r];return a}),u=e=>{const t=a.units?.[e];return t?`${e} [${t}]`:e};return _jsxs(DataTable,{value:d,scrollable:!0,scrollHeight:"60vh",virtualScrollerOptions:{itemSize:32},emptyMessage:n??"No data.",size:"small",stripedRows:!0,children:[_jsx(Column,{field:"__i",header:"#",style:{width:"5rem",textAlign:"right"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"}}),s.map(e=>_jsx(Column,{field:e,header:u(e),style:{minWidth:"8rem"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},body:t=>formatNumeric(t[e])},e))]})},formatNumeric=e=>null==e?"":"number"==typeof e&&Number.isFinite(e)?Number.parseFloat(e.toPrecision(6)).toString():String(e),CYCLE_VIRTUAL_THRESHOLD=30,CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],SG_ORDER=3,SMOOTH_TARGET_POINTS=800;function invertMatrix(e){const t=e.length,r=e.map((e,r)=>[...e,...Array.from({length:t},(e,t)=>r===t?1:0)]);for(let e=0;e<t;e++){let a=e;for(let n=e+1;n<t;n++)Math.abs(r[n][e])>Math.abs(r[a][e])&&(a=n);if(Math.abs(r[a][e])<1e-12)return null;[r[e],r[a]]=[r[a],r[e]];const n=r[e][e];for(let a=0;a<2*t;a++)r[e][a]/=n;for(let a=0;a<t;a++){if(a===e)continue;const n=r[a][e];if(0!==n)for(let l=0;l<2*t;l++)r[a][l]-=n*r[e][l]}}return r.map(e=>e.slice(t))}function sgWeights(e,t){const r=2*e+1,a=Math.min(t,r-1),n=[];for(let t=-e;t<=e;t++){const e=[];let r=1;for(let n=0;n<=a;n++)e.push(r),r*=t;n.push(e)}const l=a+1,o=Array.from({length:l},()=>new Array(l).fill(0));for(let e=0;e<l;e++)for(let t=0;t<l;t++){let a=0;for(let l=0;l<r;l++)a+=n[l][e]*n[l][t];o[e][t]=a}const s=invertMatrix(o);if(!s)return new Array(r).fill(1/r);const i=new Array(r);for(let e=0;e<r;e++){let t=0;for(let r=0;r<l;r++)t+=s[0][r]*n[e][r];i[e]=t}return i}function savitzkyGolay(e,t){const r=e.length;if(0===r||t<1)return e.slice();const a=new Map,n=e=>{let t=a.get(e);return t||(t=sgWeights(e,3),a.set(e,t)),t},l=new Array(r);for(let a=0;a<r;a++){const o=Math.min(t,a,r-1-a);if(o<1){l[a]=e[a];continue}const s=n(o);let i=0;for(let t=-o;t<=o;t++)i+=s[t+o]*e[a+t];l[a]=i}return l}function smoothTraceForDisplay(e,t,r){const a=Math.min(e.length,t.length);if(0===a)return[];const n=r&&r>1?Math.max(1,Math.floor((Math.min(r,a)-1)/2)):Math.max(2,Math.min(40,Math.round(a/250))),l=savitzkyGolay(t.slice(0,a),n),o=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),s=[];for(let t=0;t<a;t+=o)s.push({x:e[t],y:l[t]});return(a-1)%o!=0&&s.push({x:e[a-1],y:l[a-1]}),s}const DEFAULT_REGION_FILL="rgba(78, 168, 222, 0.15)",REGION_LABEL_COLOR="rgba(226, 232, 240, 0.85)";function buildRegionAnnotations(e){if(!e||0===e.length)return{};const t={};return e.forEach((e,r)=>{const a=!!e.borderColor;t[`region-${r}`]={type:"box",xScaleID:"x",xMin:e.xMin,xMax:e.xMax,backgroundColor:e.color??DEFAULT_REGION_FILL,borderColor:a?e.borderColor:"transparent",borderWidth:a?1:0,drawTime:"beforeDatasetsDraw",...e.label?{label:{display:!0,content:e.label,position:{x:"center",y:"start"},color:REGION_LABEL_COLOR,font:{size:11}}}:{}}}),t}const ChartOverlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),seriesLabel=e=>e.label??e.field??e.column??"",leftAxisLabel=e=>e?.y.filter(e=>"right"!==e.y_axis).map(seriesLabel).join(" / ")??"",rightAxisLabel=e=>e?.y.filter(e=>"right"===e.y_axis).map(seriesLabel).join(" / ")??"",formatCell=(e,t,r)=>null==e?"":(r&&1!==r&&"number"==typeof e&&Number.isFinite(e)&&(e*=r),"f32"===t||"f64"===t?"number"==typeof e?e.toFixed(4):String(e):"object"==typeof e?JSON.stringify(e):String(e));
@@ -7,7 +7,7 @@
7
7
  * Action bar: New / Duplicate / Delete / Apply / Save / Revert.
8
8
  *
9
9
  * "Apply" pushes the local Monaco buffer to the server-side stage
10
- * (`tis.put_method`). "Save" persists the entire stage to project.json
10
+ * (`tis.put_method`). "Save" persists the entire stage to test_methods.json
11
11
  * (`tis.save_config`). "Revert" drops the stage (`tis.discard_config_changes`).
12
12
  *
13
13
  * Save is disabled (with a tooltip) when active tests are open against
@@ -1 +1 @@
1
- {"version":3,"file":"SaveDiffDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/SaveDiffDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAQD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAgGxD,CAAC"}
1
+ {"version":3,"file":"SaveDiffDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/SaveDiffDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAQD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAiGxD,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import{useEffect,useState}from"react";import{Dialog}from"primereact/dialog";import{Button}from"primereact/button";export const SaveDiffDialog=({visible:e,staged:i,invoker:r,onConfirm:t,onCancel:s})=>{const[d,o]=useState(null),[n,l]=useState(null),[a,c]=useState(!1);useEffect(()=>{if(!e)return;let t=!1;return(async()=>{l(null);try{const e=await r("tis.list_schemas",{});if(t)return;if(!e.success)return void l(e.error_message??"tis.list_schemas failed");const s=e.data?.test_methods??{};o(buildDiff(s,i))}catch(e){l(String(e?.message??e))}})(),()=>{t=!0}},[e,i,r]);return _jsxs(Dialog,{header:"Save Test Methods to Disk",visible:e,onHide:s,style:{width:"60rem",maxWidth:"90vw"},children:[n&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.5rem"},children:n}),!d&&!n&&_jsx("small",{children:"Computing diff…"}),d&&_jsxs("div",{children:[_jsxs("p",{children:["About to overwrite ",_jsx("code",{children:"project.json"}),". A backup of the previous file will be written to"," ",_jsx("code",{children:"project.json.bak"}),"."]}),0===d.added.length&&0===d.removed.length&&0===d.modified.length&&_jsx("p",{children:_jsx("em",{children:"No changes detected."})}),d.added.length>0&&_jsxs("p",{children:[_jsx("strong",{children:"Added:"})," ",d.added.join(", ")]}),d.removed.length>0&&_jsxs("p",{children:[_jsx("strong",{children:"Removed:"})," ",d.removed.join(", ")]}),d.modified.length>0&&_jsxs(_Fragment,{children:[_jsxs("p",{children:[_jsx("strong",{children:"Modified:"})," ",d.modified.map(e=>e.id).join(", ")]}),_jsxs("details",{children:[_jsx("summary",{children:"Show before / after"}),d.modified.map(e=>_jsxs("div",{style:{marginTop:"0.75rem"},children:[_jsx("strong",{children:e.id}),_jsxs("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"0.5rem",marginTop:"0.25rem"},children:[_jsx("pre",{style:preStyle("before"),children:JSON.stringify(e.before,null,2)}),_jsx("pre",{style:preStyle("after"),children:JSON.stringify(e.after,null,2)})]})]},e.id))]})]})]}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:s,disabled:a}),_jsx(Button,{label:"Save",icon:"pi pi-save",disabled:a||!d,onClick:async()=>{c(!0);try{await t()}finally{c(!1)}}})]})]})};function preStyle(e){return{background:"before"===e?"#fef2f2":"#f0fdf4",color:"#0f172a",padding:"0.5rem",fontSize:"0.75rem",margin:0,maxHeight:"20rem",overflow:"auto",border:"1px solid "+("before"===e?"#fecaca":"#bbf7d0"),borderRadius:4}}function buildDiff(e,i){const r={added:[],removed:[],modified:[]},t=new Set([...Object.keys(e),...Object.keys(i)]);for(const s of t){const t=e[s],d=i[s];void 0===t&&void 0!==d?r.added.push(s):void 0!==t&&void 0===d?r.removed.push(s):void 0!==t&&void 0!==d&&JSON.stringify(t)!==JSON.stringify(d)&&r.modified.push({id:s,before:t,after:d})}return r}
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import{useEffect,useState}from"react";import{Dialog}from"primereact/dialog";import{Button}from"primereact/button";export const SaveDiffDialog=({visible:e,staged:i,invoker:t,onConfirm:s,onCancel:r})=>{const[d,o]=useState(null),[n,l]=useState(null),[a,c]=useState(!1);useEffect(()=>{if(!e)return;let s=!1;return(async()=>{l(null);try{const e=await t("tis.list_schemas",{});if(s)return;if(!e.success)return void l(e.error_message??"tis.list_schemas failed");const r=e.data?.test_methods??{};o(buildDiff(r,i))}catch(e){l(String(e?.message??e))}})(),()=>{s=!0}},[e,i,t]);return _jsxs(Dialog,{header:"Save Test Methods to Disk",visible:e,onHide:r,style:{width:"60rem",maxWidth:"90vw"},children:[n&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.5rem"},children:n}),!d&&!n&&_jsx("small",{children:"Computing diff…"}),d&&_jsxs("div",{children:[_jsxs("p",{children:["About to save the test methods to"," ",_jsx("code",{children:"test_methods.json"}),". A backup of the previous file will be written to"," ",_jsx("code",{children:"test_methods.json.bak"}),"."]}),0===d.added.length&&0===d.removed.length&&0===d.modified.length&&_jsx("p",{children:_jsx("em",{children:"No changes detected."})}),d.added.length>0&&_jsxs("p",{children:[_jsx("strong",{children:"Added:"})," ",d.added.join(", ")]}),d.removed.length>0&&_jsxs("p",{children:[_jsx("strong",{children:"Removed:"})," ",d.removed.join(", ")]}),d.modified.length>0&&_jsxs(_Fragment,{children:[_jsxs("p",{children:[_jsx("strong",{children:"Modified:"})," ",d.modified.map(e=>e.id).join(", ")]}),_jsxs("details",{children:[_jsx("summary",{children:"Show before / after"}),d.modified.map(e=>_jsxs("div",{style:{marginTop:"0.75rem"},children:[_jsx("strong",{children:e.id}),_jsxs("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"0.5rem",marginTop:"0.25rem"},children:[_jsx("pre",{style:preStyle("before"),children:JSON.stringify(e.before,null,2)}),_jsx("pre",{style:preStyle("after"),children:JSON.stringify(e.after,null,2)})]})]},e.id))]})]})]}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:r,disabled:a}),_jsx(Button,{label:"Save",icon:"pi pi-save",disabled:a||!d,onClick:async()=>{c(!0);try{await s()}finally{c(!1)}}})]})]})};function preStyle(e){return{background:"before"===e?"#fef2f2":"#f0fdf4",color:"#0f172a",padding:"0.5rem",fontSize:"0.75rem",margin:0,maxHeight:"20rem",overflow:"auto",border:"1px solid "+("before"===e?"#fecaca":"#bbf7d0"),borderRadius:4}}function buildDiff(e,i){const t={added:[],removed:[],modified:[]},s=new Set([...Object.keys(e),...Object.keys(i)]);for(const r of s){const s=e[r],d=i[r];void 0===s&&void 0!==d?t.added.push(r):void 0!==s&&void 0===d?t.removed.push(r):void 0!==s&&void 0!==d&&JSON.stringify(s)!==JSON.stringify(d)&&t.modified.push({id:r,before:s,after:d})}return t}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * useAmsSchemaConfig — load and edit the AMS custom `asset_types` catalog.
3
+ *
4
+ * Thin wrapper around the `ams.show_schema_config` / `ams.put_asset_type` /
5
+ * `ams.remove_asset_type` / `ams.save_schema_config` /
6
+ * `ams.discard_schema_changes` IPC surface. Mirrors the server's
7
+ * staged-then-saved model (and the TIS `useTisConfig` hook exactly): edits
8
+ * land in the AMS module's in-memory buffer (dirty = true) until `save()`
9
+ * persists them to the `asset_management.json` sidecar next to project.json.
10
+ *
11
+ * Only the project's CUSTOM asset types are editable here. The three
12
+ * built-in types (load_cell/linear_encoder/spring) are code-defined and
13
+ * surfaced read-only via `ams.list_schemas` (see `useAmsAssetTypes`).
14
+ *
15
+ * Pass an `invoker` override to mock the IPC layer (playground / tests).
16
+ * When omitted, the hook reads `EventEmitterContext`.
17
+ */
18
+ import type { TisIpcInvoker } from './useTisConfig';
19
+ import type { AssetTypeDef } from '../components/ams-editor/types';
20
+ export interface AmsSchemaConfig {
21
+ assetTypes: Record<string, AssetTypeDef>;
22
+ dirty: boolean;
23
+ }
24
+ export interface UseAmsSchemaConfigResult {
25
+ config: AmsSchemaConfig | null;
26
+ loading: boolean;
27
+ /** Last server-reported error from a mutation. Cleared on next success. */
28
+ error: string | null;
29
+ refresh: () => Promise<void>;
30
+ putType: (typeId: string, def: AssetTypeDef) => Promise<void>;
31
+ removeType: (typeId: string) => Promise<void>;
32
+ save: () => Promise<void>;
33
+ revert: () => Promise<void>;
34
+ }
35
+ export interface UseAmsSchemaConfigOptions {
36
+ invoker?: TisIpcInvoker;
37
+ }
38
+ export declare function useAmsSchemaConfig(options?: UseAmsSchemaConfigOptions): UseAmsSchemaConfigResult;
39
+ //# sourceMappingURL=useAmsSchemaConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAmsSchemaConfig.d.ts","sourceRoot":"","sources":["../../src/hooks/useAmsSchemaConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzC,KAAK,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACtC,OAAO,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAC9B,OAAO,CAAC,EAAE,yBAAyB,GACpC,wBAAwB,CA8F1B"}
@@ -0,0 +1 @@
1
+ import{useCallback,useContext,useEffect,useRef,useState}from"react";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export function useAmsSchemaConfig(e){const s=useContext(EventEmitterContext),t=e?.invoker??(async(e,t)=>await s.invoke(e,MessageType.Request,t)),a=useRef(t);a.current=t;const[r,n]=useState(null),[c,o]=useState(!0),[i,u]=useState(null),l=useCallback(async()=>{o(!0);try{const e=await a.current("ams.show_schema_config",{});if(!e.success)return u(e.error_message??"ams.show_schema_config failed"),void n(null);const s=e.data??{};n({assetTypes:s.asset_types??{},dirty:!!s.dirty}),u(null)}catch(e){u(String(e?.message??e)),n(null)}finally{o(!1)}},[]);useEffect(()=>{l()},[l]);const m=useCallback(async(e,s)=>{const t=await a.current("ams.put_asset_type",{type_id:e,definition:s});if(!t.success){const e=t.error_message??"ams.put_asset_type failed";throw u(e),new Error(e)}u(null),await l()},[l]),f=useCallback(async e=>{const s=await a.current("ams.remove_asset_type",{type_id:e});if(!s.success){const e=s.error_message??"ams.remove_asset_type failed";throw u(e),new Error(e)}u(null),await l()},[l]),_=useCallback(async()=>{const e=await a.current("ams.save_schema_config",{});if(!e.success){const s=e.error_message??"ams.save_schema_config failed";throw u(s),new Error(s)}u(null),await l()},[l]),y=useCallback(async()=>{const e=await a.current("ams.discard_schema_changes",{});if(!e.success){const s=e.error_message??"ams.discard_schema_changes failed";throw u(s),new Error(s)}u(null),await l()},[l]);return{config:r,loading:c,error:i,refresh:l,putType:m,removeType:f,save:_,revert:y}}
@@ -5,7 +5,7 @@
5
5
  * `tis.remove_method` / `tis.save_config` / `tis.discard_config_changes`
6
6
  * IPC surface. Mirrors the server's staged-then-saved model: edits land in
7
7
  * the server's in-memory copy (dirty = true) until `save()` persists them
8
- * to project.json.
8
+ * to the `test_methods.json` sidecar next to project.json.
9
9
  *
10
10
  * Today the server hosts exactly one project (the one configured at
11
11
  * startup), so `projectId` is informational; the hook still threads it
package/package.json CHANGED
@@ -1,118 +1,118 @@
1
1
  {
2
- "name": "@adcops/autocore-react",
3
- "version": "3.3.112",
4
- "description": "A React component library for industrial user interfaces.",
5
- "private": false,
6
- "type": "module",
7
- "scripts": {
8
- "clean": "rimraf ./dist",
9
- "minify": "node ./tools/minify.cjs",
10
- "tsc": "tsc",
11
- "build-themes": "node ./tools/build-themes.cjs",
12
- "generate-docs": "typedoc",
13
- "build": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs && npm run build-themes && npm run minify",
14
- "build:dev": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs",
15
- "dev": "vite",
16
- "playground": "vite",
17
- "playground:build": "vite build",
18
- "prestart": "tsc",
19
- "publish-package": "npm run build && npm version patch && npm publish",
20
- "prepublishOnly": "npm run build",
21
- "release:patch": "npm version patch && npm publish",
22
- "release:minor": "npm version minor && npm publish",
23
- "release:major": "npm version major && npm publish",
24
- "convert-assets": "npx @svgr/cli --typescript --out-dir src/assets/ src/assets/svg/ --jsx-runtime automatic --ignore-existing"
25
- },
26
- "repository": {
27
- "type": "git",
28
- "url": "git+https://adc360.visualstudio.com/AutoCore-React/_git/autocore-react"
29
- },
30
- "keywords": [
31
- "react",
32
- "autocore",
33
- "component-library",
34
- "industrial-ui",
35
- "typescript",
36
- "primereact"
37
- ],
38
- "author": "Automated Design Corp.",
39
- "license": "ISC",
40
- "bugs": {
41
- "url": "https://automateddesign.com"
42
- },
43
- "homepage": "https://automateddesign.com",
44
- "peerDependencies": {
45
- "@types/react": "^18",
46
- "primereact": ">=10.9.7",
47
- "react": "^18",
48
- "react-dom": "^18"
2
+ "name": "@adcops/autocore-react",
3
+ "version": "3.3.115",
4
+ "description": "A React component library for industrial user interfaces.",
5
+ "private": false,
6
+ "type": "module",
7
+ "scripts": {
8
+ "clean": "rimraf ./dist",
9
+ "minify": "node ./tools/minify.cjs",
10
+ "tsc": "tsc",
11
+ "build-themes": "node ./tools/build-themes.cjs",
12
+ "generate-docs": "typedoc",
13
+ "build": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs && npm run build-themes && npm run minify",
14
+ "build:dev": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs",
15
+ "dev": "vite",
16
+ "playground": "vite",
17
+ "playground:build": "vite build",
18
+ "prestart": "tsc",
19
+ "publish-package": "npm run build && npm version patch && npm publish",
20
+ "prepublishOnly": "npm run build",
21
+ "release:patch": "npm version patch && npm publish",
22
+ "release:minor": "npm version minor && npm publish",
23
+ "release:major": "npm version major && npm publish",
24
+ "convert-assets": "npx @svgr/cli --typescript --out-dir src/assets/ src/assets/svg/ --jsx-runtime automatic --ignore-existing"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://adc360.visualstudio.com/AutoCore-React/_git/autocore-react"
29
+ },
30
+ "keywords": [
31
+ "react",
32
+ "autocore",
33
+ "component-library",
34
+ "industrial-ui",
35
+ "typescript",
36
+ "primereact"
37
+ ],
38
+ "author": "Automated Design Corp.",
39
+ "license": "ISC",
40
+ "bugs": {
41
+ "url": "https://automateddesign.com"
42
+ },
43
+ "homepage": "https://automateddesign.com",
44
+ "peerDependencies": {
45
+ "@types/react": "^18",
46
+ "primereact": ">=10.9.7",
47
+ "react": "^18",
48
+ "react-dom": "^18"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@types/react": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "@monaco-editor/react": "^4.7.0",
57
+ "@tauri-apps/api": "^2.9.1",
58
+ "chart.js": "^4.5.1",
59
+ "chartjs-plugin-annotation": "^3.1.0",
60
+ "chartjs-plugin-zoom": "^2.2.0",
61
+ "clsx": "^2.1.1",
62
+ "numerable": "^0.3.15",
63
+ "react-blockly": "^8.1.2",
64
+ "react-chartjs-2": "^5.3.0",
65
+ "react-number-format": "^5.4.5",
66
+ "react-simple-keyboard": "^3.8.120",
67
+ "react-transition-group": "^4.4.5",
68
+ "sass": "^1.92.1",
69
+ "socket.io-client": "^4.8.1",
70
+ "use-fit-text": "^2.4.0"
71
+ },
72
+ "sideEffects": [
73
+ "**/*.css",
74
+ "**/*.scss"
75
+ ],
76
+ "engines": {
77
+ "node": ">=20.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@svgr/cli": "^8.1.0",
81
+ "@types/react": "^18.2.15",
82
+ "@types/react-dom": "^18.2.7",
83
+ "@types/react-transition-group": "^4.4.12",
84
+ "@vitejs/plugin-react": "^6.0.2",
85
+ "primereact": "^10.9.7",
86
+ "rimraf": "^6.0.1",
87
+ "terser": "^5.44.0",
88
+ "typedoc": "^0.28.12",
89
+ "typescript": "^5.9.2",
90
+ "vite": "^8.0.13"
91
+ },
92
+ "exports": {
93
+ "./core/*": {
94
+ "import": "./dist/core/*",
95
+ "types": "./dist/core/*.d.ts"
49
96
  },
50
- "peerDependenciesMeta": {
51
- "@types/react": {
52
- "optional": true
53
- }
97
+ "./components/*": {
98
+ "import": "./dist/components/*",
99
+ "types": "./dist/components/*.d.ts"
54
100
  },
55
- "dependencies": {
56
- "@monaco-editor/react": "^4.7.0",
57
- "@tauri-apps/api": "^2.9.1",
58
- "chart.js": "^4.5.1",
59
- "chartjs-plugin-annotation": "^3.1.0",
60
- "chartjs-plugin-zoom": "^2.2.0",
61
- "clsx": "^2.1.1",
62
- "numerable": "^0.3.15",
63
- "react-blockly": "^8.1.2",
64
- "react-chartjs-2": "^5.3.0",
65
- "react-number-format": "^5.4.5",
66
- "react-simple-keyboard": "^3.8.120",
67
- "react-transition-group": "^4.4.5",
68
- "sass": "^1.92.1",
69
- "socket.io-client": "^4.8.1",
70
- "use-fit-text": "^2.4.0"
101
+ "./assets/*": {
102
+ "import": "./dist/assets/*",
103
+ "types": "./dist/assets/*.d.ts"
71
104
  },
72
- "sideEffects": [
73
- "**/*.css",
74
- "**/*.scss"
75
- ],
76
- "engines": {
77
- "node": ">=20.0.0"
105
+ "./hooks/*": {
106
+ "import": "./dist/hooks/*",
107
+ "types": "./dist/hooks/*.d.ts"
78
108
  },
79
- "devDependencies": {
80
- "@svgr/cli": "^8.1.0",
81
- "@types/react": "^18.2.15",
82
- "@types/react-dom": "^18.2.7",
83
- "@types/react-transition-group": "^4.4.12",
84
- "@vitejs/plugin-react": "^6.0.2",
85
- "primereact": "^10.9.7",
86
- "rimraf": "^6.0.1",
87
- "terser": "^5.44.0",
88
- "typedoc": "^0.28.12",
89
- "typescript": "^5.9.2",
90
- "vite": "^8.0.13"
109
+ "./themes/*": {
110
+ "import": "./dist/themes/*",
111
+ "types": "./dist/themes/*.d.ts"
91
112
  },
92
- "exports": {
93
- "./core/*": {
94
- "import": "./dist/core/*",
95
- "types": "./dist/core/*.d.ts"
96
- },
97
- "./components/*": {
98
- "import": "./dist/components/*",
99
- "types": "./dist/components/*.d.ts"
100
- },
101
- "./assets/*": {
102
- "import": "./dist/assets/*",
103
- "types": "./dist/assets/*.d.ts"
104
- },
105
- "./hooks/*": {
106
- "import": "./dist/hooks/*",
107
- "types": "./dist/hooks/*.d.ts"
108
- },
109
- "./themes/*": {
110
- "import": "./dist/themes/*",
111
- "types": "./dist/themes/*.d.ts"
112
- },
113
- "./hub/*": {
114
- "import": "./dist/hub/*",
115
- "types": "./dist/hub/*.d.ts"
116
- }
113
+ "./hub/*": {
114
+ "import": "./dist/hub/*",
115
+ "types": "./dist/hub/*.d.ts"
117
116
  }
117
+ }
118
118
  }
@@ -153,24 +153,28 @@ export const AmsProvider: React.FC<AmsProviderProps> = ({ children }) => {
153
153
  }, []);
154
154
 
155
155
  // -----------------------------------------------------------------
156
+ // Re-fetch the schema catalog. Called once on mount and again whenever
157
+ // the asset-TYPE editor broadcasts `ams.schema_changed` after a save, so
158
+ // open pickers/forms pick up new or edited types without a reload.
159
+ const loadSchemas = useCallback(async () => {
160
+ try {
161
+ const resp: any = await invoke('ams.list_schemas' as any, MessageType.Request, {} as any);
162
+ if (resp?.success && resp.data) {
163
+ setSchemas((resp.data.asset_types ?? {}) as AmsSchemaRegistry);
164
+ setSchemasLoaded(true);
165
+ } else {
166
+ console.warn('[AmsProvider] ams.list_schemas failed:', resp?.error_message);
167
+ }
168
+ } catch (e) {
169
+ console.error('[AmsProvider] ams.list_schemas threw:', e);
170
+ }
171
+ }, [invoke]);
172
+
156
173
  // Schema + role load — once on mount.
157
174
  // -----------------------------------------------------------------
158
175
  useEffect(() => {
159
176
  let cancelled = false;
160
- (async () => {
161
- try {
162
- const resp: any = await invoke('ams.list_schemas' as any, MessageType.Request, {} as any);
163
- if (cancelled) return;
164
- if (resp?.success && resp.data) {
165
- setSchemas((resp.data.asset_types ?? {}) as AmsSchemaRegistry);
166
- setSchemasLoaded(true);
167
- } else {
168
- console.warn('[AmsProvider] ams.list_schemas failed:', resp?.error_message);
169
- }
170
- } catch (e) {
171
- console.error('[AmsProvider] ams.list_schemas threw:', e);
172
- }
173
- })();
177
+ void loadSchemas();
174
178
  (async () => {
175
179
  try {
176
180
  const resp: any = await invoke('ams.list_roles' as any, MessageType.Request, {} as any);
@@ -214,12 +218,13 @@ export const AmsProvider: React.FC<AmsProviderProps> = ({ children }) => {
214
218
  subscribe('ams.asset_changed', () => { refreshAssets(); }),
215
219
  subscribe('ams.calibration_added', () => { refreshAssets(); }),
216
220
  subscribe('ams.calibration_updated', () => { refreshAssets(); }),
221
+ subscribe('ams.schema_changed', () => { loadSchemas(); }),
217
222
  subscribe('ams.asset_count', (v: any) => setAlerts(a => ({ ...a, assetCount: Number(v) || 0 }))),
218
223
  subscribe('ams.alert_calibration_overdue', (v: any) => setAlerts(a => ({ ...a, calibrationOverdue: Number(v) || 0 }))),
219
224
  subscribe('ams.alert_lane_unavailable', (v: any) => setAlerts(a => ({ ...a, laneUnavailable: Number(v) || 0 }))),
220
225
  ];
221
226
  return () => { subs.forEach(unsubscribe); };
222
- }, [subscribe, unsubscribe, refreshAssets]);
227
+ }, [subscribe, unsubscribe, refreshAssets, loadSchemas]);
223
228
 
224
229
  // -----------------------------------------------------------------
225
230
  // RPC helpers
@@ -0,0 +1,103 @@
1
+ /**
2
+ * AssetFieldArray — add/remove/reorder editor for a plain `TestField[]`.
3
+ *
4
+ * A decoupled sibling of the TIS `FieldArrayEditor` (which is bound to a
5
+ * `TestMethod` keyed by `FieldArrayKey`). Asset types carry bare field
6
+ * lists (`fields`, `calibration_fields`, and the per-key lists inside a
7
+ * keyed `sub_locations`), so this operates directly on an array + onChange.
8
+ *
9
+ * Reuses the TIS `TestFieldDialog` for the per-field modal — asset fields
10
+ * have no cross-test aggregate concept, so `isResultsField` is left false.
11
+ */
12
+
13
+ import { useState } from 'react';
14
+ import { DataTable } from 'primereact/datatable';
15
+ import { Column } from 'primereact/column';
16
+ import { Button } from 'primereact/button';
17
+ import { FormSection } from '../forms/FormSection';
18
+ import { TestFieldDialog } from '../tis-editor/editor/TestFieldDialog';
19
+ import type { TestField } from '../tis-editor/types';
20
+
21
+ export interface AssetFieldArrayProps {
22
+ title: string;
23
+ description?: string;
24
+ fields: TestField[];
25
+ onChange: (next: TestField[]) => void;
26
+ }
27
+
28
+ export const AssetFieldArray: React.FC<AssetFieldArrayProps> = ({
29
+ title, description, fields, onChange,
30
+ }) => {
31
+ const [dialogOpen, setDialogOpen] = useState(false);
32
+ const [editingIdx, setEditingIdx] = useState<number | null>(null);
33
+
34
+ const handleAdd = () => { setEditingIdx(null); setDialogOpen(true); };
35
+ const handleEdit = (idx: number) => { setEditingIdx(idx); setDialogOpen(true); };
36
+
37
+ const handleSaveField = (f: TestField) => {
38
+ const next = [...fields];
39
+ if (editingIdx === null) next.push(f);
40
+ else next[editingIdx] = f;
41
+ onChange(next);
42
+ setDialogOpen(false);
43
+ };
44
+
45
+ const handleRemove = (idx: number) => {
46
+ const target = fields[idx];
47
+ if (!target) return;
48
+ if (!window.confirm(`Remove field "${target.name}"?`)) return;
49
+ onChange(fields.filter((_, i) => i !== idx));
50
+ };
51
+
52
+ const handleMove = (idx: number, dir: -1 | 1) => {
53
+ const j = idx + dir;
54
+ if (j < 0 || j >= fields.length) return;
55
+ const next = [...fields];
56
+ [next[idx], next[j]] = [next[j], next[idx]];
57
+ onChange(next);
58
+ };
59
+
60
+ const rowActions = (_row: TestField, opts: { rowIndex: number }) => (
61
+ <div style={{ display: 'flex', gap: '0.25rem' }}>
62
+ <Button icon="pi pi-arrow-up" className="p-button-text p-button-sm"
63
+ disabled={opts.rowIndex === 0}
64
+ onClick={() => handleMove(opts.rowIndex, -1)} aria-label="Move up" />
65
+ <Button icon="pi pi-arrow-down" className="p-button-text p-button-sm"
66
+ disabled={opts.rowIndex === fields.length - 1}
67
+ onClick={() => handleMove(opts.rowIndex, 1)} aria-label="Move down" />
68
+ <Button icon="pi pi-pencil" className="p-button-text p-button-sm"
69
+ onClick={() => handleEdit(opts.rowIndex)} aria-label="Edit" />
70
+ <Button icon="pi pi-trash" className="p-button-text p-button-danger p-button-sm"
71
+ onClick={() => handleRemove(opts.rowIndex)} aria-label="Remove" />
72
+ </div>
73
+ );
74
+
75
+ return (
76
+ <>
77
+ <FormSection
78
+ title={title}
79
+ description={description}
80
+ actions={<Button label="Add field" icon="pi pi-plus" size="small" onClick={handleAdd} />}
81
+ >
82
+ <DataTable value={fields} dataKey="name" emptyMessage="No fields defined.">
83
+ <Column field="name" header="Name" />
84
+ <Column field="type" header="Type" style={{ width: '6rem' }} />
85
+ <Column field="units" header="Units" style={{ width: '6rem' }} />
86
+ <Column header="Req" body={(r: TestField) => r.required ? '✓' : ''} style={{ width: '4rem' }} />
87
+ <Column field="label" header="Label" />
88
+ <Column header="" body={rowActions} style={{ width: '9rem' }} />
89
+ </DataTable>
90
+ </FormSection>
91
+ <TestFieldDialog
92
+ visible={dialogOpen}
93
+ initial={editingIdx !== null ? (fields[editingIdx] ?? null) : null}
94
+ siblingNames={fields.map(f => f.name)}
95
+ isResultsField={false}
96
+ onCancel={() => setDialogOpen(false)}
97
+ onSave={handleSaveField}
98
+ />
99
+ </>
100
+ );
101
+ };
102
+
103
+ export default AssetFieldArray;