@adcops/autocore-react 3.3.109 → 3.3.112
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.
- package/dist/assets/JogXLeft.d.ts +4 -0
- package/dist/assets/JogXLeft.d.ts.map +1 -0
- package/dist/assets/JogXLeft.js +1 -0
- package/dist/assets/JogXRight.d.ts +4 -0
- package/dist/assets/JogXRight.d.ts.map +1 -0
- package/dist/assets/JogXRight.js +1 -0
- package/dist/assets/JogYDownLeft.d.ts +4 -0
- package/dist/assets/JogYDownLeft.d.ts.map +1 -0
- package/dist/assets/JogYDownLeft.js +1 -0
- package/dist/assets/JogYUpRight.d.ts +4 -0
- package/dist/assets/JogYUpRight.d.ts.map +1 -0
- package/dist/assets/JogYUpRight.js +1 -0
- package/dist/assets/index.d.ts +4 -4
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -1
- package/dist/components/JogPanel.d.ts +29 -4
- package/dist/components/JogPanel.d.ts.map +1 -1
- package/dist/components/JogPanel.js +1 -1
- package/dist/components/ValueInput.d.ts.map +1 -1
- package/dist/components/ValueInput.js +1 -1
- package/dist/components/network/StagedChangeBanner.d.ts.map +1 -1
- package/dist/components/network/StagedChangeBanner.js +1 -1
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/dist/components/tis/ScienceTable.css +87 -0
- package/dist/components/tis/ScienceTable.d.ts +35 -0
- package/dist/components/tis/ScienceTable.d.ts.map +1 -0
- package/dist/components/tis/ScienceTable.js +1 -0
- package/dist/components/tis/TestDataView.d.ts.map +1 -1
- package/dist/components/tis/TestDataView.js +1 -1
- package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
- package/dist/components/tis/TestSetupForm.js +1 -1
- package/package.json +1 -1
- package/src/assets/{JogXNeg.tsx → JogXLeft.tsx} +6 -3
- package/src/assets/{JogXPos.tsx → JogXRight.tsx} +6 -3
- package/src/assets/{JogYPos.tsx → JogYDownLeft.tsx} +2 -2
- package/src/assets/{JogYNeg.tsx → JogYUpRight.tsx} +3 -2
- package/src/assets/index.ts +4 -4
- package/src/components/JogPanel.tsx +64 -59
- package/src/components/ValueInput.tsx +10 -3
- package/src/components/network/StagedChangeBanner.tsx +22 -3
- package/src/components/tis/ResultHistoryTable.tsx +19 -8
- package/src/components/tis/ScienceTable.css +87 -0
- package/src/components/tis/ScienceTable.tsx +114 -0
- package/src/components/tis/TestDataView.tsx +49 -54
- package/src/components/tis/TestSetupForm.tsx +6 -2
- package/dist/assets/JogXNeg.d.ts +0 -4
- package/dist/assets/JogXNeg.d.ts.map +0 -1
- package/dist/assets/JogXNeg.js +0 -1
- package/dist/assets/JogXPos.d.ts +0 -4
- package/dist/assets/JogXPos.d.ts.map +0 -1
- package/dist/assets/JogXPos.js +0 -1
- package/dist/assets/JogYNeg.d.ts +0 -4
- package/dist/assets/JogYNeg.d.ts.map +0 -1
- package/dist/assets/JogYNeg.js +0 -1
- package/dist/assets/JogYPos.d.ts +0 -4
- package/dist/assets/JogYPos.d.ts.map +0 -1
- package/dist/assets/JogYPos.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useMemo}from"react";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{Dropdown}from"primereact/dropdown";import{Dialog}from"primereact/dialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{AutoCoreTagContext}from"../../core/AutoCoreTagContext";import{MessageType}from"../../hub/CommandMessage";import{ValueInput}from"../ValueInput";import{TextInput}from"../TextInput";import{useTis}from"./TisProvider";import{useAmsAssets,useAmsRoles}from"../ams/AmsProvider";import{TestMethodDialog}from"./TestMethodDialog";import{ConfigurationDialog,configLabelOf}from"./ConfigurationDialog";const labelOf=e=>{const t=e.label&&e.label.length>0?e.label:e.name;return e.units?`${t} [${e.units}]`:t},rawToDisplay=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e*t:e,displayToRaw=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e/t:e,hasDescription=e=>"string"==typeof e.description&&e.description.length>0,DEFAULT_TOKEN_RE=/^\$\{\s*([^}]+?)\s*\}$/,resolveDefaultRaw=(e,t,s)=>{const i=e.default;if("string"==typeof i){const e=i.match(DEFAULT_TOKEN_RE);if(e){const i=t(e[1]),a=i?s[i.tagName]:void 0;return null==a?{raw:void 0,ok:!1}:{raw:a,ok:!0}}}return{raw:displayToRaw(i,e.scale),ok:!0}},rangeIssue=(e,t)=>{if(void 0===t||""===t||null===t)return null;if(null==e.min&&null==e.max)return null;const s=Number(rawToDisplay(Number(t),e.scale));return Number.isFinite(s)?null!=e.min&&s<e.min?`must be ≥ ${e.min}`:null!=e.max&&s>e.max?`must be ≤ ${e.max}`:null:null},normalizeOptions=e=>(e??[]).map(e=>null!==e&&"object"==typeof e?{label:String(e.label??e.value),value:e.value}:{label:String(e),value:e}),methodLabelOf=(e,t)=>t?.label&&t.label.length>0?t.label:e,AssetIdPicker=({assetType:e,value:t,onChange:s,invalid:i})=>{const a=useAmsAssets(),n=useAmsRoles(),o=useMemo(()=>a.filter(t=>t.asset_type===e&&"active"===t.status).map(e=>{const t=n[e.asset_type]?.find(t=>t.location===e.location)?.label,s=[e.asset_id];return t?s.push(`— ${t}`):e.location&&s.push(`— ${e.location}`),e.serial&&s.push(`(s/n ${e.serial})`),{label:s.join(" "),value:e.asset_id}}),[a,n,e]);return _jsx(Dropdown,{value:t,options:o,onChange:e=>s(e.value??""),placeholder:0===o.length?`No active ${e} assets registered — add one in Settings → Assets`:`Select ${e}…`,className:"ac-dropdown-clearable"+(i?" p-invalid":""),filter:!0,showClear:!0,disabled:0===o.length})};export const TestSetupForm=({schema:e,defaultMethodId:t,onMethodChange:s,onValidationChange:i})=>{const a=useTis(),{invoke:n,write:o}=useContext(EventEmitterContext),{rawValues:r,findTagByFqdn:l}=useContext(AutoCoreTagContext),c=useMemo(()=>Object.keys(a.schemas),[a.schemas]),d=a.selection.projectId,m=""!==d.trim()&&a.projectKnown(d.trim()),[u,p]=useState(a.selection.methodId||t||a.defaultMethodId||""),[f,h]=useState(a.selection.sampleId||""),g=a.stagedConfig,x=e=>{const t="function"==typeof e?e(a.stagedConfig):e;t!==a.stagedConfig&&a.setStagedConfig(t)},_=e??(u?a.schemas[u]:void 0),j=(e,t)=>{for(const[s,i]of Object.entries(e.defaults??{})){if("sample_id"===s)continue;const e=_?.config_fields.find(e=>e.name===s),a=displayToRaw(i,e?.scale);t[s]=a,e?.source&&Promise.resolve().then(()=>o(e.source,a)).catch(e=>{})}return t};useEffect(()=>{a.selection.methodId!==u&&u&&a.setSelection({methodId:u}),s&&s(u)},[u]),useEffect(()=>{a.selection.sampleId!==f&&a.setSelection({sampleId:f})},[f]),useEffect(()=>{a.state.stagedSampleId&&a.state.stagedSampleId!==f&&h(a.state.stagedSampleId)},[a.state.stagedSampleId]),useEffect(()=>{a.selection.methodId&&a.selection.methodId!==u&&p(a.selection.methodId)},[a.selection.methodId]);const[b,v]=useState(!1),[y,N]=useState(!1),[C,I]=useState(!1),T=_?.configurations??[],S=T.find(e=>e.name===a.configurationName),[w,k]=useState({open:!1,title:"",body:null});useEffect(()=>{if(!_||!u)return;if(a.defaultsAppliedForMethod===u)return;a.markDefaultsAppliedForMethod(u);const e=_.configurations&&_.configurations.length>0?_.configurations[0]:void 0;x(t=>{let s=t;for(const e of _.config_fields){if("sample_id"===e.name)continue;if(void 0===e.default||null===e.default)continue;if(e.source){const t=l(e.source),s=t?r[t.tagName]:void 0;if(null!=s)continue}const{raw:i,ok:a}=resolveDefaultRaw(e,l,r);a&&(s===t&&(s={...t}),s[e.name]=i,e.source&&Promise.resolve().then(()=>o(e.source,i)).catch(e=>{}))}return e&&(s===t&&(s={...t}),s=j(e,s)),s}),a.setConfigurationName(e?e.name:"")},[_,u,o,r,l,a.defaultsAppliedForMethod,a.markDefaultsAppliedForMethod,a.setConfigurationName]),useEffect(()=>{_&&x(e=>{let t=e;for(const s of _.config_fields){if("sample_id"===s.name)continue;if(!s.source)continue;const i=l(s.source);if(!i)continue;const a=r[i.tagName];null!=a&&(t[s.name]!==a&&(t===e&&(t={...e}),t[s.name]=a))}return t})},[_,r,l]),useEffect(()=>{if(!_)return void v(!1);let e=!0;m||(e=!1),u.trim()||(e=!1),f.trim()||(e=!1),e&&!a.projectFieldsLoaded&&(e=!1);for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e=!1;break}if(rangeIssue(t,s)){e=!1;break}}if(v(e),i&&i(e,g),e){const{sample_id:e,...t}=g??{},s={...a.projectFields,...t};n("tis.stage_test",MessageType.Request,{project_id:d,method_id:u,sample_id:f,config:s}).catch(e=>{})}},[g,_,d,u,f,m,a.projectFields,a.projectFieldsLoaded,i,n]);const A=async(e,t)=>{const s=displayToRaw(t,e.scale);if(x({...g,[e.name]:s}),e.source)try{await o(e.source,s)}catch(e){}};if(!_)return _jsx("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:_jsx("h3",{className:"ac-form-section",children:a.schemasLoaded?"No Test Method Selected":"Loading test methods…"})});if(!m)return _jsxs("div",{style:{padding:"1.25rem",maxWidth:"600px"},children:[_jsx("h3",{className:"ac-form-section",children:"No project selected"}),_jsxs("p",{style:{color:"var(--text-secondary-color)",marginTop:"0.5rem"},children:["Pick a project on the ",_jsx("strong",{children:"Project"})," tab first",""!==d.trim()&&` (or click + there to create "${d.trim()}")`,"."]})]});const D={padding:"1.25rem",gridTemplateColumns:"auto 1fr 1.75rem 1.75rem"},F=b&&!a.state.lastStartError;return _jsxs("div",{className:"ac-test-setup-form",children:[_jsxs("div",{className:"ac-form-grid ac-test-setup-form__head",style:D,children:[_jsxs("h3",{className:"ac-form-section",style:{display:"flex",alignItems:"center",gap:"10px"},children:["Test Setup",_jsx(Button,{icon:F?"pi pi-check-circle":"pi pi-exclamation-circle",severity:F?"success":"danger",text:!0,rounded:!0,"aria-label":"Test Setup status",onClick:()=>{const e=(()=>{const e=[];if(m||e.push("No project is selected. Pick or create one on the Project tab."),a.projectFieldsLoaded||e.push("Project fields are still loading."),u.trim()||e.push("No test method selected."),f.trim()||e.push("Sample ID is required."),_)for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e.push(`Required field "${labelOf(t)}" is empty.`);continue}const a=rangeIssue(t,s);a&&e.push(`"${labelOf(t)}" ${a}.`)}return e})(),t=a.state.lastStartError?.trim()??"";let s;s=0!==e.length||t?_jsxs("div",{children:[e.length>0&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:"The form is incomplete:"}),_jsx("ul",{style:{margin:"0 0 1rem 1.25rem"},children:e.map((e,t)=>_jsx("li",{children:e},t))})]}),t&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.25rem 0",fontWeight:600},children:"Last start_test error from the server:"}),_jsx("pre",{style:{margin:0,padding:"0.5rem",background:"rgba(0,0,0,0.25)",borderRadius:"4px",whiteSpace:"pre-wrap",fontSize:"0.875rem"},children:t})]})]}):_jsx("p",{style:{margin:0},children:"All required fields are complete. The test is staged and ready to start."}),k({open:!0,title:"Test Setup Status",body:s})}}),_jsxs("span",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)",fontWeight:"normal",marginLeft:"0.25rem"},children:["project: ",_jsx("strong",{children:d})]})]}),_jsx("span",{className:"ac-form-label",children:"Sample ID"}),_jsx(TextInput,{label:void 0,value:f,onValueChanged:e=>{h(e)},className:f.trim()?"":"p-invalid"}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:f.trim()?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:f.trim()?"pi pi-check":"pi pi-times"})}),c.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Test Method"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:methodLabelOf(u,_),readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>N(!0),tooltip:c.length>1?"Change test method":"View test method details",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:u?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:u?"pi pi-check":"pi pi-times"})})]}),T.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Configuration"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:S?configLabelOf(S):"",placeholder:"No configuration selected",readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>I(!0),tooltip:"Change configuration",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:S?"var(--green-500)":"var(--text-secondary-color)",display:"flex",alignItems:"center"},children:_jsx("i",{className:S?"pi pi-check":"pi pi-minus"})})]})]}),_jsx("div",{className:"ac-test-setup-form__body",children:_jsxs("div",{className:"ac-form-grid",style:D,children:[_jsx("h3",{className:"ac-form-section",children:"Test Configuration"}),_.config_fields.map(e=>{if("sample_id"===e.name)return null;const t=(e=>{const t=g[e.name],s=void 0===t||""===t||null===t;return!(e.required&&s||rangeIssue(e,t))})(e),s=(e=>{const t=_?.asset_refs??[],s=a.projectAssetRefs??[],i=`config.${e.name}`,n=t.find(e=>"by_id_field"===e.select&&e.from===i);if(n)return n.asset_type;const o=s.find(e=>"by_id_field"===e.select&&e.from===i);return o?o.asset_type:null})(e),i=!s&&Array.isArray(e.options)&&e.options.length>0?normalizeOptions(e.options):null,n=!s&&!i&&"string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("span",{className:"ac-form-label",children:labelOf(e)}),s?_jsx(AssetIdPicker,{assetType:s,value:null!=g[e.name]?String(g[e.name]):"",onChange:t=>A(e,t),invalid:!t}):i?_jsx(Dropdown,{value:g[e.name]??null,options:i,onChange:t=>A(e,t.value),placeholder:`Select ${e.label??e.name}…`,className:"ac-dropdown-clearable"+(t?"":" p-invalid"),showClear:!e.required}):n?_jsx(ValueInput,{label:void 0,value:null!=g[e.name]?Number(rawToDisplay(Number(g[e.name]),e.scale)):null,min:e.min,max:e.max,onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}):_jsx(TextInput,{label:void 0,value:null!=g[e.name]?String(g[e.name]):"",onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}),hasDescription(e)?_jsx(Button,{icon:"pi pi-info-circle",text:!0,rounded:!0,"aria-label":`About ${labelOf(e)}`,onClick:()=>k({open:!0,title:labelOf(e),body:_jsx("p",{style:{margin:0,whiteSpace:"pre-wrap"},children:e.description})})}):_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:t?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:t?"pi pi-check":"pi pi-times"})})]},e.name)})]})}),_jsx(TestMethodDialog,{visible:y,onHide:()=>N(!1),currentMethodId:u,onSelected:e=>p(e)}),_jsx(ConfigurationDialog,{visible:C,onHide:()=>I(!1),configurations:T,currentConfigName:a.configurationName,onSelected:e=>{const t=T.find(t=>t.name===e);t&&(x(e=>j(t,{...e})),a.setConfigurationName(e))}}),_jsx(Dialog,{header:w.title,visible:w.open,onHide:()=>k({open:!1,title:"",body:null}),style:{width:"32rem",maxWidth:"90vw"},modal:!0,dismissableMask:!0,children:w.body})]})};
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useMemo}from"react";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{Dropdown}from"primereact/dropdown";import{Dialog}from"primereact/dialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{AutoCoreTagContext}from"../../core/AutoCoreTagContext";import{MessageType}from"../../hub/CommandMessage";import{ValueInput}from"../ValueInput";import{TextInput}from"../TextInput";import{useTis}from"./TisProvider";import{useAmsAssets,useAmsRoles}from"../ams/AmsProvider";import{TestMethodDialog}from"./TestMethodDialog";import{ConfigurationDialog,configLabelOf}from"./ConfigurationDialog";const labelOf=e=>{const t=e.label&&e.label.length>0?e.label:e.name;return e.units?`${t} [${e.units}]`:t},rawToDisplay=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e*t:e,displayToRaw=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e/t:e,hasDescription=e=>"string"==typeof e.description&&e.description.length>0,DEFAULT_TOKEN_RE=/^\$\{\s*([^}]+?)\s*\}$/,resolveDefaultRaw=(e,t,s)=>{const i=e.default;if("string"==typeof i){const e=i.match(DEFAULT_TOKEN_RE);if(e){const i=t(e[1]),n=i?s[i.tagName]:void 0;return null==n?{raw:void 0,ok:!1}:{raw:n,ok:!0}}}return{raw:displayToRaw(i,e.scale),ok:!0}},rangeIssue=(e,t)=>{if(void 0===t||""===t||null===t)return null;if(null==e.min&&null==e.max)return null;const s=Number(rawToDisplay(Number(t),e.scale));if(!Number.isFinite(s))return null;const i=null!=e.min&&null!=e.max?Math.min(e.min,e.max):e.min,n=null!=e.min&&null!=e.max?Math.max(e.min,e.max):e.max;return null!=i&&s<i?`must be ≥ ${i}`:null!=n&&s>n?`must be ≤ ${n}`:null},normalizeOptions=e=>(e??[]).map(e=>null!==e&&"object"==typeof e?{label:String(e.label??e.value),value:e.value}:{label:String(e),value:e}),methodLabelOf=(e,t)=>t?.label&&t.label.length>0?t.label:e,AssetIdPicker=({assetType:e,value:t,onChange:s,invalid:i})=>{const n=useAmsAssets(),a=useAmsRoles(),o=useMemo(()=>n.filter(t=>t.asset_type===e&&"active"===t.status).map(e=>{const t=a[e.asset_type]?.find(t=>t.location===e.location)?.label,s=[e.asset_id];return t?s.push(`— ${t}`):e.location&&s.push(`— ${e.location}`),e.serial&&s.push(`(s/n ${e.serial})`),{label:s.join(" "),value:e.asset_id}}),[n,a,e]);return _jsx(Dropdown,{value:t,options:o,onChange:e=>s(e.value??""),placeholder:0===o.length?`No active ${e} assets registered — add one in Settings → Assets`:`Select ${e}…`,className:"ac-dropdown-clearable"+(i?" p-invalid":""),filter:!0,showClear:!0,disabled:0===o.length})};export const TestSetupForm=({schema:e,defaultMethodId:t,onMethodChange:s,onValidationChange:i})=>{const n=useTis(),{invoke:a,write:o}=useContext(EventEmitterContext),{rawValues:l,findTagByFqdn:r}=useContext(AutoCoreTagContext),c=useMemo(()=>Object.keys(n.schemas),[n.schemas]),d=n.selection.projectId,m=""!==d.trim()&&n.projectKnown(d.trim()),[u,p]=useState(n.selection.methodId||t||n.defaultMethodId||""),[f,h]=useState(n.selection.sampleId||""),g=n.stagedConfig,x=e=>{const t="function"==typeof e?e(n.stagedConfig):e;t!==n.stagedConfig&&n.setStagedConfig(t)},_=e??(u?n.schemas[u]:void 0),j=(e,t)=>{for(const[s,i]of Object.entries(e.defaults??{})){if("sample_id"===s)continue;const e=_?.config_fields.find(e=>e.name===s),n=displayToRaw(i,e?.scale);t[s]=n,e?.source&&Promise.resolve().then(()=>o(e.source,n)).catch(e=>{})}return t};useEffect(()=>{n.selection.methodId!==u&&u&&n.setSelection({methodId:u}),s&&s(u)},[u]),useEffect(()=>{n.selection.sampleId!==f&&n.setSelection({sampleId:f})},[f]),useEffect(()=>{n.state.stagedSampleId&&n.state.stagedSampleId!==f&&h(n.state.stagedSampleId)},[n.state.stagedSampleId]),useEffect(()=>{n.selection.methodId&&n.selection.methodId!==u&&p(n.selection.methodId)},[n.selection.methodId]);const[b,v]=useState(!1),[y,N]=useState(!1),[C,I]=useState(!1),T=_?.configurations??[],S=T.find(e=>e.name===n.configurationName),[w,k]=useState({open:!1,title:"",body:null});useEffect(()=>{if(!_||!u)return;if(n.defaultsAppliedForMethod===u)return;n.markDefaultsAppliedForMethod(u);const e=_.configurations&&_.configurations.length>0?_.configurations[0]:void 0;x(t=>{let s=t;for(const e of _.config_fields){if("sample_id"===e.name)continue;if(void 0===e.default||null===e.default)continue;if(e.source){const t=r(e.source),s=t?l[t.tagName]:void 0;if(null!=s)continue}const{raw:i,ok:n}=resolveDefaultRaw(e,r,l);n&&(s===t&&(s={...t}),s[e.name]=i,e.source&&Promise.resolve().then(()=>o(e.source,i)).catch(e=>{}))}return e&&(s===t&&(s={...t}),s=j(e,s)),s}),n.setConfigurationName(e?e.name:"")},[_,u,o,l,r,n.defaultsAppliedForMethod,n.markDefaultsAppliedForMethod,n.setConfigurationName]),useEffect(()=>{_&&x(e=>{let t=e;for(const s of _.config_fields){if("sample_id"===s.name)continue;if(!s.source)continue;const i=r(s.source);if(!i)continue;const n=l[i.tagName];null!=n&&(t[s.name]!==n&&(t===e&&(t={...e}),t[s.name]=n))}return t})},[_,l,r]),useEffect(()=>{if(!_)return void v(!1);let e=!0;m||(e=!1),u.trim()||(e=!1),f.trim()||(e=!1),e&&!n.projectFieldsLoaded&&(e=!1);for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e=!1;break}if(rangeIssue(t,s)){e=!1;break}}if(v(e),i&&i(e,g),e){const{sample_id:e,...t}=g??{},s={...n.projectFields,...t};a("tis.stage_test",MessageType.Request,{project_id:d,method_id:u,sample_id:f,config:s}).catch(e=>{})}},[g,_,d,u,f,m,n.projectFields,n.projectFieldsLoaded,i,a]);const A=async(e,t)=>{const s=displayToRaw(t,e.scale);if(x({...g,[e.name]:s}),e.source)try{await o(e.source,s)}catch(e){}};if(!_)return _jsx("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:_jsx("h3",{className:"ac-form-section",children:n.schemasLoaded?"No Test Method Selected":"Loading test methods…"})});if(!m)return _jsxs("div",{style:{padding:"1.25rem",maxWidth:"600px"},children:[_jsx("h3",{className:"ac-form-section",children:"No project selected"}),_jsxs("p",{style:{color:"var(--text-secondary-color)",marginTop:"0.5rem"},children:["Pick a project on the ",_jsx("strong",{children:"Project"})," tab first",""!==d.trim()&&` (or click + there to create "${d.trim()}")`,"."]})]});const D={padding:"1.25rem",gridTemplateColumns:"auto 1fr 1.75rem 1.75rem"},F=b&&!n.state.lastStartError;return _jsxs("div",{className:"ac-test-setup-form",children:[_jsxs("div",{className:"ac-form-grid ac-test-setup-form__head",style:D,children:[_jsxs("h3",{className:"ac-form-section",style:{display:"flex",alignItems:"center",gap:"10px"},children:["Test Setup",_jsx(Button,{icon:F?"pi pi-check-circle":"pi pi-exclamation-circle",severity:F?"success":"danger",text:!0,rounded:!0,"aria-label":"Test Setup status",onClick:()=>{const e=(()=>{const e=[];if(m||e.push("No project is selected. Pick or create one on the Project tab."),n.projectFieldsLoaded||e.push("Project fields are still loading."),u.trim()||e.push("No test method selected."),f.trim()||e.push("Sample ID is required."),_)for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e.push(`Required field "${labelOf(t)}" is empty.`);continue}const n=rangeIssue(t,s);n&&e.push(`"${labelOf(t)}" ${n}.`)}return e})(),t=n.state.lastStartError?.trim()??"";let s;s=0!==e.length||t?_jsxs("div",{children:[e.length>0&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:"The form is incomplete:"}),_jsx("ul",{style:{margin:"0 0 1rem 1.25rem"},children:e.map((e,t)=>_jsx("li",{children:e},t))})]}),t&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.25rem 0",fontWeight:600},children:"Last start_test error from the server:"}),_jsx("pre",{style:{margin:0,padding:"0.5rem",background:"rgba(0,0,0,0.25)",borderRadius:"4px",whiteSpace:"pre-wrap",fontSize:"0.875rem"},children:t})]})]}):_jsx("p",{style:{margin:0},children:"All required fields are complete. The test is staged and ready to start."}),k({open:!0,title:"Test Setup Status",body:s})}}),_jsxs("span",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)",fontWeight:"normal",marginLeft:"0.25rem"},children:["project: ",_jsx("strong",{children:d})]})]}),_jsx("span",{className:"ac-form-label",children:"Sample ID"}),_jsx(TextInput,{label:void 0,value:f,onValueChanged:e=>{h(e)},className:f.trim()?"":"p-invalid"}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:f.trim()?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:f.trim()?"pi pi-check":"pi pi-times"})}),c.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Test Method"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:methodLabelOf(u,_),readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>N(!0),tooltip:c.length>1?"Change test method":"View test method details",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:u?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:u?"pi pi-check":"pi pi-times"})})]}),T.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Configuration"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:S?configLabelOf(S):"",placeholder:"No configuration selected",readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>I(!0),tooltip:"Change configuration",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:S?"var(--green-500)":"var(--text-secondary-color)",display:"flex",alignItems:"center"},children:_jsx("i",{className:S?"pi pi-check":"pi pi-minus"})})]})]}),_jsx("div",{className:"ac-test-setup-form__body",children:_jsxs("div",{className:"ac-form-grid",style:D,children:[_jsx("h3",{className:"ac-form-section",children:"Test Configuration"}),_.config_fields.map(e=>{if("sample_id"===e.name)return null;const t=(e=>{const t=g[e.name],s=void 0===t||""===t||null===t;return!(e.required&&s||rangeIssue(e,t))})(e),s=(e=>{const t=_?.asset_refs??[],s=n.projectAssetRefs??[],i=`config.${e.name}`,a=t.find(e=>"by_id_field"===e.select&&e.from===i);if(a)return a.asset_type;const o=s.find(e=>"by_id_field"===e.select&&e.from===i);return o?o.asset_type:null})(e),i=!s&&Array.isArray(e.options)&&e.options.length>0?normalizeOptions(e.options):null,a=!s&&!i&&"string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("span",{className:"ac-form-label",children:labelOf(e)}),s?_jsx(AssetIdPicker,{assetType:s,value:null!=g[e.name]?String(g[e.name]):"",onChange:t=>A(e,t),invalid:!t}):i?_jsx(Dropdown,{value:g[e.name]??null,options:i,onChange:t=>A(e,t.value),placeholder:`Select ${e.label??e.name}…`,className:"ac-dropdown-clearable"+(t?"":" p-invalid"),showClear:!e.required}):a?_jsx(ValueInput,{label:void 0,value:null!=g[e.name]?Number(rawToDisplay(Number(g[e.name]),e.scale)):null,min:e.min,max:e.max,onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}):_jsx(TextInput,{label:void 0,value:null!=g[e.name]?String(g[e.name]):"",onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}),hasDescription(e)?_jsx(Button,{icon:"pi pi-info-circle",text:!0,rounded:!0,"aria-label":`About ${labelOf(e)}`,onClick:()=>k({open:!0,title:labelOf(e),body:_jsx("p",{style:{margin:0,whiteSpace:"pre-wrap"},children:e.description})})}):_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:t?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:t?"pi pi-check":"pi pi-times"})})]},e.name)})]})}),_jsx(TestMethodDialog,{visible:y,onHide:()=>N(!1),currentMethodId:u,onSelected:e=>p(e)}),_jsx(ConfigurationDialog,{visible:C,onHide:()=>I(!1),configurations:T,currentConfigName:n.configurationName,onSelected:e=>{const t=T.find(t=>t.name===e);t&&(x(e=>j(t,{...e})),n.setConfigurationName(e))}}),_jsx(Dialog,{header:w.title,visible:w.open,onHide:()=>k({open:!1,title:"",body:null}),style:{width:"32rem",maxWidth:"90vw"},modal:!0,dismissableMask:!0,children:w.body})]})};
|
package/package.json
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* Created Date: 2026-06-02
|
|
4
4
|
* -----
|
|
5
5
|
*
|
|
6
|
+
* Named for the direction it points (left), not a jog polarity — which
|
|
7
|
+
* axis end "positive" corresponds to varies by machine, so the icon is
|
|
8
|
+
* chosen by direction via the JogPanel `icon` field, decoupled from action.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import type { SVGProps } from "react";
|
|
9
12
|
|
|
10
|
-
export const
|
|
13
|
+
export const SvgJogXLeft = (props: SVGProps<SVGSVGElement>) => (
|
|
11
14
|
<svg
|
|
12
15
|
xmlns="http://www.w3.org/2000/svg"
|
|
13
16
|
width="24"
|
|
@@ -20,11 +23,11 @@ export const SvgJogXNeg = (props: SVGProps<SVGSVGElement>) => (
|
|
|
20
23
|
strokeLinejoin="round"
|
|
21
24
|
{...props}
|
|
22
25
|
>
|
|
23
|
-
{/* Triangle pointing left
|
|
26
|
+
{/* Triangle pointing left */}
|
|
24
27
|
<path d="M10 6 3 12 10 18Z" fill="currentColor" />
|
|
25
28
|
{/* Letter X */}
|
|
26
29
|
<path d="M14 6l8 12M22 6l-8 12" />
|
|
27
30
|
</svg>
|
|
28
31
|
);
|
|
29
32
|
|
|
30
|
-
export default
|
|
33
|
+
export default SvgJogXLeft;
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* Created Date: 2026-06-02
|
|
4
4
|
* -----
|
|
5
5
|
*
|
|
6
|
+
* Named for the direction it points (right), not a jog polarity — which
|
|
7
|
+
* axis end "positive" corresponds to varies by machine, so the icon is
|
|
8
|
+
* chosen by direction via the JogPanel `icon` field, decoupled from action.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import type { SVGProps } from "react";
|
|
9
12
|
|
|
10
|
-
export const
|
|
13
|
+
export const SvgJogXRight = (props: SVGProps<SVGSVGElement>) => (
|
|
11
14
|
<svg
|
|
12
15
|
xmlns="http://www.w3.org/2000/svg"
|
|
13
16
|
width="24"
|
|
@@ -22,9 +25,9 @@ export const SvgJogXPos = (props: SVGProps<SVGSVGElement>) => (
|
|
|
22
25
|
>
|
|
23
26
|
{/* Letter X */}
|
|
24
27
|
<path d="M2 6l8 12M10 6l-8 12" />
|
|
25
|
-
{/* Triangle pointing right
|
|
28
|
+
{/* Triangle pointing right */}
|
|
26
29
|
<path d="M14 6 21 12 14 18Z" fill="currentColor" />
|
|
27
30
|
</svg>
|
|
28
31
|
);
|
|
29
32
|
|
|
30
|
-
export default
|
|
33
|
+
export default SvgJogXRight;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { SVGProps } from "react";
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const SvgJogYDownLeft = (props: SVGProps<SVGSVGElement>) => (
|
|
11
11
|
<svg
|
|
12
12
|
xmlns="http://www.w3.org/2000/svg"
|
|
13
13
|
width="24"
|
|
@@ -27,4 +27,4 @@ export const SvgJogYNeg = (props: SVGProps<SVGSVGElement>) => (
|
|
|
27
27
|
</svg>
|
|
28
28
|
);
|
|
29
29
|
|
|
30
|
-
export default
|
|
30
|
+
export default SvgJogYDownLeft;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { SVGProps } from "react";
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const SvgJogYUpRight = (props: SVGProps<SVGSVGElement>) => (
|
|
11
11
|
<svg
|
|
12
12
|
xmlns="http://www.w3.org/2000/svg"
|
|
13
13
|
width="24"
|
|
@@ -27,4 +27,5 @@ export const SvgJogYPos = (props: SVGProps<SVGSVGElement>) => (
|
|
|
27
27
|
</svg>
|
|
28
28
|
);
|
|
29
29
|
|
|
30
|
-
export default
|
|
30
|
+
export default SvgJogYUpRight;
|
|
31
|
+
|
package/src/assets/index.ts
CHANGED
|
@@ -3,10 +3,10 @@ export { default as Distance } from "./Distance";
|
|
|
3
3
|
export { default as JogLong } from "./JogLong";
|
|
4
4
|
export { default as JogMedium } from "./JogMedium";
|
|
5
5
|
export { default as JogShort } from "./JogShort";
|
|
6
|
-
export { default as
|
|
7
|
-
export { default as
|
|
8
|
-
export { default as
|
|
9
|
-
export { default as
|
|
6
|
+
export { default as JogXRight } from "./JogXRight";
|
|
7
|
+
export { default as JogXLeft } from "./JogXLeft";
|
|
8
|
+
export { default as JogYUpRight } from "./JogYUpRight";
|
|
9
|
+
export { default as JogYDownLeft } from "./JogYDownLeft";
|
|
10
10
|
export { default as JogZPos } from "./JogZPos";
|
|
11
11
|
export { default as JogZNeg } from "./JogZNeg";
|
|
12
12
|
export { default as On } from "./On";
|
|
@@ -19,7 +19,7 @@ import { Distance, JogShort, JogMedium, JogLong } from '../assets';
|
|
|
19
19
|
import { Speed, SpeedSlow, SpeedMedium, SpeedFast } from '../assets';
|
|
20
20
|
import { RotationCcwA, RotationCcwB, RotationCcwC } from '../assets';
|
|
21
21
|
import { RotationCwA, RotationCwB, RotationCwC } from '../assets';
|
|
22
|
-
import {
|
|
22
|
+
import { JogXLeft, JogXRight, JogYDownLeft, JogYUpRight, JogZPos, JogZNeg } from '../assets';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Enumerates the source button or action when an event occurs.
|
|
@@ -80,6 +80,17 @@ export enum JogSpeedAction {
|
|
|
80
80
|
* Jog button definition type for defining the button in the JogPanel.
|
|
81
81
|
*/
|
|
82
82
|
export interface JogPanelButtonDefinition {
|
|
83
|
+
/**
|
|
84
|
+
* Direction key selecting the arrow icon shown on the button, e.g.
|
|
85
|
+
* `jog_x_left`, `jog_x_right`, `jog_y_up_right`, `jog_y_down_left`,
|
|
86
|
+
* `jog_z_up`, `jog_z_down`, or the rotation keys `rot_{a|b|c}_{cw|ccw}`.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately independent of `action`: which physical direction a
|
|
89
|
+
* machine's "jog X positive" moves is not consistent across machines,
|
|
90
|
+
* so the icon (what the operator sees) is chosen here by direction while
|
|
91
|
+
* `action` carries the polarity sent to the control program. See
|
|
92
|
+
* `selectIcon`. Legacy `pi pi-arrow-*` values are still accepted.
|
|
93
|
+
*/
|
|
83
94
|
icon: string;
|
|
84
95
|
alt: string;
|
|
85
96
|
action: JogPanelAction
|
|
@@ -126,18 +137,18 @@ interface JogPanelState {
|
|
|
126
137
|
* A default jog button configuration for linear 3D motion.
|
|
127
138
|
*/
|
|
128
139
|
export const DefaultLinearJogButtons : (JogPanelButtonDefinition | undefined)[][] = [
|
|
129
|
-
[undefined, { icon: "
|
|
130
|
-
[{ icon: "
|
|
131
|
-
[{ icon: "
|
|
140
|
+
[undefined, { icon: "jog_z_up", action: JogPanelAction.zPositive, alt: "Z Positive" }, { icon: "jog_y_up_right", action: JogPanelAction.yNegative, alt: "Y Negative" }],
|
|
141
|
+
[{ icon: "jog_x_left", action: JogPanelAction.xNegative, alt: "X Negative" }, undefined, { icon: "jog_x_right", action: JogPanelAction.xPositive, alt: "X Positive" }],
|
|
142
|
+
[{ icon: "jog_y_down_left", action: JogPanelAction.yPositive, alt: "Y Positive" }, { icon: "jog_z_down", action: JogPanelAction.zNegative, alt: "Z Negative" }, undefined],
|
|
132
143
|
];
|
|
133
144
|
|
|
134
145
|
/**
|
|
135
146
|
* A default jog button configuration for rotational 3D motion.
|
|
136
147
|
*/
|
|
137
148
|
export const DefaultRotationJogButtons : (JogPanelButtonDefinition | undefined)[][] = [
|
|
138
|
-
[{ icon: "
|
|
139
|
-
[{ icon: "
|
|
140
|
-
[undefined, { icon: "
|
|
149
|
+
[{ icon: "rot_b_cw", action: JogPanelAction.bPositive, alt: "Rot B Positive" }, { icon: "rot_c_cw", action: JogPanelAction.cPositive, alt: "Rot C Positive" }, undefined],
|
|
150
|
+
[{ icon: "rot_a_ccw", action: JogPanelAction.aNegative, alt: "Rot A Negative" }, undefined, { icon: "rot_a_cw", action: JogPanelAction.aPositive, alt: "Rot A Positive" }],
|
|
151
|
+
[undefined, { icon: "rot_c_ccw", action: JogPanelAction.cNegative, alt: "Rot C Negative" }, { icon: "rot_b_ccw", action: JogPanelAction.bNegative, alt: "Rot B Negative" }],
|
|
141
152
|
];
|
|
142
153
|
|
|
143
154
|
/**
|
|
@@ -157,10 +168,14 @@ export const DefaultRotationJogButtons : (JogPanelButtonDefinition | undefined)[
|
|
|
157
168
|
* Example:
|
|
158
169
|
* ```
|
|
159
170
|
* export const kDefaultButtonDefinitions : (JogPanelButtonDefinition | undefined)[][] = [
|
|
160
|
-
* [undefined, { icon: "
|
|
161
|
-
* [{ icon: "
|
|
162
|
-
* [{ icon: "
|
|
171
|
+
* [undefined, { icon: "jog_z_up", action: JogPanelAction.zPositive, alt: "Z Positive" }, { icon: "jog_y_down_left", action: JogPanelAction.yNegative, alt: "Y Negative" }],
|
|
172
|
+
* [{ icon: "jog_x_left", action: JogPanelAction.xNegative, alt: "X Negative" }, undefined, { icon: "jog_x_right", action: JogPanelAction.xPositive, alt: "X Positive" }],
|
|
173
|
+
* [{ icon: "jog_y_up_right", action: JogPanelAction.yPositive, alt: "Y Positive" }, { icon: "jog_z_down", action: JogPanelAction.zNegative, alt: "Z Negative" }, undefined],
|
|
163
174
|
* ];
|
|
175
|
+
*
|
|
176
|
+
* // The icon is chosen by direction, not polarity: on a machine where
|
|
177
|
+
* // "X positive" jogs left, give that button `icon: "jog_x_left"` while
|
|
178
|
+
* // keeping `action: JogPanelAction.xPositive`.
|
|
164
179
|
* ```
|
|
165
180
|
*
|
|
166
181
|
*/
|
|
@@ -242,53 +257,42 @@ export class JogPanel extends React.Component<JogPanelProps, JogPanelState> {
|
|
|
242
257
|
</div>
|
|
243
258
|
}
|
|
244
259
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return <
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return <
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return <
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return <
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return <
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return <
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return <RotationCwB />
|
|
281
|
-
}
|
|
282
|
-
else if (action == JogPanelAction.cNegative) {
|
|
283
|
-
return <RotationCcwC />
|
|
284
|
-
}
|
|
285
|
-
else if (action == JogPanelAction.cPositive) {
|
|
286
|
-
return <RotationCwC />
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
return <i />
|
|
260
|
+
/**
|
|
261
|
+
* Map a button's `icon` direction key to its arrow glyph. Decoupled from
|
|
262
|
+
* `action` on purpose: which physical direction "jog X positive" moves is
|
|
263
|
+
* not consistent across machines, so the caller picks the icon by the
|
|
264
|
+
* direction the operator should see. Unknown/empty keys render nothing.
|
|
265
|
+
*
|
|
266
|
+
* The `pi pi-arrow-*` cases are legacy aliases kept so button definitions
|
|
267
|
+
* written before the direction-key scheme (which already encoded the
|
|
268
|
+
* arrow here — it was simply ignored) keep rendering the right glyph.
|
|
269
|
+
*/
|
|
270
|
+
private selectIcon(icon : string | undefined) {
|
|
271
|
+
switch (icon) {
|
|
272
|
+
// Direction keys
|
|
273
|
+
case 'jog_x_left': return <JogXLeft />
|
|
274
|
+
case 'jog_x_right': return <JogXRight />
|
|
275
|
+
case 'jog_y_up_right': return <JogYUpRight />
|
|
276
|
+
case 'jog_y_down_left': return <JogYDownLeft />
|
|
277
|
+
case 'jog_z_up': return <JogZPos />
|
|
278
|
+
case 'jog_z_down': return <JogZNeg />
|
|
279
|
+
case 'rot_a_cw': return <RotationCwA />
|
|
280
|
+
case 'rot_a_ccw': return <RotationCcwA />
|
|
281
|
+
case 'rot_b_cw': return <RotationCwB />
|
|
282
|
+
case 'rot_b_ccw': return <RotationCcwB />
|
|
283
|
+
case 'rot_c_cw': return <RotationCwC />
|
|
284
|
+
case 'rot_c_ccw': return <RotationCcwC />
|
|
285
|
+
|
|
286
|
+
// Legacy PrimeReact arrow-class aliases (back-compat)
|
|
287
|
+
case 'pi pi-arrow-left': return <JogXLeft />
|
|
288
|
+
case 'pi pi-arrow-right': return <JogXRight />
|
|
289
|
+
case 'pi pi-arrow-up': return <JogZPos />
|
|
290
|
+
case 'pi pi-arrow-down': return <JogZNeg />
|
|
291
|
+
case 'pi pi-arrow-up-right': return <JogYUpRight />
|
|
292
|
+
case 'pi pi-arrow-down-left': return <JogYDownLeft />
|
|
293
|
+
|
|
294
|
+
default: return <i />
|
|
290
295
|
}
|
|
291
|
-
|
|
292
296
|
}
|
|
293
297
|
|
|
294
298
|
standardButton(row : number, col : number) {
|
|
@@ -298,14 +302,15 @@ export class JogPanel extends React.Component<JogPanelProps, JogPanelState> {
|
|
|
298
302
|
&& this.props.buttonDefinitions[row][col] !== undefined
|
|
299
303
|
&& this.props.buttonDefinitions[row][col]?.action !== undefined ) {
|
|
300
304
|
|
|
301
|
-
const
|
|
305
|
+
const def = this.props.buttonDefinitions[row][col];
|
|
306
|
+
const action = def?.action;
|
|
302
307
|
|
|
303
308
|
return <Button
|
|
304
309
|
key={`${row}-${col}`}
|
|
305
|
-
tooltip={
|
|
310
|
+
tooltip={def?.alt}
|
|
306
311
|
onClick={() => this.handleClicked(action)}
|
|
307
312
|
className="button-item"
|
|
308
|
-
icon={() => this.
|
|
313
|
+
icon={() => this.selectIcon(def?.icon)}/>
|
|
309
314
|
|
|
310
315
|
}
|
|
311
316
|
else {
|
|
@@ -108,10 +108,17 @@ export const ValueInput: React.FC<ValueInputProps> = ({
|
|
|
108
108
|
}
|
|
109
109
|
}, [value, editing]);
|
|
110
110
|
|
|
111
|
+
// Treat the bounds as an unordered interval: an author may write the range
|
|
112
|
+
// in "magnitude" order (e.g. min:-50, max:-1000 for a compressive load),
|
|
113
|
+
// which is numerically inverted. Without this, min > max yields an empty
|
|
114
|
+
// valid set and the field rejects every value, including its own default.
|
|
115
|
+
const lo = min !== undefined && max !== undefined ? Math.min(min, max) : min;
|
|
116
|
+
const hi = min !== undefined && max !== undefined ? Math.max(min, max) : max;
|
|
117
|
+
|
|
111
118
|
const validate = (val: number | null): boolean => {
|
|
112
119
|
if (val === null || Number.isNaN(val)) return false;
|
|
113
|
-
if (
|
|
114
|
-
if (
|
|
120
|
+
if (lo !== undefined && val < lo) return false;
|
|
121
|
+
if (hi !== undefined && val > hi) return false;
|
|
115
122
|
return true;
|
|
116
123
|
};
|
|
117
124
|
|
|
@@ -148,7 +155,7 @@ export const ValueInput: React.FC<ValueInputProps> = ({
|
|
|
148
155
|
};
|
|
149
156
|
|
|
150
157
|
const isLabelDefined = label !== undefined && label !== null && label !== '';
|
|
151
|
-
const allowNegative =
|
|
158
|
+
const allowNegative = lo === undefined || lo < 0;
|
|
152
159
|
|
|
153
160
|
return (
|
|
154
161
|
<div>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import React, { useEffect, useState } from 'react';
|
|
14
|
+
import { createPortal } from 'react-dom';
|
|
14
15
|
import { Button } from 'primereact/button';
|
|
15
16
|
import { useNetwork } from './NetworkProvider';
|
|
16
17
|
|
|
@@ -38,10 +39,23 @@ export const StagedChangeBanner: React.FC<StagedChangeBannerProps> = ({ style })
|
|
|
38
39
|
const total = Math.max(1, staged.revert_in_seconds);
|
|
39
40
|
const pctRemaining = Math.max(0, Math.min(100, (remainingSec / total) * 100));
|
|
40
41
|
|
|
42
|
+
// NOTE: the banner is rendered through a portal into <body> (see the
|
|
43
|
+
// createPortal call below) and positioned `fixed`, not `sticky`. That is
|
|
44
|
+
// deliberate: host apps commonly wrap their content in a
|
|
45
|
+
// `position: fixed`/`transform`ed shell (which establishes a stacking
|
|
46
|
+
// context) and open configuration screens as PrimeReact *modal* dialogs.
|
|
47
|
+
// A modal Dialog portals its mask to <body> at z-index ~1100+, so a
|
|
48
|
+
// banner living inside the app shell — no matter how high its own
|
|
49
|
+
// z-index — renders *beneath* the modal and becomes unreachable
|
|
50
|
+
// ("press confirm on a popup that is hidden"). Portaling to <body> puts
|
|
51
|
+
// the banner in the root stacking context alongside the modal, where a
|
|
52
|
+
// z-index above the PrimeReact modal range actually wins.
|
|
41
53
|
const containerStyle: React.CSSProperties = {
|
|
42
|
-
position: '
|
|
54
|
+
position: 'fixed',
|
|
43
55
|
top: 0,
|
|
44
|
-
|
|
56
|
+
left: 0,
|
|
57
|
+
right: 0,
|
|
58
|
+
zIndex: 2000,
|
|
45
59
|
padding: '0.75rem 1rem',
|
|
46
60
|
background: remainingSec <= 10 ? '#7f1d1d' : '#78350f',
|
|
47
61
|
color: 'white',
|
|
@@ -50,7 +64,7 @@ export const StagedChangeBanner: React.FC<StagedChangeBannerProps> = ({ style })
|
|
|
50
64
|
...style,
|
|
51
65
|
};
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
const banner = (
|
|
54
68
|
<div style={containerStyle} role="alertdialog" aria-live="polite">
|
|
55
69
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
|
|
56
70
|
<div style={{ minWidth: 0 }}>
|
|
@@ -96,6 +110,11 @@ export const StagedChangeBanner: React.FC<StagedChangeBannerProps> = ({ style })
|
|
|
96
110
|
</div>
|
|
97
111
|
</div>
|
|
98
112
|
);
|
|
113
|
+
|
|
114
|
+
// Guard for non-DOM environments (SSR/tests); render inline if there is
|
|
115
|
+
// no document to portal into.
|
|
116
|
+
if (typeof document === 'undefined' || !document.body) return banner;
|
|
117
|
+
return createPortal(banner, document.body);
|
|
99
118
|
};
|
|
100
119
|
|
|
101
120
|
export default StagedChangeBanner;
|
|
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useContext } from 'react';
|
|
|
2
2
|
import { DataTable } from 'primereact/datatable';
|
|
3
3
|
import { Column } from 'primereact/column';
|
|
4
4
|
import { Button } from 'primereact/button';
|
|
5
|
-
import {
|
|
5
|
+
import { Badge } from 'primereact/badge';
|
|
6
6
|
import { EventEmitterContext } from '../../core/EventEmitterContext';
|
|
7
7
|
import { MessageType } from '../../hub/CommandMessage';
|
|
8
8
|
import { useTis } from './TisProvider';
|
|
@@ -364,17 +364,26 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
|
|
|
364
364
|
style={{ minWidth: '12rem' }}
|
|
365
365
|
/>
|
|
366
366
|
<Column field="method_id" header="Test Method" sortable style={{ minWidth: '10rem' }} />
|
|
367
|
-
|
|
367
|
+
{/* Run ID dropped from the table — it's an internal handle
|
|
368
|
+
(the row's sample_id + date already identify the run for a
|
|
369
|
+
human), and its 12rem column was the main reason the table
|
|
370
|
+
overflowed in portrait. Still carried on the row data and
|
|
371
|
+
used for selection + downloads. */}
|
|
368
372
|
<Column header="Status" field="status" sortable
|
|
369
373
|
body={(rowData) => {
|
|
370
374
|
const s = statusOf(rowData);
|
|
371
|
-
return <
|
|
375
|
+
return <Badge value={s.label} severity={s.severity} />;
|
|
372
376
|
}}
|
|
373
|
-
style={{ minWidth: '
|
|
377
|
+
style={{ minWidth: '6rem' }}
|
|
374
378
|
/>
|
|
379
|
+
{/* Inclusion shown as a single checkmark / eye-slash toggle so
|
|
380
|
+
the column stays narrow. Green check = counted in cross-test
|
|
381
|
+
results; amber eye-slash = excluded. Click toggles. */}
|
|
375
382
|
<Column
|
|
376
|
-
header="
|
|
377
|
-
|
|
383
|
+
header="Incl."
|
|
384
|
+
align="center"
|
|
385
|
+
alignHeader="center"
|
|
386
|
+
style={{ width: '4.5rem' }}
|
|
378
387
|
body={(rowData) => {
|
|
379
388
|
const isExcluded = rowData?.excluded === true;
|
|
380
389
|
const isBusy = excludingRunId === rowData.run_id;
|
|
@@ -382,12 +391,14 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
|
|
|
382
391
|
<Button
|
|
383
392
|
icon={isBusy ? 'pi pi-spin pi-spinner'
|
|
384
393
|
: (isExcluded ? 'pi pi-eye-slash' : 'pi pi-check')}
|
|
385
|
-
|
|
394
|
+
rounded
|
|
395
|
+
text
|
|
386
396
|
size="small"
|
|
387
|
-
outlined
|
|
388
397
|
severity={isExcluded ? 'warning' : 'success'}
|
|
389
398
|
disabled={excludingRunId !== null}
|
|
390
399
|
onClick={() => handleToggleExclude(rowData)}
|
|
400
|
+
aria-label={isExcluded ? 'Excluded — click to include'
|
|
401
|
+
: 'Included — click to exclude'}
|
|
391
402
|
tooltip={isExcluded
|
|
392
403
|
? 'Excluded from cross-test results — click to include'
|
|
393
404
|
: 'Included in cross-test results — click to exclude'}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ScienceTable — "instrument report" styling for cycle/results tables.
|
|
3
|
+
*
|
|
4
|
+
* Every rule is scoped under `.p-datatable.ac-science-table` (both classes on
|
|
5
|
+
* the DataTable root) so it out-specifies the PrimeReact theme's
|
|
6
|
+
* `.p-datatable ...` rules and cannot bleed onto any other table. The palette
|
|
7
|
+
* is a fixed light "lab report" look (dark headers, cream cells) independent
|
|
8
|
+
* of the app theme — that IS the requested Bluehill appearance.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.ac-science-table-wrap {
|
|
12
|
+
border: 1px solid #2b2b2b;
|
|
13
|
+
border-radius: 4px;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
background: #f7f4e9;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Dark title bar with a centered caption, like "Results 1". */
|
|
19
|
+
.ac-science-table-title {
|
|
20
|
+
background: #2b2b2b;
|
|
21
|
+
color: #ffffff;
|
|
22
|
+
text-align: center;
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
letter-spacing: 0.02em;
|
|
25
|
+
padding: 0.4rem 0.75rem;
|
|
26
|
+
font-size: 0.95rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Horizontal scroll for wide results rows without pushing the page sideways. */
|
|
30
|
+
.ac-science-table-scroll {
|
|
31
|
+
overflow-x: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ---- Column header band -------------------------------------------------- */
|
|
35
|
+
.p-datatable.ac-science-table .p-datatable-thead > tr > th {
|
|
36
|
+
background: #3d3d3d;
|
|
37
|
+
color: #ffffff;
|
|
38
|
+
text-align: center;
|
|
39
|
+
border: 1px solid #2b2b2b;
|
|
40
|
+
border-top: none;
|
|
41
|
+
padding: 0.35rem 0.6rem;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
vertical-align: middle;
|
|
44
|
+
}
|
|
45
|
+
.p-datatable.ac-science-table .p-datatable-thead > tr > th .p-column-header-content {
|
|
46
|
+
justify-content: center;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ac-sci-th {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
align-items: center;
|
|
53
|
+
line-height: 1.15;
|
|
54
|
+
}
|
|
55
|
+
.ac-sci-th-unit {
|
|
56
|
+
font-weight: 400;
|
|
57
|
+
font-size: 0.8em;
|
|
58
|
+
opacity: 0.85;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ---- Data cells: cream, dark text, thin gridlines, tabular figures ------- */
|
|
62
|
+
.p-datatable.ac-science-table .p-datatable-tbody > tr {
|
|
63
|
+
background: #f7f4e9;
|
|
64
|
+
color: #1a1a1a;
|
|
65
|
+
}
|
|
66
|
+
.p-datatable.ac-science-table .p-datatable-tbody > tr > td {
|
|
67
|
+
background: #f7f4e9;
|
|
68
|
+
color: #1a1a1a;
|
|
69
|
+
text-align: center;
|
|
70
|
+
border: 1px solid #d9d2b8;
|
|
71
|
+
padding: 0.3rem 0.6rem;
|
|
72
|
+
font-variant-numeric: tabular-nums;
|
|
73
|
+
}
|
|
74
|
+
/* Faint zebra banding keeps long cycle lists readable on the cream base. */
|
|
75
|
+
.p-datatable.ac-science-table .p-datatable-tbody > tr:nth-child(even) > td {
|
|
76
|
+
background: #efe9d4;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ---- Row-index column: dark, matching the header band -------------------- */
|
|
80
|
+
.p-datatable.ac-science-table .p-datatable-thead > tr > th.ac-sci-index,
|
|
81
|
+
.p-datatable.ac-science-table .p-datatable-tbody > tr > td.ac-sci-index {
|
|
82
|
+
background: #3d3d3d;
|
|
83
|
+
color: #ffffff;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
text-align: center;
|
|
86
|
+
border: 1px solid #2b2b2b;
|
|
87
|
+
}
|