diff --git a/datasets/assetData/index.js b/datasets/assetData/index.js new file mode 100644 index 0000000..af0ccda --- /dev/null +++ b/datasets/assetData/index.js @@ -0,0 +1,89 @@ +const fs = require('fs'); +const path = require('path'); + +class AssetCategoryManager { + constructor(relPath = '.') { + this.assetDir = path.resolve(__dirname, relPath); + this.cache = new Map(); + } + + getCategory(softwareType) { + if (!softwareType) { + throw new Error('softwareType is required'); + } + + if (this.cache.has(softwareType)) { + return this.cache.get(softwareType); + } + + const filePath = path.resolve(this.assetDir, `${softwareType}.json`); + if (!fs.existsSync(filePath)) { + throw new Error(`Asset data '${softwareType}' not found in ${this.assetDir}`); + } + + const raw = fs.readFileSync(filePath, 'utf8'); + const parsed = JSON.parse(raw); + this.cache.set(softwareType, parsed); + return parsed; + } + + hasCategory(softwareType) { + const filePath = path.resolve(this.assetDir, `${softwareType}.json`); + return fs.existsSync(filePath); + } + + listCategories({ withMeta = false } = {}) { + const files = fs.readdirSync(this.assetDir, { withFileTypes: true }); + + return files + .filter( + (entry) => + entry.isFile() && + entry.name.endsWith('.json') && + entry.name !== 'index.json' && + entry.name !== 'assetData.json' + ) + .map((entry) => path.basename(entry.name, '.json')) + .map((name) => { + if (!withMeta) { + return name; + } + + const data = this.getCategory(name); + return { + softwareType: data.softwareType || name, + label: data.label || name, + file: `${name}.json` + }; + }); + } + + searchCategories(query) { + const term = (query || '').trim().toLowerCase(); + if (!term) { + return []; + } + + return this.listCategories({ withMeta: true }).filter( + ({ softwareType, label }) => + softwareType.toLowerCase().includes(term) || + label.toLowerCase().includes(term) + ); + } + + clearCache() { + this.cache.clear(); + } +} + +const assetCategoryManager = new AssetCategoryManager(); + +module.exports = { + AssetCategoryManager, + assetCategoryManager, + getCategory: (softwareType) => assetCategoryManager.getCategory(softwareType), + listCategories: (options) => assetCategoryManager.listCategories(options), + searchCategories: (query) => assetCategoryManager.searchCategories(query), + hasCategory: (softwareType) => assetCategoryManager.hasCategory(softwareType), + clearCache: () => assetCategoryManager.clearCache() +}; diff --git a/datasets/assetData/machine.json b/datasets/assetData/machine.json new file mode 100644 index 0000000..04c8563 --- /dev/null +++ b/datasets/assetData/machine.json @@ -0,0 +1,21 @@ +{ + "id": "machine", + "label": "machine", + "softwareType": "machine", + "suppliers": [ + { + "id": "hidrostal", + "name": "Hidrostal", + "types": [ + { + "id": "pump-centrifugal", + "name": "Centrifugal", + "models": [ + { "id": "hidrostal-H05K-S03R", "name": "hidrostal-H05K-S03R", "units": ["l/s","m3/h"] }, + { "id": "hidrostal-C5-D03R-SHN1", "name": "hidrostal-C5-D03R-SHN1", "units": ["l/s"] } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/datasets/assetData/measurement.json b/datasets/assetData/measurement.json new file mode 100644 index 0000000..04e34ad --- /dev/null +++ b/datasets/assetData/measurement.json @@ -0,0 +1,52 @@ +{ + "id": "sensor", + "label": "Sensor", + "softwareType": "measurement", + "suppliers": [ + { + "id": "vega", + "name": "Vega", + "types": [ + { + "id": "temperature", + "name": "Temperature", + "models": [ + { "id": "vega-temp-10", "name": "VegaTemp 10", "units": ["degC", "degF"] }, + { "id": "vega-temp-20", "name": "VegaTemp 20", "units": ["degC", "degF"] } + ] + }, + { + "id": "pressure", + "name": "Pressure", + "models": [ + { "id": "vega-pressure-10", "name": "VegaPressure 10", "units": ["bar", "mbar", "psi"] }, + { "id": "vega-pressure-20", "name": "VegaPressure 20", "units": ["bar", "mbar", "psi"] } + ] + }, + { + "id": "flow", + "name": "Flow", + "models": [ + { "id": "vega-flow-10", "name": "VegaFlow 10", "units": ["m3/h", "gpm", "l/min"] }, + { "id": "vega-flow-20", "name": "VegaFlow 20", "units": ["m3/h", "gpm", "l/min"] } + ] + }, + { + "id": "level", + "name": "Level", + "models": [ + { "id": "vega-level-10", "name": "VegaLevel 10", "units": ["m", "ft", "mm"] }, + { "id": "vega-level-20", "name": "VegaLevel 20", "units": ["m", "ft", "mm"] } + ] + }, + { + "id": "oxygen", + "name": "Quantity (oxygen)", + "models": [ + { "id": "vega-oxy-10", "name": "VegaOxySense 10", "units": ["g/m3", "mol/m3"] } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/datasets/assetData/modelData/ECDV.json b/datasets/assetData/modelData/ECDV.json new file mode 100644 index 0000000..895c38e --- /dev/null +++ b/datasets/assetData/modelData/ECDV.json @@ -0,0 +1,16 @@ +{ + "1.204": { + "125": { + "x": [0,10,20,30,40,50,60,70,80,90,100], + "y": [0,18,50,95,150,216,337,564,882,1398,1870] + }, + "150": { + "x": [0,10,20,30,40,50,60,70,80,90,100], + "y": [0,25,73,138,217,314,490,818,1281,2029,2715] + }, + "400": { + "x": [0,10,20,30,40,50,60,70,80,90,100], + "y": [0,155,443,839,1322,1911,2982,4980,7795,12349,16524] + } + } +} \ No newline at end of file diff --git a/datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json b/datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json new file mode 100644 index 0000000..2ea753b --- /dev/null +++ b/datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json @@ -0,0 +1,838 @@ +{ + "np": { + "400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5953611390998625, + 1.6935085477165994, + 3.801139124304824, + 7.367829525776738, + 12.081735423116616 + ] + }, + "500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.8497068236812997, + 3.801139124304824, + 7.367829525776738, + 12.081735423116616 + ] + }, + "600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.7497197821018213, + 3.801139124304824, + 7.367829525776738, + 12.081735423116616 + ] + }, + "700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.788320579602724, + 3.9982668237045984, + 7.367829525776738, + 12.081735423116616 + ] + }, + "800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.7824519364844427, + 3.9885060367793064, + 7.367829525776738, + 12.081735423116616 + ] + }, + "900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6934482683506376, + 3.9879559558537054, + 7.367829525776738, + 12.081735423116616 + ] + }, + "1000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6954385513069579, + 4.0743508382926795, + 7.422392692482345, + 12.081735423116616 + ] + }, + "1100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.160745720731654, + 7.596626714476177, + 12.081735423116616 + ] + }, + "1200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.302551231007837, + 7.637247864947884, + 12.081735423116616 + ] + }, + "1300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.37557913990704, + 7.773442147000839, + 12.081735423116616 + ] + }, + "1400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.334434337766139, + 7.940911352646818, + 12.081735423116616 + ] + }, + "1500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.2327206586037995, + 8.005238800611183, + 12.254836577088351 + ] + }, + "1600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.195405588464695, + 7.991827302945298, + 12.423663269044452 + ] + }, + "1700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 14.255458319309813, + 8.096768422220196, + 12.584668380908582 + ] + }, + "1800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 31.54620347513727, + 12.637080520201405 + ] + }, + "1900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.148423429611098, + 12.74916725120127 + ] + }, + "2000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.146439484120116, + 12.905178964345618 + ] + }, + "2100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.149576025637684, + 13.006940917309247 + ] + }, + "2200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.126246430368305, + 13.107503837410825 + ] + }, + "2300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.104379361635342, + 13.223235973280122 + ] + }, + "2400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.135190080423746, + 13.36128347785936 + ] + }, + "2500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.981219508598527, + 13.473697427231842 + ] + }, + "2600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.863899404441271, + 13.50303289156837 + ] + }, + "2700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.658860522528131, + 13.485230880073107 + ] + }, + "2800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.44407948309266, + 13.446135725634615 + ] + }, + "2900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.44407948309266, + 13.413693596332184 + ] + } + }, + "nq": { + "400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 7.6803204433986965, + 25.506609120436963, + 35.4, + 44.4, + 52.5 + ] + }, + "500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 22.622804921188227, + 35.4, + 44.4, + 52.5 + ] + }, + "600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 19.966301579194372, + 35.4, + 44.4, + 52.5 + ] + }, + "700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 17.430763940163832, + 33.79508340848005, + 44.4, + 52.5 + ] + }, + "800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 14.752921911234477, + 31.71885034449889, + 44.4, + 52.5 + ] + }, + "900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 11.854693031181021, + 29.923046639543475, + 44.4, + 52.5 + ] + }, + "1000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.549433913822687, + 26.734189128096668, + 43.96760750800311, + 52.5 + ] + }, + "1100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 26.26933164936586, + 42.23523193272671, + 52.5 + ] + }, + "1200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 24.443114637042832, + 40.57167959798151, + 52.5 + ] + }, + "1300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 22.41596168949836, + 39.04561852479495, + 52.5 + ] + }, + "1400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 20.276864821170303, + 37.557663261443224, + 52.252852231224054 + ] + }, + "1500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 18.252772588147742, + 35.9974418607538, + 50.68604059588987 + ] + }, + "1600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 16.31441663648616, + 34.51170378091407, + 49.20153034100798 + ] + }, + "1700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 14.255458319309813, + 33.043410795291045, + 47.820213744181245 + ] + }, + "1800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 31.54620347513727, + 46.51705619739449 + ] + }, + "1900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 29.986013742375484, + 45.29506741639918 + ] + }, + "2000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 28.432646044605782, + 44.107822395271945 + ] + }, + "2100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 26.892634464336055, + 42.758175515158776 + ] + }, + "2200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 25.270679127870263, + 41.467063889795895 + ] + }, + "2300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 23.531132157718837, + 40.293041104955826 + ] + }, + "2400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 21.815645106750623, + 39.03109248860755 + ] + }, + "2500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 20.34997949463564, + 37.71320701654063 + ] + }, + "2600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 18.81710568651804, + 36.35563657017404 + ] + }, + "2700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 17.259072160217805, + 35.02979557646653 + ] + }, + "2800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 16, + 33.74372254979665 + ] + }, + "2900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 16, + 32.54934541379723 + ] + } + } +} diff --git a/datasets/assetData/modelData/hidrostal-H05K-S03R.json b/datasets/assetData/modelData/hidrostal-H05K-S03R.json new file mode 100644 index 0000000..3c381a7 --- /dev/null +++ b/datasets/assetData/modelData/hidrostal-H05K-S03R.json @@ -0,0 +1,1062 @@ +{ + "np": { + "700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.962460720759278, + 20.65443723573673, + 31.029351002816465, + 44.58926412111886, + 62.87460150792057 + ] + }, + "800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 13.035157335397209, + 20.74906989186132, + 31.029351002816465, + 44.58926412111886, + 62.87460150792057 + ] + }, + "900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 13.064663380158798, + 20.927197054134297, + 31.107126521989933, + 44.58926412111886, + 62.87460150792057 + ] + }, + "1000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 13.039271391128953, + 21.08680188366637, + 31.30899920405947, + 44.58926412111886, + 62.87460150792057 + ] + }, + "1100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.940075520572446, + 21.220547481589954, + 31.51468295656385, + 44.621326083982, + 62.87460150792057 + ] + }, + "1200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.784378070157494, + 21.287467135615458, + 31.736145492247378, + 44.833460637148086, + 62.87460150792057 + ] + }, + "1300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.586915243939579, + 21.276682281369446, + 31.930487772749828, + 45.09147841519212, + 62.87460150792057 + ] + }, + "1400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.072531459639976, + 21.236263402754997, + 31.98957228629009, + 45.343639823277805, + 62.948551456696194 + ] + }, + "1500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 11.498673648884504, + 20.996631954252724, + 31.954252725886462, + 45.54353714625641, + 63.22528016894755 + ] + }, + "1600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 52.14679487594751, + 20.746724065725342, + 31.960270693111905, + 45.6989826531509, + 63.50000000000001 + ] + }, + "1700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 10.785741081439639, + 20.410520957192535, + 31.950197200275465, + 45.844022293894504, + 63.800401477703126 + ] + }, + "1800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 10.26507140279083, + 20.02134876415971, + 31.90474593035864, + 45.99882821699525, + 64.10190222175436 + ] + }, + "1900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.844493687783078, + 19.615126745440445, + 31.784477814504157, + 46.121518686299474, + 64.37205899496851 + ] + }, + "2000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.42546845395214, + 19.224613161465353, + 31.3852031134771, + 46.15771544706397, + 64.55065634962911 + ] + }, + "2100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.977806634186596, + 18.777333452839002, + 31.231492686456505, + 46.13420576468383, + 64.64634734417953 + ] + }, + "2200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.5551220832516, + 18.192271683023783, + 31.21886730567425, + 46.10526642440768, + 64.7459373335406 + ] + }, + "2300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.224790390000274, + 17.635073073073073, + 30.69719637959011, + 46.04336860563764, + 64.87880030950727 + ] + }, + "2400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 17.235714899207412, + 30.206677994537266, + 45.90194286632148, + 65.00133289948793 + ] + }, + "2500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 16.699519153953943, + 29.81226369335321, + 45.68999350609509, + 65.08194121217663 + ] + }, + "2600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 16.128295133509337, + 29.372650465392372, + 45.440269896240885, + 65.1262338514688 + ] + }, + "2700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 15.655831107176521, + 28.888887637256676, + 45.14580957087996, + 65.13308230125698 + ] + }, + "2800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 15.218098933011891, + 28.362864023341317, + 44.807426250648106, + 65.10511931024406 + ] + }, + "2900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 14.727036592419225, + 27.800257499369994, + 44.41688206158469, + 65.01783815190142 + ] + }, + "3000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 14.220778455429796, + 27.231492686456505, + 44.05531409059111, + 64.84454626378002 + ] + }, + "3100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.791032481569887, + 26.655487058053755, + 43.47550152847766, + 64.61338781598111 + ] + }, + "3200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 57.998168647814666, + 42.997354839160536, + 64.33911122026377 + ] + }, + "3300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 53.35067019159144, + 42.48429874246399, + 64.03769740244357 + ] + }, + "3400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 24.605489108239045, + 41.93544657954916, + 63.75332312922636 + ] + }, + "3500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 24.02776812223464, + 41.3462311518563, + 63.43861799695663 + ] + }, + "3600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 23.461492562203443, + 40.66666743038082, + 63.03442493367597 + ] + }, + "3700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 22.83964444901582, + 39.93227924494096, + 62.58510941648396 + ] + }, + "3800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 22.224853190033304, + 39.26854818553173, + 62.120049154943764 + ] + }, + "3900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 21.72969647212158, + 38.65394379517984, + 61.64012936635131 + ] + } + }, + "nq": { + "700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 119.13938764447377, + 150.12178608265387, + 178.82698019104356, + 202.3699313222398, + 227.06382297856618 + ] + }, + "800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 112.59072109293984, + 148.15847460389205, + 178.82698019104356, + 202.3699313222398, + 227.06382297856618 + ] + }, + "900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 105.6217241180404, + 144.00502117747064, + 177.15212647335034, + 202.3699313222398, + 227.06382297856618 + ] + }, + "1000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 97.96933385655602, + 139.33203004341362, + 172.8335214963562, + 202.3699313222398, + 227.06382297856618 + ] + }, + "1100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 89.46890733013123, + 133.63746503107248, + 168.6757638770697, + 201.51457815731206, + 227.06382297856618 + ] + }, + "1200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 81.7102176307068, + 127.54746478805862, + 164.86083942366332, + 197.9278536516828, + 227.06382297856618 + ] + }, + "1300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 73.54434350844221, + 121.17569010344418, + 160.74497886055957, + 194.59764221140935, + 227.06382297856618 + ] + }, + "1400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 65.44062943901834, + 114.06019126455426, + 155.75252082246928, + 191.17149532208072, + 226.18795889319966 + ] + }, + "1500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 58.16022827241729, + 106.8304040176964, + 150.34769411635546, + 187.41150790422392, + 223.01071026385065 + ] + }, + "1600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 52.14679487594751, + 99.83305618056556, + 144.95937497345926, + 183.42837752248894, + 219.8652102448096 + ] + }, + "1700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 46.01552158918748, + 93.03792449348434, + 139.34246720444983, + 179.30356404990695, + 216.85840103402688 + ] + }, + "1800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 39.891102390431755, + 86.36278038299652, + 133.36260934920088, + 175.064220776007, + 213.87502962516638 + ] + }, + "1900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 34.11562677513334, + 79.92122861259746, + 127.3583046971797, + 170.60370417418847, + 210.68808498795732 + ] + }, + "2000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 28.23139893028536, + 74.27239543161777, + 121.3852031134771, + 165.77656329385528, + 207.11383345844555 + ] + }, + "2100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 22.028625643397625, + 68.52803664413138, + 115.60209814095579, + 160.766567321836, + 203.25772755139087 + ] + }, + "2200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 15.99225052287954, + 62.252556641890656, + 109.75606164153551, + 155.8982054779732, + 199.33642607507025 + ] + }, + "2300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 10.057512077941812, + 56.78739880947559, + 103.28490253306859, + 150.87040317632852, + 195.3712838183181 + ] + }, + "2400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 51.89879972018922, + 97.49411123502856, + 145.15861930790484, + 191.3102286463588 + ] + }, + "2500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 46.49650512979005, + 92.5946794216408, + 139.3607940098774, + 187.1228895952043 + ] + }, + "2600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 40.96259596169048, + 87.48581221097406, + 134.10330733835585, + 182.81452539519677 + ] + }, + "2700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 35.97902929973601, + 82.22524681851449, + 129.0407272627695, + 178.46909993743765 + ] + }, + "2800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 31.23575309746966, + 77.15557437941118, + 124.12760990251361, + 174.10587281860919 + ] + }, + "2900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 26.43435480236642, + 72.24738368847274, + 119.17501218716764, + 169.57138175798616 + ] + }, + "3000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 21.604008702139296, + 67.42496298247559, + 114.05531409059111, + 164.73832634582294 + ] + }, + "3100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 16.57058014337364, + 62.68624249227209, + 109.06006424231046, + 159.88207336974 + ] + }, + "3200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 57.998168647814666, + 104.24541477723719, + 155.19789417429632 + ] + }, + "3300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 53.35067019159144, + 99.62632764730057, + 150.4942891087851 + ] + }, + "3400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 48.8526661624979, + 95.33061875213433, + 145.56922638322362 + ] + }, + "3500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 44.55023162119764, + 91.08960051646183, + 140.69583687494227 + ] + }, + "3600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 40.31135640023136, + 86.51969834209932, + 136.26067387204347 + ] + }, + "3700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 36.07819040356928, + 81.84472835862138, + 132.05652400156956 + ] + }, + "3800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 31.85665211119843, + 77.5230204653733, + 127.71198568555374 + ] + }, + "3900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 27.623838938465713, + 73.46672821834078, + 123.26611832311883 + ] + } + } +} \ No newline at end of file diff --git a/datasets/assetData/modelData/index.js b/datasets/assetData/modelData/index.js new file mode 100644 index 0000000..c4af256 --- /dev/null +++ b/datasets/assetData/modelData/index.js @@ -0,0 +1,124 @@ +const fs = require('fs'); +const path = require('path'); + +class AssetLoader { + constructor() { + this.relPath = './' + this.baseDir = path.resolve(__dirname, this.relPath); + this.cache = new Map(); // Cache loaded JSON files for better performance + } + + /** + * Load a specific curve by type + * @param {string} curveType - The curve identifier (e.g., 'hidrostal-H05K-S03R') + * @returns {Object|null} The curve data object or null if not found + */ + loadModel(modelType) { + return this.loadAsset('models', modelType); + } + + /** + * Load any asset from a specific dataset folder + * @param {string} datasetType - The dataset folder name (e.g., 'curves', 'assetData') + * @param {string} assetId - The specific asset identifier + * @returns {Object|null} The asset data object or null if not found + */ + loadAsset(datasetType, assetId) { + //const cacheKey = `${datasetType}/${assetId}`; + const cacheKey = `${assetId}`; + + + // Check cache first + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey); + } + + try { + const filePath = path.join(this.baseDir, `${assetId}.json`); + + // Check if file exists + if (!fs.existsSync(filePath)) { + console.warn(`Asset not found: ${filePath}`); + return null; + } + + // Load and parse JSON + const rawData = fs.readFileSync(filePath, 'utf8'); + const assetData = JSON.parse(rawData); + + // Cache the result + this.cache.set(cacheKey, assetData); + + return assetData; + } catch (error) { + console.error(`Error loading asset ${cacheKey}:`, error.message); + return null; + } + } + + /** + * Get all available assets in a dataset + * @param {string} datasetType - The dataset folder name + * @returns {string[]} Array of available asset IDs + */ + getAvailableAssets(datasetType) { + try { + const datasetPath = path.join(this.baseDir, datasetType); + + if (!fs.existsSync(datasetPath)) { + return []; + } + + return fs.readdirSync(datasetPath) + .filter(file => file.endsWith('.json')) + .map(file => file.replace('.json', '')); + } catch (error) { + console.error(`Error reading dataset ${datasetType}:`, error.message); + return []; + } + } + + /** + * Clear the cache (useful for development/testing) + */ + clearCache() { + this.cache.clear(); + } +} + +// Create and export a singleton instance +const assetLoader = new AssetLoader(); + +module.exports = { + AssetLoader, + assetLoader, + // Convenience methods for backward compatibility + loadModel: (modelType) => assetLoader.loadModel(modelType), + loadAsset: (datasetType, assetId) => assetLoader.loadAsset(datasetType, assetId), + getAvailableAssets: (datasetType) => assetLoader.getAvailableAssets(datasetType) +}; + +/* +// Example usage in your scripts +const loader = new AssetLoader(); + +// Load a specific curve +const curve = loader.loadModel('hidrostal-H05K-S03R'); +if (curve) { + console.log('Model loaded:', curve); +} else { + console.log('Model not found'); +} +/* +// Load any asset from any dataset +const someAsset = loadAsset('assetData', 'some-asset-id'); + +// Get list of available models +const availableCurves = getAvailableAssets('curves'); +console.log('Available curves:', availableCurves); + +// Using the class directly for more control +const { AssetLoader } = require('./index.js'); +const customLoader = new AssetLoader(); +const data = customLoader.loadCurve('hidrostal-H05K-S03R'); +*/ \ No newline at end of file diff --git a/datasets/assetData/valve.json b/datasets/assetData/valve.json new file mode 100644 index 0000000..56ba2e1 --- /dev/null +++ b/datasets/assetData/valve.json @@ -0,0 +1,27 @@ +{ + "id": "valve", + "label": "valve", + "softwareType": "valve", + "suppliers": [ + { + "id": "binder", + "name": "Binder Engineering", + "types": [ + { + "id": "valve-gate", + "name": "Gate", + "models": [ + { "id": "binder-valve-001", "name": "ECDV", "units": ["m3/h", "gpm", "l/min"] } + ] + }, + { + "id": "valve-jet", + "name": "Jet", + "models": [ + { "id": "binder-valve-002", "name": "JCV", "units": ["m3/h", "gpm", "l/min"] } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/index.js b/index.js index 3ca63cb..97dffa3 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,8 @@ const MenuManager = require('./src/menu/index.js'); const predict = require('./src/predict/predict_class.js'); const interpolation = require('./src/predict/interpolation.js'); const childRegistrationUtils = require('./src/helper/childRegistrationUtils.js'); -const { loadCurve } = require('./datasets/assetData/curves/index.js'); +const { loadCurve } = require('./datasets/assetData/curves/index.js'); //deprecated replace with load model data +const { loadModel } = require('./datasets/assetData/modelData/index.js'); // Export everything module.exports = { @@ -45,6 +46,7 @@ module.exports = { convert, MenuManager, childRegistrationUtils, - loadCurve, + loadCurve, //deprecated replace with loadModel + loadModel, gravity }; diff --git a/src/configs/rotatingMachine.json b/src/configs/rotatingMachine.json index 744d9fb..102a0b3 100644 --- a/src/configs/rotatingMachine.json +++ b/src/configs/rotatingMachine.json @@ -260,6 +260,7 @@ "statuscheck", "execmovement", "execsequence", + "flowmovement", "emergencystop", "entermaintenance" ], @@ -273,6 +274,7 @@ "default": [ "statuscheck", "execmovement", + "flowmovement", "execsequence", "emergencystop", "exitmaintenance" diff --git a/src/menu/asset.js b/src/menu/asset.js index 76933e2..b47dc4d 100644 --- a/src/menu/asset.js +++ b/src/menu/asset.js @@ -1,62 +1,83 @@ -// asset.js -const fs = require('fs'); -const path = require('path'); +const { assetCategoryManager } = require('../../datasets/assetData'); class AssetMenu { - /** Define path where to find data of assets in constructor for now */ - constructor(relPath = '../../datasets/assetData') { - this.baseDir = path.resolve(__dirname, relPath); - this.assetData = this._loadJSON('assetData'); + constructor({ manager = assetCategoryManager, softwareType = null } = {}) { + this.manager = manager; + this.softwareType = softwareType; + this.categories = this.manager + .listCategories({ withMeta: true }) + .reduce((map, meta) => { + map[meta.softwareType] = this.manager.getCategory(meta.softwareType); + return map; + }, {}); } - _loadJSON(...segments) { - const filePath = path.resolve(this.baseDir, ...segments) + '.json'; - try { - return JSON.parse(fs.readFileSync(filePath, 'utf8')); - } catch (err) { - throw new Error(`Failed to load ${filePath}: ${err.message}`); + normalizeCategory(key) { + const category = this.categories[key]; + if (!category) { + return null; } - } - - /** - * ADD THIS METHOD - * Compiles all menu data from the file system into a single nested object. - * This is run once on the server to pre-load everything. - * @returns {object} A comprehensive object with all menu options. - */ - getAllMenuData() { - // load the raw JSON once - const data = this._loadJSON('assetData'); - const allData = {}; - data.suppliers.forEach(sup => { - allData[sup.name] = {}; - sup.categories.forEach(cat => { - allData[sup.name][cat.name] = {}; - cat.types.forEach(type => { - // here: store the full array of model objects, not just names - allData[sup.name][cat.name][type.name] = type.models; - }); - }); - }); - - return allData; + return { + ...category, + label: category.label || category.softwareType || key, + suppliers: (category.suppliers || []).map((supplier) => ({ + ...supplier, + id: supplier.id || supplier.name, + types: (supplier.types || []).map((type) => ({ + ...type, + id: type.id || type.name, + models: (type.models || []).map((model) => ({ + ...model, + id: model.id || model.name, + units: model.units || [] + })) + })) + })) + }; } - /** - * Convert the static initEditor function to a string that can be served to the client - * @param {string} nodeName - The name of the node type - * @returns {string} JavaScript code as a string - */ -getClientInitCode(nodeName) { - // step 1: get the two helper strings - const htmlCode = this.getHtmlInjectionCode(nodeName); - const dataCode = this.getDataInjectionCode(nodeName); - const eventsCode = this.getEventInjectionCode(nodeName); - const saveCode = this.getSaveInjectionCode(nodeName); + resolveCategoryForNode(nodeName) { + const keys = Object.keys(this.categories); + if (keys.length === 0) { + return null; + } + if (this.softwareType && this.categories[this.softwareType]) { + return this.softwareType; + } - return ` + if (nodeName) { + const normalized = typeof nodeName === 'string' ? nodeName.toLowerCase() : nodeName; + if (normalized && this.categories[normalized]) { + return normalized; + } + } + + return keys[0]; + } + + getAllMenuData(nodeName) { + const categoryKey = this.resolveCategoryForNode(nodeName); + const selectedCategories = {}; + + if (categoryKey && this.categories[categoryKey]) { + selectedCategories[categoryKey] = this.normalizeCategory(categoryKey); + } + + return { + categories: selectedCategories, + defaultCategory: categoryKey + }; + } + + getClientInitCode(nodeName) { + const htmlCode = this.getHtmlInjectionCode(nodeName); + const dataCode = this.getDataInjectionCode(nodeName); + const eventsCode = this.getEventInjectionCode(nodeName); + const saveCode = this.getSaveInjectionCode(nodeName); + + return ` // --- AssetMenu for ${nodeName} --- window.EVOLV.nodes.${nodeName}.assetMenu = window.EVOLV.nodes.${nodeName}.assetMenu || {}; @@ -66,103 +87,276 @@ getClientInitCode(nodeName) { ${eventsCode} ${saveCode} - // wire it all up when the editor loads window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) { - // ------------------ BELOW sequence is important! ------------------------------- - console.log('Initializing asset properties for ${nodeName}…'); + console.log('Initializing asset properties for ${nodeName}'); this.injectHtml(); - // load the data and wire up events - // this will populate the fields and set up the event listeners this.wireEvents(node); - // this will load the initial data into the fields - // this is important to ensure the fields are populated correctly this.loadData(node); }; `; - -} + } -getDataInjectionCode(nodeName) { + getDataInjectionCode(nodeName) { return ` - // Asset Data loader for ${nodeName} + // Asset data loader for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.loadData = function(node) { - const data = window.EVOLV.nodes.${nodeName}.menuData.asset; + const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; + const categories = menuAsset.categories || {}; + const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; const elems = { supplier: document.getElementById('node-input-supplier'), - category: document.getElementById('node-input-category'), - type: document.getElementById('node-input-assetType'), - model: document.getElementById('node-input-model'), - unit: document.getElementById('node-input-unit') + type: document.getElementById('node-input-assetType'), + model: document.getElementById('node-input-model'), + unit: document.getElementById('node-input-unit') }; - function populate(el, opts, sel) { - const old = el.value; - el.innerHTML = ''; - (opts||[]).forEach(o=>{ + + function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { + const previous = selectEl.value; + const mapper = typeof mapFn === 'function' + ? mapFn + : (value) => ({ value, label: value }); + + selectEl.innerHTML = ''; + + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = placeholderText; + placeholder.disabled = true; + placeholder.selected = true; + selectEl.appendChild(placeholder); + + items.forEach((item) => { + const option = mapper(item); + if (!option || typeof option.value === 'undefined') { + return; + } const opt = document.createElement('option'); - opt.value = o; opt.textContent = o; - el.appendChild(opt); + opt.value = option.value; + opt.textContent = option.label; + selectEl.appendChild(opt); }); - el.value = sel||""; - if(el.value!==old) el.dispatchEvent(new Event('change')); + + if (selectedValue) { + selectEl.value = selectedValue; + if (!selectEl.value) { + selectEl.value = ''; + } + } else { + selectEl.value = ''; + } + if (selectEl.value !== previous) { + selectEl.dispatchEvent(new Event('change')); + } } - // initial population - populate(elems.supplier, Object.keys(data), node.supplier); + + const resolveCategoryKey = () => { + if (node.softwareType && categories[node.softwareType]) { + return node.softwareType; + } + if (node.category && categories[node.category]) { + return node.category; + } + return defaultCategory; + }; + + const categoryKey = resolveCategoryKey(); + node.category = categoryKey; + const activeCategory = categoryKey ? categories[categoryKey] : null; + + const suppliers = activeCategory ? activeCategory.suppliers : []; + populate( + elems.supplier, + suppliers, + node.supplier, + (supplier) => ({ value: supplier.id || supplier.name, label: supplier.name }), + suppliers.length ? 'Select...' : 'No suppliers available' + ); + + const activeSupplier = suppliers.find( + (supplier) => (supplier.id || supplier.name) === node.supplier + ); + const types = activeSupplier ? activeSupplier.types : []; + populate( + elems.type, + types, + node.assetType, + (type) => ({ value: type.id || type.name, label: type.name }), + activeSupplier ? 'Select...' : 'Awaiting Supplier Selection' + ); + + const activeType = types.find( + (type) => (type.id || type.name) === node.assetType + ); + const models = activeType ? activeType.models : []; + populate( + elems.model, + models, + node.model, + (model) => ({ value: model.id || model.name, label: model.name }), + activeType ? 'Select...' : 'Awaiting Type Selection' + ); + + const activeModel = models.find( + (model) => (model.id || model.name) === node.model + ); + populate( + elems.unit, + activeModel ? activeModel.units || [] : [], + node.unit, + (unit) => ({ value: unit, label: unit }), + activeModel ? 'Select...' : activeType ? 'Awaiting Model Selection' : 'Awaiting Type Selection' + ); }; - ` + `; } -getEventInjectionCode(nodeName) { + getEventInjectionCode(nodeName) { return ` - // Asset Event wiring for ${nodeName} + // Asset event wiring for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.wireEvents = function(node) { - const data = window.EVOLV.nodes.${nodeName}.menuData.asset; + const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; + const categories = menuAsset.categories || {}; + const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; const elems = { supplier: document.getElementById('node-input-supplier'), - category: document.getElementById('node-input-category'), - type: document.getElementById('node-input-assetType'), - model: document.getElementById('node-input-model'), - unit: document.getElementById('node-input-unit') + type: document.getElementById('node-input-assetType'), + model: document.getElementById('node-input-model'), + unit: document.getElementById('node-input-unit') }; - function populate(el, opts, sel) { - const old = el.value; - el.innerHTML = ''; - (opts||[]).forEach(o=>{ + + function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { + const previous = selectEl.value; + const mapper = typeof mapFn === 'function' + ? mapFn + : (value) => ({ value, label: value }); + + selectEl.innerHTML = ''; + + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = placeholderText; + placeholder.disabled = true; + placeholder.selected = true; + selectEl.appendChild(placeholder); + + items.forEach((item) => { + const option = mapper(item); + if (!option || typeof option.value === 'undefined') { + return; + } const opt = document.createElement('option'); - opt.value = o; opt.textContent = o; - el.appendChild(opt); + opt.value = option.value; + opt.textContent = option.label; + selectEl.appendChild(opt); }); - el.value = sel||""; - if(el.value!==old) el.dispatchEvent(new Event('change')); + + if (selectedValue) { + selectEl.value = selectedValue; + if (!selectEl.value) { + selectEl.value = ''; + } + } else { + selectEl.value = ''; + } + if (selectEl.value !== previous) { + selectEl.dispatchEvent(new Event('change')); + } } - elems.supplier.addEventListener('change', ()=>{ - populate(elems.category, - elems.supplier.value? Object.keys(data[elems.supplier.value]||{}) : [], - node.category); + + const resolveCategoryKey = () => { + if (node.softwareType && categories[node.softwareType]) { + return node.softwareType; + } + if (node.category && categories[node.category]) { + return node.category; + } + return defaultCategory; + }; + + const getActiveCategory = () => { + const key = resolveCategoryKey(); + return key ? categories[key] : null; + }; + + node.category = resolveCategoryKey(); + + elems.supplier.addEventListener('change', () => { + const category = getActiveCategory(); + const supplier = category + ? category.suppliers.find( + (item) => (item.id || item.name) === elems.supplier.value + ) + : null; + const types = supplier ? supplier.types : []; + populate( + elems.type, + types, + node.assetType, + (type) => ({ value: type.id || type.name, label: type.name }), + supplier ? 'Select...' : 'Awaiting Supplier Selection' + ); + populate(elems.model, [], '', undefined, 'Awaiting Type Selection'); + populate(elems.unit, [], '', undefined, 'Awaiting Type Selection'); }); - elems.category.addEventListener('change', ()=>{ - const s=elems.supplier.value, c=elems.category.value; - populate(elems.type, - (s&&c)? Object.keys(data[s][c]||{}) : [], - node.assetType); + + elems.type.addEventListener('change', () => { + const category = getActiveCategory(); + const supplier = category + ? category.suppliers.find( + (item) => (item.id || item.name) === elems.supplier.value + ) + : null; + const type = supplier + ? supplier.types.find( + (item) => (item.id || item.name) === elems.type.value + ) + : null; + const models = type ? type.models : []; + populate( + elems.model, + models, + node.model, + (model) => ({ value: model.id || model.name, label: model.name }), + type ? 'Select...' : 'Awaiting Type Selection' + ); + populate( + elems.unit, + [], + '', + undefined, + type ? 'Awaiting Model Selection' : 'Awaiting Type Selection' + ); }); - elems.type.addEventListener('change', ()=>{ - const s=elems.supplier.value, c=elems.category.value, t=elems.type.value; - const md = (s&&c&&t)? data[s][c][t]||[] : []; - populate(elems.model, md.map(m=>m.name), node.model); - }); - elems.model.addEventListener('change', ()=>{ - const s=elems.supplier.value, c=elems.category.value, t=elems.type.value, m=elems.model.value; - const md = (s&&c&&t)? data[s][c][t]||[] : []; - const entry = md.find(x=>x.name===m); - populate(elems.unit, entry? entry.units : [], node.unit); + + elems.model.addEventListener('change', () => { + const category = getActiveCategory(); + const supplier = category + ? category.suppliers.find( + (item) => (item.id || item.name) === elems.supplier.value + ) + : null; + const type = supplier + ? supplier.types.find( + (item) => (item.id || item.name) === elems.type.value + ) + : null; + const model = type + ? type.models.find( + (item) => (item.id || item.name) === elems.model.value + ) + : null; + populate( + elems.unit, + model ? model.units || [] : [], + node.unit, + (unit) => ({ value: unit, label: unit }), + model ? 'Select...' : type ? 'Awaiting Model Selection' : 'Awaiting Type Selection' + ); }); }; - ` + `; } -/** - * Generate HTML template for asset fields - */ getHtmlTemplate() { return ` @@ -172,10 +366,6 @@ getEventInjectionCode(nodeName) { -
- - -
@@ -192,12 +382,11 @@ getEventInjectionCode(nodeName) { `; } - /** - * Get client-side HTML injection code - */ getHtmlInjectionCode(nodeName) { - const htmlTemplate = this.getHtmlTemplate().replace(/`/g, '\\`').replace(/\$/g, '\\$'); - + const htmlTemplate = this.getHtmlTemplate() + .replace(/`/g, '\\`') + .replace(/\$/g, '\\$'); + return ` // Asset HTML injection for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.injectHtml = function() { @@ -210,33 +399,53 @@ getEventInjectionCode(nodeName) { `; } - /** - * Returns the JS that injects the saveEditor function - */ getSaveInjectionCode(nodeName) { return ` - // Asset Save injection for ${nodeName} + // Asset save handler for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.saveEditor = function(node) { - console.log('Saving asset properties for ${nodeName}…'); - const fields = ['supplier','category','assetType','model','unit']; - const errors = []; - fields.forEach(f => { - const el = document.getElementById(\`node-input-\${f}\`); - node[f] = el ? el.value : ''; - }); - if (node.assetType && !node.unit) errors.push('Unit must be set when type is specified.'); - if (!node.unit) errors.push('Unit is required.'); - errors.forEach(e=>RED.notify(e,'error')); - - // --- DEBUG: show exactly what was saved --- - const saved = fields.reduce((o,f) => { o[f] = node[f]; return o; }, {}); - console.log('→ assetMenu.saveEditor result:', saved); + console.log('Saving asset properties for ${nodeName}'); + const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; + const categories = menuAsset.categories || {}; + const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; + const resolveCategoryKey = () => { + if (node.softwareType && categories[node.softwareType]) { + return node.softwareType; + } + if (node.category && categories[node.category]) { + return node.category; + } + return defaultCategory || ''; + }; - return errors.length===0; + node.category = resolveCategoryKey(); + + const fields = ['supplier', 'assetType', 'model', 'unit']; + const errors = []; + + fields.forEach((field) => { + const el = document.getElementById(\`node-input-\${field}\`); + node[field] = el ? el.value : ''; + }); + + if (node.assetType && !node.unit) { + errors.push('Unit must be set when a type is specified.'); + } + if (!node.unit) { + errors.push('Unit is required.'); + } + + errors.forEach((msg) => RED.notify(msg, 'error')); + + const saved = fields.reduce((acc, field) => { + acc[field] = node[field]; + return acc; + }, {}); + console.log('[AssetMenu] save result:', saved); + + return errors.length === 0; }; `; } - } module.exports = AssetMenu; diff --git a/src/menu/asset_DEPRECATED.js b/src/menu/asset_DEPRECATED.js new file mode 100644 index 0000000..5f0fa3c --- /dev/null +++ b/src/menu/asset_DEPRECATED.js @@ -0,0 +1,243 @@ +// asset.js +const fs = require('fs'); +const path = require('path'); + + +class AssetMenu { + /** Define path where to find data of assets in constructor for now */ + constructor(relPath = '../../datasets/assetData') { + this.baseDir = path.resolve(__dirname, relPath); + this.assetData = this._loadJSON('assetData'); + } + + _loadJSON(...segments) { + const filePath = path.resolve(this.baseDir, ...segments) + '.json'; + try { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch (err) { + throw new Error(`Failed to load ${filePath}: ${err.message}`); + } + } + + /** + * ADD THIS METHOD + * Compiles all menu data from the file system into a single nested object. + * This is run once on the server to pre-load everything. + * @returns {object} A comprehensive object with all menu options. + */ + getAllMenuData() { + // load the raw JSON once + const data = this._loadJSON('assetData'); + const allData = {}; + + data.suppliers.forEach(sup => { + allData[sup.name] = {}; + sup.categories.forEach(cat => { + allData[sup.name][cat.name] = {}; + cat.types.forEach(type => { + // here: store the full array of model objects, not just names + allData[sup.name][cat.name][type.name] = type.models; + }); + }); + }); + + return allData; + } + + /** + * Convert the static initEditor function to a string that can be served to the client + * @param {string} nodeName - The name of the node type + * @returns {string} JavaScript code as a string + */ +getClientInitCode(nodeName) { + // step 1: get the two helper strings + const htmlCode = this.getHtmlInjectionCode(nodeName); + const dataCode = this.getDataInjectionCode(nodeName); + const eventsCode = this.getEventInjectionCode(nodeName); + const saveCode = this.getSaveInjectionCode(nodeName); + + + return ` + // --- AssetMenu for ${nodeName} --- + window.EVOLV.nodes.${nodeName}.assetMenu = + window.EVOLV.nodes.${nodeName}.assetMenu || {}; + + ${htmlCode} + ${dataCode} + ${eventsCode} + ${saveCode} + + // wire it all up when the editor loads + window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) { + // ------------------ BELOW sequence is important! ------------------------------- + console.log('Initializing asset properties for ${nodeName}…'); + this.injectHtml(); + // load the data and wire up events + // this will populate the fields and set up the event listeners + this.wireEvents(node); + // this will load the initial data into the fields + // this is important to ensure the fields are populated correctly + this.loadData(node); + }; + `; + +} + +getDataInjectionCode(nodeName) { + return ` + // Asset Data loader for ${nodeName} + window.EVOLV.nodes.${nodeName}.assetMenu.loadData = function(node) { + const data = window.EVOLV.nodes.${nodeName}.menuData.asset; + const elems = { + supplier: document.getElementById('node-input-supplier'), + category: document.getElementById('node-input-category'), + type: document.getElementById('node-input-assetType'), + model: document.getElementById('node-input-model'), + unit: document.getElementById('node-input-unit') + }; + function populate(el, opts, sel) { + const old = el.value; + el.innerHTML = ''; + (opts||[]).forEach(o=>{ + const opt = document.createElement('option'); + opt.value = o; opt.textContent = o; + el.appendChild(opt); + }); + el.value = sel||""; + if(el.value!==old) el.dispatchEvent(new Event('change')); + } + // initial population + populate(elems.supplier, Object.keys(data), node.supplier); + }; + ` + } + +getEventInjectionCode(nodeName) { + return ` + // Asset Event wiring for ${nodeName} + window.EVOLV.nodes.${nodeName}.assetMenu.wireEvents = function(node) { + const data = window.EVOLV.nodes.${nodeName}.menuData.asset; + const elems = { + supplier: document.getElementById('node-input-supplier'), + category: document.getElementById('node-input-category'), + type: document.getElementById('node-input-assetType'), + model: document.getElementById('node-input-model'), + unit: document.getElementById('node-input-unit') + }; + function populate(el, opts, sel) { + const old = el.value; + el.innerHTML = ''; + (opts||[]).forEach(o=>{ + const opt = document.createElement('option'); + opt.value = o; opt.textContent = o; + el.appendChild(opt); + }); + el.value = sel||""; + if(el.value!==old) el.dispatchEvent(new Event('change')); + } + elems.supplier.addEventListener('change', ()=>{ + populate(elems.category, + elems.supplier.value? Object.keys(data[elems.supplier.value]||{}) : [], + node.category); + }); + elems.category.addEventListener('change', ()=>{ + const s=elems.supplier.value, c=elems.category.value; + populate(elems.type, + (s&&c)? Object.keys(data[s][c]||{}) : [], + node.assetType); + }); + elems.type.addEventListener('change', ()=>{ + const s=elems.supplier.value, c=elems.category.value, t=elems.type.value; + const md = (s&&c&&t)? data[s][c][t]||[] : []; + populate(elems.model, md.map(m=>m.name), node.model); + }); + elems.model.addEventListener('change', ()=>{ + const s=elems.supplier.value, c=elems.category.value, t=elems.type.value, m=elems.model.value; + const md = (s&&c&&t)? data[s][c][t]||[] : []; + const entry = md.find(x=>x.name===m); + populate(elems.unit, entry? entry.units : [], node.unit); + }); + }; + ` + } + +/** + * Generate HTML template for asset fields + */ + getHtmlTemplate() { + return ` + +
+

Asset selection

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ `; + } + + /** + * Get client-side HTML injection code + */ + getHtmlInjectionCode(nodeName) { + const htmlTemplate = this.getHtmlTemplate().replace(/`/g, '\\`').replace(/\$/g, '\\$'); + + return ` + // Asset HTML injection for ${nodeName} + window.EVOLV.nodes.${nodeName}.assetMenu.injectHtml = function() { + const placeholder = document.getElementById('asset-fields-placeholder'); + if (placeholder && !placeholder.hasChildNodes()) { + placeholder.innerHTML = \`${htmlTemplate}\`; + console.log('Asset HTML injected successfully'); + } + }; + `; + } + + /** + * Returns the JS that injects the saveEditor function + */ + getSaveInjectionCode(nodeName) { + return ` + // Asset Save injection for ${nodeName} + window.EVOLV.nodes.${nodeName}.assetMenu.saveEditor = function(node) { + console.log('Saving asset properties for ${nodeName}…'); + const fields = ['supplier','category','assetType','model','unit']; + const errors = []; + fields.forEach(f => { + const el = document.getElementById(\`node-input-\${f}\`); + node[f] = el ? el.value : ''; + }); + if (node.assetType && !node.unit) errors.push('Unit must be set when type is specified.'); + if (!node.unit) errors.push('Unit is required.'); + errors.forEach(e=>RED.notify(e,'error')); + + // --- DEBUG: show exactly what was saved --- + const saved = fields.reduce((o,f) => { o[f] = node[f]; return o; }, {}); + console.log('→ assetMenu.saveEditor result:', saved); + + return errors.length===0; + }; + `; + } + +} + +module.exports = AssetMenu; diff --git a/src/menu/index.js b/src/menu/index.js index a6def7f..1a051d2 100644 --- a/src/menu/index.js +++ b/src/menu/index.js @@ -2,13 +2,17 @@ const AssetMenu = require('./asset.js'); const { TagcodeApp, DynamicAssetMenu } = require('./tagcodeApp.js'); const LoggerMenu = require('./logger.js'); const PhysicalPositionMenu = require('./physicalPosition.js'); +const ConfigManager = require('../configs'); class MenuManager { constructor() { this.registeredMenus = new Map(); + this.configManager = new ConfigManager('../configs'); // Register factory functions - this.registerMenu('asset', () => new AssetMenu()); // static menu to be replaced by dynamic one but later + this.registerMenu('asset', (nodeName) => new AssetMenu({ + softwareType: this._getSoftwareType(nodeName) + })); // static menu to be replaced by dynamic one but later //this.registerMenu('asset', (nodeName) => new DynamicAssetMenu(nodeName, new TagcodeApp())); this.registerMenu('logger', () => new LoggerMenu()); this.registerMenu('position', () => new PhysicalPositionMenu()); @@ -23,6 +27,20 @@ class MenuManager { this.registeredMenus.set(menuType, menuFactory); } + _getSoftwareType(nodeName) { + if (!nodeName) { + return null; + } + + try { + const config = this.configManager.getConfig(nodeName); + return config?.functionality?.softwareType || nodeName; + } catch (error) { + console.warn(`Unable to determine softwareType for ${nodeName}: ${error.message}`); + return nodeName; + } + } + /** * Create a complete endpoint script with data and initialization functions * @param {string} nodeName - The name of the node type @@ -54,7 +72,7 @@ class MenuManager { try { const handler = instantiatedMenus.get(menuType); if (handler && typeof handler.getAllMenuData === 'function') { - menuData[menuType] = handler.getAllMenuData(); + menuData[menuType] = handler.getAllMenuData(nodeName); } else { // Provide default empty data if method doesn't exist menuData[menuType] = {}; @@ -172,4 +190,4 @@ class MenuManager { } } -module.exports = MenuManager; \ No newline at end of file +module.exports = MenuManager;