@adcops/autocore-react 3.3.75 → 3.3.79
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/components/Indicator.d.ts +29 -52
- package/dist/components/Indicator.d.ts.map +1 -1
- package/dist/components/Indicator.js +1 -1
- package/dist/components/ams/AmsProvider.d.ts +7 -0
- package/dist/components/ams/AmsProvider.d.ts.map +1 -1
- package/dist/components/ams/AssetDetailView.d.ts.map +1 -1
- package/dist/components/ams/AssetDetailView.js +1 -1
- package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
- package/dist/components/ams/AssetRegistryTable.js +1 -1
- package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -1
- package/dist/components/ams/CalibrationEntryDialog.js +1 -1
- package/dist/components/ams/MissingAssetsBanner.d.ts +11 -0
- package/dist/components/ams/MissingAssetsBanner.d.ts.map +1 -0
- package/dist/components/ams/MissingAssetsBanner.js +1 -0
- package/dist/components/ams/PlaceholderHealthPanel.d.ts +3 -0
- package/dist/components/ams/PlaceholderHealthPanel.d.ts.map +1 -0
- package/dist/components/ams/PlaceholderHealthPanel.js +1 -0
- package/dist/components/ams/index.d.ts +2 -0
- package/dist/components/ams/index.d.ts.map +1 -1
- package/dist/components/ams/index.js +1 -1
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/network/NetworkPanel.d.ts +8 -0
- package/dist/components/network/NetworkPanel.d.ts.map +1 -0
- package/dist/components/network/NetworkPanel.js +1 -0
- package/dist/components/network/NetworkProvider.d.ts +72 -0
- package/dist/components/network/NetworkProvider.d.ts.map +1 -0
- package/dist/components/network/NetworkProvider.js +1 -0
- package/dist/components/network/StagedChangeBanner.d.ts +8 -0
- package/dist/components/network/StagedChangeBanner.d.ts.map +1 -0
- package/dist/components/network/StagedChangeBanner.js +1 -0
- package/dist/components/network/index.d.ts +7 -0
- package/dist/components/network/index.d.ts.map +1 -0
- package/dist/components/network/index.js +1 -0
- package/dist/components/tis/ProjectManager.d.ts +7 -0
- package/dist/components/tis/ProjectManager.d.ts.map +1 -0
- package/dist/components/tis/ProjectManager.js +1 -0
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/package.json +1 -1
- package/src/components/Indicator.tsx +177 -162
- package/src/components/ams/AmsProvider.tsx +7 -0
- package/src/components/ams/AssetDetailView.tsx +287 -4
- package/src/components/ams/AssetRegistryTable.tsx +325 -21
- package/src/components/ams/CalibrationEntryDialog.tsx +163 -30
- package/src/components/ams/MissingAssetsBanner.tsx +124 -0
- package/src/components/ams/PlaceholderHealthPanel.tsx +188 -0
- package/src/components/ams/index.ts +2 -0
- package/src/components/index.ts +26 -0
- package/src/components/network/NetworkPanel.tsx +363 -0
- package/src/components/network/NetworkProvider.tsx +349 -0
- package/src/components/network/StagedChangeBanner.tsx +101 -0
- package/src/components/network/index.ts +17 -0
- package/src/components/tis/ProjectManager.tsx +393 -0
- package/src/components/tis/ResultHistoryTable.tsx +126 -188
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useState}from"react";import{Button}from"primereact/button";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{confirmDialog}from"primereact/confirmdialog";import{ProgressBar}from"primereact/progressbar";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const formatBytes=e=>{if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KiB","MiB","GiB","TiB","PiB"];let r=0,s=e;for(;s>=1024&&r<t.length-1;)s/=1024,r++;return`${s.toFixed(s>=100?0:s>=10?1:2)} ${t[r]}`},formatDate=e=>{if(!e)return"";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()};export const ProjectManager=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,{invoke:s}=useContext(EventEmitterContext),[a,i]=useState([]),[o,n]=useState(!1),[l,c]=useState(null),[d,m]=useState(!1),[p,u]=useState(!1),[f,h]=useState(null),[g,_]=useState(""),y=useCallback(async()=>{if(r){n(!0);try{const e=await s("tis.list_tests",MessageType.Request,{project_id:r});e?.success&&e.data?.tests?i(e.data.tests):i([])}catch(e){i([])}finally{n(!1)}}else i([])},[r,s]),j=useCallback(async()=>{try{const e=await s("tis.disk_usage",MessageType.Request,{});e?.success&&e.data?(h(e.data),_("")):(h(null),_(e?.error_message??"disk_usage failed"))}catch(e){h(null),_(e instanceof Error?e.message:String(e))}},[s]);useEffect(()=>{y()},[y,t.state.activeRunId]),useEffect(()=>{j()},[j]);const b=e=>{const a=e?.run_id??"",i=e?.sample_id??"";confirmDialog({header:"Delete test",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete run ",_jsx("code",{children:a}),i?_jsxs(_Fragment,{children:[" (sample ",_jsx("code",{children:i}),")"]}):null,"?"]}),_jsxs("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:["Removes ",_jsx("code",{children:"test.json"}),", cycles, raw and filtered data for this run. This action cannot be undone."]})]}),accept:()=>{(async e=>{const a=e?.run_id,i=e?.method_id;if(r&&i&&a){c(a);try{const e=await s("tis.delete_test",MessageType.Request,{project_id:r,method_id:i,run_id:a});if(!e?.success)return void alert(`Failed to delete test ${a}`+(e?.error_message?`: ${e.error_message}`:""));await y(),t.selection.runId===a&&t.setSelection({runId:null})}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{c(null)}}})(e)}})},x=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const r=e?.config;return r&&"object"==typeof r&&"string"==typeof r.sample_id?r.sample_id:""},v=f&&f.total_bytes>0?Math.min(100,Math.round(f.used_bytes/f.total_bytes*100)):0;return _jsxs("div",{style:{width:"100%",maxWidth:"100%",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:r?`Project Manager: ${r}`:"Project Manager (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center",flexWrap:"wrap"},children:[_jsx(Button,{icon:p?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!r||p||d,onClick:async()=>{if(r){u(!0);try{const e=await s("tis.export_project_zip",MessageType.Request,{project_id:r});if(!e?.success)return void alert("Failed to build project archive"+(e?.error_message?`: ${e.error_message}`:""));const t="string"==typeof e.data?.download_url?e.data.download_url:"";if(!t)return void alert("Server did not return a download URL for the archive.");const a=document.createElement("a");a.href=t,a.download="string"==typeof e.data?.filename?e.data.filename:`${r}_project_archive.zip`,document.body.appendChild(a),a.click(),a.remove()}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{u(!1)}}},tooltip:"Download a ZIP of the entire project directory",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:d?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete Project",size:"small",severity:"danger",disabled:!r||d||p,onClick:()=>{if(!r)return;const e=a.length;confirmDialog({header:"Delete project",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete Project",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete project ",_jsx("code",{children:r})," and all ",_jsx("strong",{children:e})," test",1===e?"":"s"," inside it?"]}),_jsx("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:"This removes every method, run, cycle, and raw/filtered blob under the project. Consider downloading the archive first. This action cannot be undone."})]}),accept:()=>{(async()=>{if(r){m(!0);try{const e=await s("tis.delete_project",MessageType.Request,{project_id:r});if(!e?.success)return void alert(`Failed to delete project ${r}`+(e?.error_message?`: ${e.error_message}`:""));t.setSelection({projectId:null,methodId:null,sampleId:null,runId:null}),await t.refreshProjects(),await j(),i([])}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(!1)}}})()}})},tooltip:"Permanently delete the project and every test inside it",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:()=>{y(),j()},disabled:o})]})]}),_jsxs("div",{style:{marginBottom:"1rem",padding:"0.75rem 1rem",border:"1px solid #2a2a2a",borderRadius:4,background:"#161616"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",flexWrap:"wrap",gap:"0.5rem"},children:[_jsx("strong",{children:"Server Disk Space"}),f?_jsxs("span",{style:{fontSize:"0.875rem",color:"#9ca3af"},children:[formatBytes(f.available_bytes)," free of ",formatBytes(f.total_bytes)," ","(",100-v,"% available)"]}):_jsx("span",g?{style:{fontSize:"0.875rem",color:"#f87171"},children:g}:{style:{fontSize:"0.875rem",color:"#9ca3af"},children:"Loading…"})]}),f&&_jsxs(_Fragment,{children:[_jsx(ProgressBar,{value:v,showValue:!1,style:{height:"0.5rem",marginTop:"0.5rem"},color:v>=90?"#dc2626":v>=75?"#f59e0b":void 0}),_jsx("div",{style:{fontSize:"0.75rem",color:"#6b7280",marginTop:"0.4rem"},children:_jsx("code",{children:f.base_directory})})]})]}),_jsxs(DataTable,{value:a,loading:o,paginator:!0,rows:10,emptyMessage:r?"No tests in this project.":"Select a project to manage.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},children:[_jsx(Column,{header:"Sample ID",body:x,sortable:!0,sortFunction:e=>{const t=[...e.data];return t.sort((t,r)=>x(t).localeCompare(x(r))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>formatDate(e.start_time),style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Action",style:{width:"8rem"},body:e=>{const t=l===e.run_id;return _jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete",size:"small",severity:"danger",outlined:!0,disabled:null!==l||d,onClick:()=>b(e),tooltip:"Permanently delete this test",tooltipOptions:{position:"left"}})}})]})]})};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAwBD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAwRhE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const downloadCsvBlob=(e,t)=>{const o=new Blob([t],{type:"text/csv;charset=utf-8;"}),r=URL.createObjectURL(o),i=document.createElement("a");i.href=r,i.download=e,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(r)};export const ResultHistoryTable=e=>{const t=useTis(),o=e.projectId??t.selection.projectId,r=e.methodId,[i,s]=useState([]),[a,n]=useState(!1),[l,d]=useState(null),[c,p]=useState(null),{invoke:m}=useContext(EventEmitterContext),u=async()=>{if(o){n(!0);try{const e={project_id:o};r&&(e.method_id=r);const t=await m("tis.list_tests",MessageType.Request,e);t.success&&t.data&&t.data.tests&&s(t.data.tests)}catch(e){}n(!1)}else s([])};useEffect(()=>{u()},[o,r,t.state.activeRunId]);const f=async(e,t)=>{const i=e?.run_id,s=e?.method_id??r;if(!i||!s||!o)return;const a="report"===t?"tis.export_test_csv":"tis.export_test_data_csv",n="report"===t?"test report":"test data";d({runId:i,kind:t});try{const e=await m(a,MessageType.Request,{project_id:o,method_id:s,run_id:i});if(!e?.success)return void alert(`Failed to build ${n} for ${i}`+(e?.error_message?`: ${e.error_message}`:""));const r="string"==typeof e.data?.csv?e.data.csv:"";if(!r)return void alert(`${n} for ${i} is empty.`);const l="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${o}_${s}_${i}_${t}.csv`;downloadCsvBlob(l,r)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},_=async e=>{if(!o)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",r="report"===e?"project report":"project archive";p(e);try{const i=await m(t,MessageType.Request,{project_id:o});if(!i?.success)return void alert(`Failed to build ${r}`+(i?.error_message?`: ${i.error_message}`:""));if("report"===e){const e="string"==typeof i.data?.csv?i.data.csv:"";if(!e)return void alert(`Project ${o} has no tests to report.`);const t="string"==typeof i.data?.filename&&i.data.filename?i.data.filename:`${o}_project_report.csv`;downloadCsvBlob(t,e)}else{const e="string"==typeof i.data?.download_url?i.data.download_url:"";if(!e)return void alert(`Server did not return a download URL for ${r}.`);const t=document.createElement("a");t.href=e,t.download="string"==typeof i.data?.filename?i.data.filename:`${o}_project_archive.zip`,document.body.appendChild(t),t.click(),t.remove()}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},h=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const o=e?.config;return o&&"object"==typeof o&&"string"==typeof o.sample_id?o.sample_id:""};return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:o?`Test History: ${o}${r?` / ${r}`:""}`:"Test History (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center"},children:[_jsx(Button,{icon:"report"===c?"pi pi-spin pi-spinner":"pi pi-file",label:"Download Report",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>_("report"),tooltip:"Download a CSV report of every test in this project",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"archive"===c?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>_("archive"),tooltip:"Download a ZIP of the entire project directory (all tests, raw data, configs)",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:u,disabled:a})]})]}),_jsxs(DataTable,{value:i,loading:a,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},selectionMode:"single",onSelectionChange:e=>{const o=e.value;o?.run_id&&t.setSelection({projectId:o.project_id??null,methodId:o.method_id??null,runId:o.run_id})},children:[_jsx(Column,{header:"Sample ID",sortable:!0,body:h,sortFunction:e=>{const t=[...e.data];return t.sort((t,o)=>h(t).localeCompare(h(o))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Download",style:{width:"14rem"},body:e=>{const t=l?.runId===e.run_id&&"data"===l?.kind,o=l?.runId===e.run_id&&"report"===l?.kind,r=null!==l;return _jsxs("div",{style:{display:"flex",gap:"0.4rem"},children:[_jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-download",label:"Data",size:"small",outlined:!0,disabled:r,onClick:()=>f(e,"data"),tooltip:"Download raw + filtered trace data as one CSV",tooltipOptions:{position:"left"}}),_jsx(Button,{icon:o?"pi pi-spin pi-spinner":"pi pi-file",label:"Report",size:"small",outlined:!0,disabled:r,onClick:()=>f(e,"report"),tooltip:"Download a CSV report (metadata + cycles + results) for this test",tooltipOptions:{position:"left"}})]})}})]})]})};
|
package/package.json
CHANGED
|
@@ -1,194 +1,209 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* <Indicator> — non-interactive visual state lamp.
|
|
5
|
+
*
|
|
6
|
+
* Two usage shapes are supported:
|
|
7
|
+
*
|
|
8
|
+
* 1. Icon / children mode (matches <IndicatorButton>):
|
|
9
|
+
*
|
|
10
|
+
* <Indicator
|
|
11
|
+
* className="ac-toolbar-icon-btn"
|
|
12
|
+
* onColor={IndicatorColor.IndicatorGreen}
|
|
13
|
+
* offColor={IndicatorColor.IndicatorRed}
|
|
14
|
+
* value={controlPowerOk}
|
|
15
|
+
* >
|
|
16
|
+
* <i className="pi pi-power-off" />
|
|
17
|
+
* </Indicator>
|
|
18
|
+
*
|
|
19
|
+
* Renders a square (non-rounded) box whose background is driven by
|
|
20
|
+
* `value` (and `onColor` / `offColor`). The children are layered on
|
|
21
|
+
* top — typically an icon — and the box gets a subtle inset shadow
|
|
22
|
+
* so it visually reads as a passive indicator rather than a button.
|
|
23
|
+
*
|
|
24
|
+
* 2. Legacy label mode:
|
|
25
|
+
*
|
|
26
|
+
* <Indicator label="Pump Running" value={pumpOk} />
|
|
27
|
+
*
|
|
28
|
+
* A small color swatch followed by a text label. Kept for callers
|
|
29
|
+
* that pre-date the icon form.
|
|
30
|
+
*
|
|
31
|
+
* State source priority: explicit `value` prop > subscribed `topic` value.
|
|
32
|
+
* `undefined` displays as `invalidColor` (default black) — meaning "not
|
|
33
|
+
* available," not "off."
|
|
8
34
|
*/
|
|
9
35
|
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
//import clsx from 'clsx';
|
|
36
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
37
|
+
import clsx from 'clsx';
|
|
13
38
|
import { EventEmitterContext, type EventEmitterContextType } from '../core/EventEmitterContext';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export {IndicatorColor}
|
|
17
|
-
|
|
18
|
-
//import {Lamp} from "./Lamp";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
interface IndicatorProps {
|
|
24
|
-
|
|
39
|
+
import { IndicatorColor } from '../core/IndicatorColor';
|
|
40
|
+
export { IndicatorColor };
|
|
25
41
|
|
|
42
|
+
export interface IndicatorProps {
|
|
26
43
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* ## Examples
|
|
30
|
-
* ```
|
|
31
|
-
* <Indicator label="Simple Text" />
|
|
32
|
-
* <Indicator label={<span><i className="pi pi-check"></i> Icon and Text</span>} />
|
|
33
|
-
* <Indicator label={<YourCustomIconComponent />} />
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
label? : React.ReactNode;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* State to be displayed<br/>
|
|
41
|
-
*
|
|
42
|
-
* Available states: **on**, **off** and **not available**:
|
|
44
|
+
* State to be displayed.
|
|
43
45
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*/
|
|
48
|
-
value?: boolean | undefined;
|
|
49
|
-
/**
|
|
50
|
-
* CSS color for **enabled** state
|
|
46
|
+
* * `true` → on
|
|
47
|
+
* * `false` → off
|
|
48
|
+
* * `undefined` → not available (invalid color)
|
|
51
49
|
*/
|
|
50
|
+
value?: boolean;
|
|
51
|
+
|
|
52
|
+
/** CSS color when value is true. Defaults to IndicatorColor.IndicatorGreen. */
|
|
52
53
|
onColor?: string;
|
|
53
|
-
/**
|
|
54
|
-
* CSS color for **disabled** state
|
|
55
|
-
*/
|
|
54
|
+
/** CSS color when value is false. Defaults to IndicatorColor.IndicatorOff (gray). */
|
|
56
55
|
offColor?: string;
|
|
57
|
-
/**
|
|
58
|
-
* CSS color for **not available** state
|
|
59
|
-
*/
|
|
56
|
+
/** CSS color when value is undefined. Defaults to IndicatorColor.IndicatorInvalid (black). */
|
|
60
57
|
invalidColor?: string;
|
|
61
|
-
/**
|
|
62
|
-
* Custom class name to be attached
|
|
63
|
-
*/
|
|
64
|
-
className?: string;
|
|
65
58
|
|
|
66
59
|
/**
|
|
67
|
-
*
|
|
60
|
+
* Optional broadcast topic to subscribe to. The latest broadcast value
|
|
61
|
+
* is used when `value` is left undefined; an explicit `value` always
|
|
62
|
+
* wins so callers can drive the indicator from React state instead.
|
|
68
63
|
*/
|
|
69
64
|
topic?: string;
|
|
70
65
|
|
|
66
|
+
/** Additional CSS class names applied to the outer box. */
|
|
67
|
+
className?: string;
|
|
71
68
|
|
|
72
|
-
|
|
69
|
+
/** Inline style overrides for the outer box. */
|
|
70
|
+
style?: React.CSSProperties;
|
|
73
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Legacy text label rendered next to a small color swatch. Mutually
|
|
74
|
+
* exclusive with `children` — pass one or the other. Plain strings or
|
|
75
|
+
* React nodes both work.
|
|
76
|
+
*/
|
|
77
|
+
label?: React.ReactNode;
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Icon (or any node) to display on top of the colored backdrop.
|
|
81
|
+
* Use this for the modern square-button-shaped indicator, e.g.
|
|
82
|
+
* `<Indicator ...><i className="pi pi-power-off" /></Indicator>`.
|
|
83
|
+
*/
|
|
84
|
+
children?: React.ReactNode;
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Pick the color to paint based on the displayed state. Kept outside the
|
|
89
|
+
* component so the rendering logic is trivial to read.
|
|
90
|
+
*/
|
|
91
|
+
const pickColor = (
|
|
92
|
+
displayValue: boolean | undefined,
|
|
93
|
+
onColor: string,
|
|
94
|
+
offColor: string,
|
|
95
|
+
invalidColor: string,
|
|
96
|
+
): string => {
|
|
97
|
+
if (displayValue === true) return onColor;
|
|
98
|
+
if (displayValue === false) return offColor;
|
|
99
|
+
return invalidColor;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const Indicator: React.FC<IndicatorProps> = ({
|
|
103
|
+
value,
|
|
104
|
+
onColor = IndicatorColor.IndicatorGreen,
|
|
105
|
+
offColor = IndicatorColor.IndicatorOff,
|
|
106
|
+
invalidColor = IndicatorColor.IndicatorInvalid,
|
|
107
|
+
topic,
|
|
108
|
+
className,
|
|
109
|
+
style,
|
|
110
|
+
label,
|
|
111
|
+
children,
|
|
112
|
+
}) => {
|
|
113
|
+
const { subscribe, unsubscribe } = useContext(EventEmitterContext) as EventEmitterContextType;
|
|
114
|
+
|
|
115
|
+
const [subscribedValue, setSubscribedValue] = useState<boolean | undefined>(undefined);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!topic || !subscribe) return;
|
|
119
|
+
const token = subscribe(topic, (v: any) => {
|
|
120
|
+
// The Hub publishes any payload; coerce to boolean for boolean
|
|
121
|
+
// indicators. Non-boolean topics should use IndicatorRect instead.
|
|
122
|
+
setSubscribedValue(v === undefined || v === null ? undefined : !!v);
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
try {
|
|
126
|
+
if (unsubscribe && token !== undefined) unsubscribe(token);
|
|
127
|
+
} catch {
|
|
128
|
+
/* no-op */
|
|
129
|
+
}
|
|
97
130
|
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
componentDidMount() {
|
|
101
|
-
const { topic } = this.props;
|
|
102
|
-
if (topic && this.unsubscribeTopicId === null) {
|
|
103
|
-
const { subscribe } = this.context as EventEmitterContextType;
|
|
104
|
-
this.unsubscribeTopicId = subscribe(topic, (value: any) => {
|
|
105
|
-
this.setState({ subscribedValue: value });
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
componentDidUpdate(prevProps: IndicatorProps) {
|
|
111
|
-
if (prevProps.value !== this.props.value) {
|
|
112
|
-
this.setState({ subscribedValue: this.props.value });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
componentWillUnmount() {
|
|
117
|
-
if (this.unsubscribeTopicId !== null) {
|
|
118
|
-
const { unsubscribe } = this.context as EventEmitterContextType;
|
|
119
|
-
unsubscribe(this.unsubscribeTopicId);
|
|
120
|
-
this.unsubscribeTopicId = null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
131
|
+
}, [topic, subscribe, unsubscribe]);
|
|
123
132
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
// Load the value from the proper source.
|
|
130
|
-
// If the `value` property is undefined, that takes precedence.
|
|
131
|
-
//
|
|
132
|
-
let displayValue = subscribedValue;
|
|
133
|
-
if (value !== undefined) {
|
|
134
|
-
displayValue = value;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
let color;
|
|
138
|
-
switch (displayValue) {
|
|
139
|
-
case true:
|
|
140
|
-
color = onColor;
|
|
141
|
-
break;
|
|
142
|
-
case false:
|
|
143
|
-
color = offColor;
|
|
144
|
-
break;
|
|
145
|
-
default:
|
|
146
|
-
color = invalidColor;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// const style: React.CSSProperties = {
|
|
151
|
-
// position: useAbsolutePositioning ? 'absolute' : 'relative',
|
|
152
|
-
// display: useAbsolutePositioning ? "" : "inline-block",
|
|
153
|
-
// verticalAlign: 'middle',
|
|
154
|
-
// top: useAbsolutePositioning && y ? `${yOffset + adjScale * y}px` : undefined,
|
|
155
|
-
// left: useAbsolutePositioning && x ? `${xOffset + adjScale * x}px` : undefined,
|
|
156
|
-
// width: adjWidth ? `${adjWidth * adjScale}px` : undefined,
|
|
157
|
-
// height: adjHeight ? `${adjHeight * adjScale}px` : undefined,
|
|
158
|
-
// lineHeight: adjHeight ? `${adjHeight * adjScale}px` : undefined,
|
|
159
|
-
// backgroundColor: color,
|
|
160
|
-
// borderRadius: "20px"
|
|
161
|
-
// };
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const groupStyle : React.CSSProperties = {
|
|
165
|
-
alignItems: "center",
|
|
166
|
-
// PrimeReact injects `.p-inputgroup { width: 100% }`, which makes
|
|
167
|
-
// this Indicator hog its parent flex row and squeeze siblings
|
|
168
|
-
// (e.g. an adjacent ToggleGroup's buttons wrap vertically).
|
|
169
|
-
// Size to content instead.
|
|
170
|
-
width: "auto",
|
|
171
|
-
flex: "0 0 auto",
|
|
172
|
-
};
|
|
133
|
+
// Explicit value prop wins over the subscription.
|
|
134
|
+
const displayValue = value !== undefined ? value : subscribedValue;
|
|
135
|
+
const color = pickColor(displayValue, onColor, offColor, invalidColor);
|
|
173
136
|
|
|
174
|
-
|
|
137
|
+
// Children mode: square, filled, sunken look. Sized by the caller's
|
|
138
|
+
// className (e.g. `ac-toolbar-icon-btn`) so it lines up with sibling
|
|
139
|
+
// IndicatorButtons in the same toolbar.
|
|
140
|
+
if (children !== undefined && children !== null) {
|
|
141
|
+
const boxStyle: React.CSSProperties = {
|
|
175
142
|
backgroundColor: color,
|
|
176
|
-
|
|
143
|
+
color: 'white',
|
|
144
|
+
display: 'inline-flex',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
justifyContent: 'center',
|
|
147
|
+
// Square corners distinguish a passive indicator from an
|
|
148
|
+
// interactive button (PrimeReact buttons round to ~6px).
|
|
149
|
+
borderRadius: 0,
|
|
150
|
+
// Classic Win95-style chiseled-in bevel: dark shadow on
|
|
151
|
+
// the top + left (the "wall" above the recess casts a
|
|
152
|
+
// shadow into it) plus a lighter highlight on the bottom +
|
|
153
|
+
// right (light bouncing back out). The previous single
|
|
154
|
+
// top-only shadow read as raised because the bottom edge
|
|
155
|
+
// had nothing to contrast it. The shadow opacities are
|
|
156
|
+
// chosen to remain legible against every IndicatorColor
|
|
157
|
+
// — green, red, orange, blue, gray, black.
|
|
158
|
+
boxShadow:
|
|
159
|
+
'inset 2px 2px 3px rgba(0, 0, 0, 0.55), ' +
|
|
160
|
+
'inset -1px -1px 2px rgba(255, 255, 255, 0.20)',
|
|
161
|
+
boxSizing: 'border-box',
|
|
162
|
+
...style,
|
|
177
163
|
};
|
|
178
|
-
|
|
179
164
|
return (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
</div>
|
|
189
|
-
|
|
165
|
+
<span
|
|
166
|
+
className={clsx('indicator', className)}
|
|
167
|
+
style={boxStyle}
|
|
168
|
+
role="status"
|
|
169
|
+
aria-label={typeof label === 'string' ? label : undefined}
|
|
170
|
+
>
|
|
171
|
+
{children}
|
|
172
|
+
</span>
|
|
190
173
|
);
|
|
191
174
|
}
|
|
192
|
-
|
|
175
|
+
|
|
176
|
+
// Legacy label mode: small color swatch + text. Kept so existing
|
|
177
|
+
// <Indicator label="..." /> callers don't have to migrate. The
|
|
178
|
+
// `p-inputgroup` width:auto guard prevents PrimeReact from stretching
|
|
179
|
+
// this to fill its parent flex row.
|
|
180
|
+
const groupStyle: React.CSSProperties = {
|
|
181
|
+
alignItems: 'center',
|
|
182
|
+
width: 'auto',
|
|
183
|
+
flex: '0 0 auto',
|
|
184
|
+
...style,
|
|
185
|
+
};
|
|
186
|
+
const lampStyle: React.CSSProperties = {
|
|
187
|
+
backgroundColor: color,
|
|
188
|
+
width: '22px',
|
|
189
|
+
borderRadius: 0,
|
|
190
|
+
// Same Win95-sunken treatment as the children-mode box, dialed
|
|
191
|
+
// down a touch since the swatch is only 22px wide.
|
|
192
|
+
boxShadow:
|
|
193
|
+
'inset 1px 1px 2px rgba(0, 0, 0, 0.5), ' +
|
|
194
|
+
'inset -1px -1px 1px rgba(255, 255, 255, 0.18)',
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div className={clsx('p-inputgroup indicator', className)} style={groupStyle} role="status">
|
|
199
|
+
<span className="p-inputgroup-addon" style={lampStyle}>
|
|
200
|
+
|
|
201
|
+
</span>
|
|
202
|
+
{label !== undefined && (
|
|
203
|
+
<span className="p-inputgroup-addon">{label}</span>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
193
208
|
|
|
194
209
|
export default Indicator;
|
|
@@ -48,6 +48,13 @@ export interface AmsRole {
|
|
|
48
48
|
description: string | null;
|
|
49
49
|
/** Test method IDs whose start_test will pick up an asset in this role. */
|
|
50
50
|
used_by: string[];
|
|
51
|
+
/** Module names whose configs reference this role via
|
|
52
|
+
* `${ams.by_location.<location>.*}` placeholders. Surfaces in the
|
|
53
|
+
* AIS Role dropdown so the operator can see *why* a role exists
|
|
54
|
+
* even when no test method directly resolves it (e.g., a load_cell
|
|
55
|
+
* feeding NI bridge channel calibration). Empty for purely
|
|
56
|
+
* test-method-driven roles. */
|
|
57
|
+
used_by_modules: string[];
|
|
51
58
|
}
|
|
52
59
|
export type AmsRoleRegistry = { [assetType: string]: AmsRole[] };
|
|
53
60
|
|