@bonsae/nrg 0.5.4 → 0.6.0
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/README.md +56 -7
- package/build/server/index.cjs +120 -39
- package/build/server/resources/nrg-client.js +1440 -1377
- package/package.json +1 -1
- package/src/core/client/app.vue +15 -25
- package/src/core/client/components/node-red-json-schema-form.vue +21 -2
- package/src/core/client/components/node-red-toggle.vue +115 -0
- package/src/core/client/index.ts +2 -0
- package/src/core/server/api/index.ts +1 -0
- package/src/core/server/api/serve-nrg-resources.ts +54 -0
- package/src/core/server/index.ts +21 -57
- package/src/core/server/nodes/factories.ts +133 -0
- package/src/core/server/nodes/index.ts +1 -0
- package/src/core/server/nodes/io-node.ts +2 -1
- package/src/core/server/nodes/types/factories.ts +115 -0
- package/src/core/server/nodes/types/index.ts +1 -0
- package/src/core/server/nodes/types/io-node.ts +3 -0
- package/src/core/server/schemas/types/index.ts +1 -0
package/package.json
CHANGED
package/src/core/client/app.vue
CHANGED
|
@@ -2,32 +2,22 @@
|
|
|
2
2
|
<div
|
|
3
3
|
v-if="features.hasInputSchema || features.hasOutputSchema"
|
|
4
4
|
class="form-row"
|
|
5
|
-
style="display: flex; align-items: center; gap:
|
|
5
|
+
style="display: flex; align-items: center; gap: 12px"
|
|
6
6
|
>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
:id="`node-input-validateOutput-${localNode.id}`"
|
|
22
|
-
type="checkbox"
|
|
23
|
-
:checked="localNode.validateOutput"
|
|
24
|
-
style="width: auto; margin: 0"
|
|
25
|
-
@change="
|
|
26
|
-
localNode.validateOutput = ($event.target as HTMLInputElement).checked
|
|
27
|
-
"
|
|
28
|
-
/>
|
|
29
|
-
<NodeRedInputLabel label="Validate Output" style="width: auto" />
|
|
30
|
-
</template>
|
|
7
|
+
<NodeRedToggle
|
|
8
|
+
v-if="features.hasInputSchema"
|
|
9
|
+
:model-value="localNode.validateInput"
|
|
10
|
+
label="Validate Input"
|
|
11
|
+
style="flex: 1"
|
|
12
|
+
@update:model-value="localNode.validateInput = $event"
|
|
13
|
+
/>
|
|
14
|
+
<NodeRedToggle
|
|
15
|
+
v-if="features.hasOutputSchema"
|
|
16
|
+
:model-value="localNode.validateOutput"
|
|
17
|
+
label="Validate Output"
|
|
18
|
+
style="flex: 1"
|
|
19
|
+
@update:model-value="localNode.validateOutput = $event"
|
|
20
|
+
/>
|
|
31
21
|
</div>
|
|
32
22
|
<div style="width: 100%; padding-bottom: 12px">
|
|
33
23
|
<NodeRedNodeForm
|
|
@@ -12,12 +12,20 @@
|
|
|
12
12
|
@update:value="node[field.key] = $event"
|
|
13
13
|
/>
|
|
14
14
|
|
|
15
|
+
<div v-else-if="field.inputType === 'boolean' && field.toggle">
|
|
16
|
+
<NodeRedToggle
|
|
17
|
+
:model-value="node[field.key]"
|
|
18
|
+
:label="field.label"
|
|
19
|
+
:icon="field.icon"
|
|
20
|
+
@update:model-value="node[field.key] = $event"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
15
24
|
<div v-else-if="field.inputType === 'boolean'">
|
|
16
25
|
<NodeRedInputLabel
|
|
17
26
|
:label="field.label"
|
|
18
27
|
:icon="field.icon"
|
|
19
28
|
:required="field.required"
|
|
20
|
-
style="width: auto"
|
|
21
29
|
/>
|
|
22
30
|
<input
|
|
23
31
|
type="checkbox"
|
|
@@ -144,6 +152,7 @@
|
|
|
144
152
|
import type { PropType } from "vue";
|
|
145
153
|
import { defineComponent } from "vue";
|
|
146
154
|
import NodeRedInputLabel from "./node-red-input-label.vue";
|
|
155
|
+
import NodeRedToggle from "./node-red-toggle.vue";
|
|
147
156
|
import NodeRedInput from "./node-red-input.vue";
|
|
148
157
|
import NodeRedSelectInput from "./node-red-select-input.vue";
|
|
149
158
|
import NodeRedTypedInput from "./node-red-typed-input.vue";
|
|
@@ -169,6 +178,7 @@ interface NrgFormOptions {
|
|
|
169
178
|
icon?: string;
|
|
170
179
|
typedInputTypes?: string[];
|
|
171
180
|
editorLanguage?: string;
|
|
181
|
+
toggle?: boolean;
|
|
172
182
|
}
|
|
173
183
|
|
|
174
184
|
interface FieldSchema {
|
|
@@ -206,6 +216,7 @@ interface FormField {
|
|
|
206
216
|
types?: string[];
|
|
207
217
|
configType?: string;
|
|
208
218
|
language?: string;
|
|
219
|
+
toggle?: boolean;
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
function formatLabel(key: string): string {
|
|
@@ -292,7 +303,14 @@ function buildField(
|
|
|
292
303
|
|
|
293
304
|
switch (rawType) {
|
|
294
305
|
case "boolean":
|
|
295
|
-
return {
|
|
306
|
+
return {
|
|
307
|
+
key,
|
|
308
|
+
label,
|
|
309
|
+
icon,
|
|
310
|
+
inputType: "boolean",
|
|
311
|
+
required,
|
|
312
|
+
toggle: form.toggle,
|
|
313
|
+
};
|
|
296
314
|
|
|
297
315
|
case "number":
|
|
298
316
|
case "integer":
|
|
@@ -368,6 +386,7 @@ export default defineComponent({
|
|
|
368
386
|
name: "NodeRedJsonSchemaForm",
|
|
369
387
|
components: {
|
|
370
388
|
NodeRedInputLabel,
|
|
389
|
+
NodeRedToggle,
|
|
371
390
|
NodeRedInput,
|
|
372
391
|
NodeRedSelectInput,
|
|
373
392
|
NodeRedTypedInput,
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="nrg-toggle-wrapper">
|
|
3
|
+
<label class="nrg-toggle" :class="{ 'nrg-toggle--checked': modelValue }">
|
|
4
|
+
<input
|
|
5
|
+
type="checkbox"
|
|
6
|
+
:checked="modelValue"
|
|
7
|
+
class="nrg-toggle__input"
|
|
8
|
+
@change="
|
|
9
|
+
$emit(
|
|
10
|
+
'update:modelValue',
|
|
11
|
+
($event.target as HTMLInputElement).checked,
|
|
12
|
+
)
|
|
13
|
+
"
|
|
14
|
+
/>
|
|
15
|
+
<span v-if="icon || label" class="nrg-toggle__label">
|
|
16
|
+
<i v-if="icon" :class="iconClass"></i>
|
|
17
|
+
{{ label }}
|
|
18
|
+
</span>
|
|
19
|
+
<span class="nrg-toggle__slider"></span>
|
|
20
|
+
</label>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import { defineComponent } from "vue";
|
|
26
|
+
|
|
27
|
+
export default defineComponent({
|
|
28
|
+
name: "NodeRedToggle",
|
|
29
|
+
props: {
|
|
30
|
+
modelValue: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
label: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: "",
|
|
37
|
+
},
|
|
38
|
+
icon: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: "",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
emits: ["update:modelValue"],
|
|
44
|
+
computed: {
|
|
45
|
+
iconClass(): string {
|
|
46
|
+
if (!this.icon) return "";
|
|
47
|
+
const name = this.icon.startsWith("fa-") ? this.icon : `fa-${this.icon}`;
|
|
48
|
+
return `fa ${name}`;
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style scoped>
|
|
55
|
+
.nrg-toggle-wrapper {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.nrg-toggle {
|
|
61
|
+
position: relative;
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
gap: 8px;
|
|
66
|
+
user-select: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.nrg-toggle__input {
|
|
70
|
+
position: absolute;
|
|
71
|
+
opacity: 0;
|
|
72
|
+
width: 0;
|
|
73
|
+
height: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.nrg-toggle__slider {
|
|
77
|
+
position: relative;
|
|
78
|
+
display: inline-block;
|
|
79
|
+
width: 36px;
|
|
80
|
+
min-width: 36px;
|
|
81
|
+
height: 20px;
|
|
82
|
+
background-color: var(--red-ui-secondary-border-color, #ccc);
|
|
83
|
+
border-radius: 10px;
|
|
84
|
+
transition: background-color 0.2s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.nrg-toggle__slider::after {
|
|
88
|
+
content: "";
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: 2px;
|
|
91
|
+
left: 2px;
|
|
92
|
+
width: 16px;
|
|
93
|
+
height: 16px;
|
|
94
|
+
background-color: white;
|
|
95
|
+
border-radius: 50%;
|
|
96
|
+
transition: transform 0.2s ease;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.nrg-toggle--checked .nrg-toggle__slider {
|
|
100
|
+
background-color: var(--red-ui-text-color-link, #0070d2);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.nrg-toggle--checked .nrg-toggle__slider::after {
|
|
104
|
+
transform: translateX(16px);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.nrg-toggle__label {
|
|
108
|
+
cursor: default;
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.nrg-toggle__label i {
|
|
113
|
+
margin-right: 2px;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
package/src/core/client/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import NodeRedConfigInput from "./components/node-red-config-input.vue";
|
|
|
9
9
|
import NodeRedSelectInput from "./components/node-red-select-input.vue";
|
|
10
10
|
import NodeRedEditorInput from "./components/node-red-editor-input.vue";
|
|
11
11
|
import NodeRedInputLabel from "./components/node-red-input-label.vue";
|
|
12
|
+
import NodeRedToggle from "./components/node-red-toggle.vue";
|
|
12
13
|
import NodeRedJsonSchemaForm from "./components/node-red-json-schema-form.vue";
|
|
13
14
|
|
|
14
15
|
const _schemas: Record<string, any> = {};
|
|
@@ -165,6 +166,7 @@ function createNodeRedVueApp(
|
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
app.component("NodeRedInputLabel", NodeRedInputLabel);
|
|
169
|
+
app.component("NodeRedToggle", NodeRedToggle);
|
|
168
170
|
app.component("NodeRedInput", NodeRedInput);
|
|
169
171
|
app.component("NodeRedTypedInput", NodeRedTypedInput);
|
|
170
172
|
app.component("NodeRedConfigInput", NodeRedConfigInput);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { serveNrgResources } from "./serve-nrg-resources";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import type { RED } from "../types";
|
|
4
|
+
|
|
5
|
+
const MIME: Record<string, string> = {
|
|
6
|
+
".js": "application/javascript",
|
|
7
|
+
".mjs": "application/javascript",
|
|
8
|
+
".css": "text/css",
|
|
9
|
+
".json": "application/json",
|
|
10
|
+
".map": "application/json",
|
|
11
|
+
".png": "image/png",
|
|
12
|
+
".svg": "image/svg+xml",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let _registered = false;
|
|
16
|
+
|
|
17
|
+
function serveNrgResources(RED: RED): void {
|
|
18
|
+
if (_registered) return;
|
|
19
|
+
_registered = true;
|
|
20
|
+
|
|
21
|
+
const clientDir = path.resolve(__dirname, "./resources");
|
|
22
|
+
if (!fs.existsSync(clientDir)) return;
|
|
23
|
+
|
|
24
|
+
const httpAdmin = (RED as any).httpAdmin;
|
|
25
|
+
if (!httpAdmin) return;
|
|
26
|
+
|
|
27
|
+
// /nrg/assets/ is not handled by Node-RED's editorApp, so our handler
|
|
28
|
+
// appended via use() is reached normally without any stack manipulation.
|
|
29
|
+
httpAdmin.use(function (req: any, res: any, next: any) {
|
|
30
|
+
const prefix = "/nrg/assets/";
|
|
31
|
+
if (!(req.path as string).startsWith(prefix)) return next();
|
|
32
|
+
let reqPath = (req.path as string).slice(prefix.length);
|
|
33
|
+
// Serve the Vue dev build in development for devtools support
|
|
34
|
+
if (
|
|
35
|
+
reqPath === "vue.esm-browser.prod.js" &&
|
|
36
|
+
process.env.NODE_ENV !== "production"
|
|
37
|
+
) {
|
|
38
|
+
const devPath = path.resolve(clientDir, "vue.esm-browser.js");
|
|
39
|
+
if (fs.existsSync(devPath)) {
|
|
40
|
+
reqPath = "vue.esm-browser.js";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const filePath = path.resolve(clientDir, reqPath);
|
|
44
|
+
const rel = path.relative(clientDir, filePath);
|
|
45
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return next();
|
|
46
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
|
|
47
|
+
return next();
|
|
48
|
+
const ext = path.extname(filePath);
|
|
49
|
+
res.setHeader("Content-Type", MIME[ext] ?? "application/octet-stream");
|
|
50
|
+
fs.createReadStream(filePath).pipe(res);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { serveNrgResources };
|
package/src/core/server/index.ts
CHANGED
|
@@ -1,60 +1,10 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
1
|
import { getCredentialsFromSchema } from "./utils";
|
|
4
2
|
import { Node } from "./nodes";
|
|
5
3
|
import { type RED } from "./types";
|
|
6
4
|
import { initValidator } from "./validator";
|
|
5
|
+
import { serveNrgResources } from "./api";
|
|
7
6
|
import { NrgError } from "../errors";
|
|
8
7
|
|
|
9
|
-
const MIME: Record<string, string> = {
|
|
10
|
-
".js": "application/javascript",
|
|
11
|
-
".mjs": "application/javascript",
|
|
12
|
-
".css": "text/css",
|
|
13
|
-
".json": "application/json",
|
|
14
|
-
".map": "application/json",
|
|
15
|
-
".png": "image/png",
|
|
16
|
-
".svg": "image/svg+xml",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
let _nrgResourcesRegistered = false;
|
|
20
|
-
|
|
21
|
-
function serveNrgResources(RED: RED): void {
|
|
22
|
-
if (_nrgResourcesRegistered) return;
|
|
23
|
-
_nrgResourcesRegistered = true;
|
|
24
|
-
|
|
25
|
-
const clientDir = path.resolve(__dirname, "./resources");
|
|
26
|
-
if (!fs.existsSync(clientDir)) return;
|
|
27
|
-
|
|
28
|
-
const httpAdmin = (RED as any).httpAdmin;
|
|
29
|
-
if (!httpAdmin) return;
|
|
30
|
-
|
|
31
|
-
// /nrg/assets/ is not handled by Node-RED's editorApp, so our handler
|
|
32
|
-
// appended via use() is reached normally without any stack manipulation.
|
|
33
|
-
httpAdmin.use(function (req: any, res: any, next: any) {
|
|
34
|
-
const prefix = "/nrg/assets/";
|
|
35
|
-
if (!(req.path as string).startsWith(prefix)) return next();
|
|
36
|
-
let reqPath = (req.path as string).slice(prefix.length);
|
|
37
|
-
// Serve the Vue dev build in development for devtools support
|
|
38
|
-
if (
|
|
39
|
-
reqPath === "vue.esm-browser.prod.js" &&
|
|
40
|
-
process.env.NODE_ENV !== "production"
|
|
41
|
-
) {
|
|
42
|
-
const devPath = path.resolve(clientDir, "vue.esm-browser.js");
|
|
43
|
-
if (fs.existsSync(devPath)) {
|
|
44
|
-
reqPath = "vue.esm-browser.js";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const filePath = path.resolve(clientDir, reqPath);
|
|
48
|
-
const rel = path.relative(clientDir, filePath);
|
|
49
|
-
if (rel.startsWith("..") || path.isAbsolute(rel)) return next();
|
|
50
|
-
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
|
|
51
|
-
return next();
|
|
52
|
-
const ext = path.extname(filePath);
|
|
53
|
-
res.setHeader("Content-Type", MIME[ext] ?? "application/octet-stream");
|
|
54
|
-
fs.createReadStream(filePath).pipe(res);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
8
|
type AnyNodeClass = (abstract new (...args: any[]) => Node) &
|
|
59
9
|
Partial<typeof Node>;
|
|
60
10
|
|
|
@@ -187,7 +137,7 @@ async function registerType(RED: RED, NodeClass: AnyNodeClass) {
|
|
|
187
137
|
RED.log.debug(`Type registered: ${NC.type}`);
|
|
188
138
|
}
|
|
189
139
|
|
|
190
|
-
type
|
|
140
|
+
type RegistrationFunction = ((RED: RED) => Promise<void>) & {
|
|
191
141
|
nodes: AnyNodeClass[];
|
|
192
142
|
};
|
|
193
143
|
|
|
@@ -199,7 +149,7 @@ type NodeRedPackageFunction = ((RED: RED) => Promise<void>) & {
|
|
|
199
149
|
*
|
|
200
150
|
* @param nodes - Array of node classes to register
|
|
201
151
|
*/
|
|
202
|
-
function registerTypes(nodes: AnyNodeClass[]):
|
|
152
|
+
function registerTypes(nodes: AnyNodeClass[]): RegistrationFunction {
|
|
203
153
|
const fn = async function (RED: RED) {
|
|
204
154
|
initValidator(RED);
|
|
205
155
|
serveNrgResources(RED);
|
|
@@ -214,12 +164,26 @@ function registerTypes(nodes: AnyNodeClass[]): NodeRedPackageFunction {
|
|
|
214
164
|
throw error;
|
|
215
165
|
}
|
|
216
166
|
};
|
|
217
|
-
(fn as
|
|
218
|
-
return fn as
|
|
167
|
+
(fn as RegistrationFunction).nodes = nodes;
|
|
168
|
+
return fn as RegistrationFunction;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface ModuleDefinition {
|
|
172
|
+
nodes: AnyNodeClass[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function defineModule(definition: ModuleDefinition): ModuleDefinition {
|
|
176
|
+
return definition;
|
|
219
177
|
}
|
|
220
178
|
|
|
221
|
-
export { registerType, registerTypes };
|
|
222
|
-
export {
|
|
179
|
+
export { registerType, registerTypes, defineModule };
|
|
180
|
+
export {
|
|
181
|
+
Node,
|
|
182
|
+
IONode,
|
|
183
|
+
ConfigNode,
|
|
184
|
+
defineIONode,
|
|
185
|
+
defineConfigNode,
|
|
186
|
+
} from "./nodes";
|
|
223
187
|
export { NrgError } from "../errors";
|
|
224
188
|
export type { RED } from "./types";
|
|
225
189
|
export { SchemaType, defineSchema } from "./schemas";
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { type TSchema } from "@sinclair/typebox";
|
|
2
|
+
import type { Schema } from "../schemas/types";
|
|
3
|
+
import type { RED } from "../types";
|
|
4
|
+
import type {
|
|
5
|
+
InferOr,
|
|
6
|
+
InferOutputs,
|
|
7
|
+
IONodeDefinition,
|
|
8
|
+
ConfigNodeDefinition,
|
|
9
|
+
HexColor,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import { IONode } from "./io-node";
|
|
12
|
+
import { ConfigNode } from "./config-node";
|
|
13
|
+
|
|
14
|
+
function defineIONode<
|
|
15
|
+
TConfigSchema extends TSchema | undefined = undefined,
|
|
16
|
+
TCredsSchema extends TSchema | undefined = undefined,
|
|
17
|
+
TSettingsSchema extends TSchema | undefined = undefined,
|
|
18
|
+
TInputSchema extends TSchema | undefined = undefined,
|
|
19
|
+
TOutputsSchema extends TSchema | readonly TSchema[] | undefined = undefined,
|
|
20
|
+
>(
|
|
21
|
+
def: IONodeDefinition<
|
|
22
|
+
TConfigSchema,
|
|
23
|
+
TCredsSchema,
|
|
24
|
+
TSettingsSchema,
|
|
25
|
+
TInputSchema,
|
|
26
|
+
TOutputsSchema
|
|
27
|
+
>,
|
|
28
|
+
) {
|
|
29
|
+
const NodeClass = class extends IONode<
|
|
30
|
+
InferOr<TConfigSchema, any>,
|
|
31
|
+
InferOr<TCredsSchema, any>,
|
|
32
|
+
InferOr<TInputSchema, any>,
|
|
33
|
+
InferOutputs<TOutputsSchema>,
|
|
34
|
+
InferOr<TSettingsSchema, any>
|
|
35
|
+
> {
|
|
36
|
+
static override readonly type: string = def.type;
|
|
37
|
+
static override readonly category: string = def.category ?? "function";
|
|
38
|
+
static override readonly color: HexColor = def.color ?? "#a6bbcf";
|
|
39
|
+
static override readonly inputs: number = def.inputs ?? 1;
|
|
40
|
+
static override readonly outputs: number = def.outputs ?? 1;
|
|
41
|
+
static override readonly paletteLabel = def.paletteLabel;
|
|
42
|
+
static override readonly inputLabels = def.inputLabels;
|
|
43
|
+
static override readonly outputLabels = def.outputLabels;
|
|
44
|
+
static override readonly align = def.align;
|
|
45
|
+
static override readonly labelStyle = def.labelStyle;
|
|
46
|
+
|
|
47
|
+
static override readonly configSchema: Schema | undefined =
|
|
48
|
+
def.configSchema as Schema | undefined;
|
|
49
|
+
static override readonly credentialsSchema: Schema | undefined =
|
|
50
|
+
def.credentialsSchema as Schema | undefined;
|
|
51
|
+
static override readonly settingsSchema: Schema | undefined =
|
|
52
|
+
def.settingsSchema as Schema | undefined;
|
|
53
|
+
static override readonly inputSchema: Schema | undefined =
|
|
54
|
+
def.inputSchema as Schema | undefined;
|
|
55
|
+
static override readonly outputsSchema: Schema | Schema[] | undefined =
|
|
56
|
+
def.outputsSchema as Schema | Schema[] | undefined;
|
|
57
|
+
static override readonly validateInput: boolean =
|
|
58
|
+
def.validateInput ?? false;
|
|
59
|
+
static override readonly validateOutput: boolean =
|
|
60
|
+
def.validateOutput ?? false;
|
|
61
|
+
|
|
62
|
+
static override _registered(RED: RED) {
|
|
63
|
+
this.validateSettings(RED);
|
|
64
|
+
return def.registered?.(RED);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async input(msg: InferOr<TInputSchema, any>) {
|
|
68
|
+
return def.input.call(this as any, msg);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override async created() {
|
|
72
|
+
if (def.created) return def.created.call(this as any);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override async closed(removed?: boolean) {
|
|
76
|
+
if (def.closed) return def.closed.call(this as any, removed);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
Object.defineProperty(NodeClass, "name", {
|
|
81
|
+
value: def.type.replace(/(^|-)(\w)/g, (_, __, c: string) =>
|
|
82
|
+
c.toUpperCase(),
|
|
83
|
+
),
|
|
84
|
+
configurable: true,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return NodeClass;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function defineConfigNode<
|
|
91
|
+
TConfigSchema extends TSchema | undefined = undefined,
|
|
92
|
+
TCredsSchema extends TSchema | undefined = undefined,
|
|
93
|
+
TSettingsSchema extends TSchema | undefined = undefined,
|
|
94
|
+
>(def: ConfigNodeDefinition<TConfigSchema, TCredsSchema, TSettingsSchema>) {
|
|
95
|
+
const NodeClass = class extends ConfigNode<
|
|
96
|
+
InferOr<TConfigSchema, any>,
|
|
97
|
+
InferOr<TCredsSchema, any>,
|
|
98
|
+
InferOr<TSettingsSchema, any>
|
|
99
|
+
> {
|
|
100
|
+
static override readonly type: string = def.type;
|
|
101
|
+
|
|
102
|
+
static override readonly configSchema: Schema | undefined =
|
|
103
|
+
def.configSchema as Schema | undefined;
|
|
104
|
+
static override readonly credentialsSchema: Schema | undefined =
|
|
105
|
+
def.credentialsSchema as Schema | undefined;
|
|
106
|
+
static override readonly settingsSchema: Schema | undefined =
|
|
107
|
+
def.settingsSchema as Schema | undefined;
|
|
108
|
+
|
|
109
|
+
static override _registered(RED: RED) {
|
|
110
|
+
this.validateSettings(RED);
|
|
111
|
+
return def.registered?.(RED);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override async created() {
|
|
115
|
+
if (def.created) return def.created.call(this as any);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override async closed(removed?: boolean) {
|
|
119
|
+
if (def.closed) return def.closed.call(this as any, removed);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
Object.defineProperty(NodeClass, "name", {
|
|
124
|
+
value: def.type.replace(/(^|-)(\w)/g, (_, __, c: string) =>
|
|
125
|
+
c.toUpperCase(),
|
|
126
|
+
),
|
|
127
|
+
configurable: true,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return NodeClass;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { defineIONode, defineConfigNode };
|
|
@@ -3,6 +3,7 @@ import type { RED } from "../../server/types";
|
|
|
3
3
|
import { validator } from "../validator";
|
|
4
4
|
import { Node } from "./node";
|
|
5
5
|
import type {
|
|
6
|
+
HexColor,
|
|
6
7
|
IONodeContext,
|
|
7
8
|
IONodeContextScope,
|
|
8
9
|
IONodeStatus,
|
|
@@ -19,7 +20,7 @@ abstract class IONode<
|
|
|
19
20
|
TSettings = any,
|
|
20
21
|
> extends Node<TConfig, TCredentials, TSettings> {
|
|
21
22
|
public static readonly align?: "left" | "right";
|
|
22
|
-
public static readonly color:
|
|
23
|
+
public static readonly color: HexColor;
|
|
23
24
|
public static readonly labelStyle?:
|
|
24
25
|
| "node_label"
|
|
25
26
|
| "node_label_italic"
|