Compare commits
590 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2feb290ae3 | ||
|
45bcb74dae | ||
|
6da890bf88 | ||
|
b8eeef182c | ||
|
54e0de64a7 | ||
|
3bd1b58217 | ||
|
71f8de94b0 | ||
|
4723378f2f | ||
|
8837597ff5 | ||
|
473b93f497 | ||
|
d9c5144fe2 | ||
|
cbc91a9ac8 | ||
|
88c946d401 | ||
|
ff565bacb4 | ||
|
e55301c073 | ||
|
dc69226944 | ||
|
bf6b18b8a6 | ||
|
e1b591d761 | ||
|
27463197cd | ||
|
854460db56 | ||
|
4984af48f1 | ||
|
e9dab46de8 | ||
|
4605f01c5d | ||
|
a0ddf96e03 | ||
|
16d25b9d41 | ||
|
77c4ccf8fb | ||
|
7d9e09f5a7 | ||
|
f4c184af4d | ||
|
9694c8bdfa | ||
|
ca61efc986 | ||
|
ffdbd94927 | ||
|
43df2318d4 | ||
|
21612a5215 | ||
|
756485e308 | ||
|
efbe38f509 | ||
|
daa76e6e5f | ||
|
48d2d269a5 | ||
|
13cac1b5ef | ||
|
479b7e756d | ||
|
503ef62cf5 | ||
|
b355a37378 | ||
|
1acc16c9ef | ||
|
4cbf672b26 | ||
|
272355a48e | ||
|
e2981f2970 | ||
|
804551000a | ||
|
254b6a1e23 | ||
|
3838e4e605 | ||
|
953b7584a3 | ||
|
3da22882e9 | ||
|
1e8f840993 | ||
|
4845a1f7eb | ||
|
a0952d9a07 | ||
|
7fa4e60c82 | ||
|
30eead76e6 | ||
|
100e5244c8 | ||
|
ed0399b855 | ||
|
27e9c18a4e | ||
|
c6895713ed | ||
|
965ca97ad1 | ||
|
7785ce0dc0 | ||
|
1a47e2fc76 | ||
|
b7e96ce6bc | ||
|
7a3741165b | ||
|
e9d5d20e2d | ||
|
867a6ad2da | ||
|
03507c2a1f | ||
|
aa79aa5479 | ||
|
16005a462d | ||
|
82c756b091 | ||
|
b139eb4a18 | ||
|
6af3c8c2a9 | ||
|
2c3fbb1467 | ||
|
01716119e6 | ||
|
a50a37ac26 | ||
|
11c4277466 | ||
|
7d284ce157 | ||
|
aa1d5ad06b | ||
|
00a3010933 | ||
|
56a4530ec6 | ||
|
89e40a0b8f | ||
|
66bd1feb47 | ||
|
b419e2e303 | ||
|
dae4ba8044 | ||
|
fe22afea6a | ||
|
69753a9940 | ||
|
f6e565ba04 | ||
|
e4fdf24545 | ||
|
43a9a3c3b1 | ||
|
bfd98aaf22 | ||
|
4e61c54be5 | ||
|
39a85c721d | ||
|
f9877f8d0b | ||
|
92dff4bacd | ||
|
338ddf17de | ||
|
4e6c8ea367 | ||
|
5f92bc83fd | ||
|
5e429f3be0 | ||
|
2a71175cd4 | ||
|
aee531bf16 | ||
|
2c99909353 | ||
|
50e821d5d7 | ||
|
06f3f3c0be | ||
|
0b09cf5fa9 | ||
|
93102837dd | ||
|
e8d81d814c | ||
|
f6cf051282 | ||
|
328390c2a9 | ||
|
6194285b6e | ||
|
84a2fbed2e | ||
|
5ce3cdb845 | ||
|
3e0b5f2fe8 | ||
|
94e3fdd7a9 | ||
|
69b413040f | ||
|
ffecf86281 | ||
|
4cb3ccc984 | ||
|
47e1389548 | ||
|
6d6e6fa416 | ||
|
ad615a76c8 | ||
|
e48607c743 | ||
|
046d56d692 | ||
|
604c70ec04 | ||
|
59a133cc13 | ||
|
0590d81e80 | ||
|
c8a02d53e8 | ||
|
deccfdf654 | ||
|
f2d72b1050 | ||
|
3d9bc265dd | ||
|
abe0b60bf7 | ||
|
54bf3f4402 | ||
|
d3219f0600 | ||
|
348ec34446 | ||
|
33a5b2527c | ||
|
443492d6eb | ||
|
a7ee31307e | ||
|
f67268b89a | ||
|
42382e1a03 | ||
|
419dfbf08b | ||
|
70aed23ef0 | ||
|
966064328f | ||
|
83696abf9d | ||
|
2fd7aee4da | ||
|
7555e0644f | ||
|
c363f375b6 | ||
|
2220956007 | ||
|
b3aff3a3e6 | ||
|
d0ad62a82b | ||
|
892933ff75 | ||
|
61fd01b871 | ||
|
2eba754801 | ||
|
a7b1ce0cf8 | ||
|
2854351909 | ||
|
fe9354d10b | ||
|
71cfa87303 | ||
|
802b116b01 | ||
|
27b54199f5 | ||
|
90ea3c15b3 | ||
|
acd500fe3d | ||
|
8988176c22 | ||
|
35f35705a5 | ||
|
a0033697ea | ||
|
49a3eded59 | ||
|
d50ccea017 | ||
|
3ef205fb33 | ||
|
b1bfba8b01 | ||
|
21832a0bd0 | ||
|
a0b4fc8372 | ||
|
3812ed5ed3 | ||
|
bdb545d6eb | ||
|
7650620a78 | ||
|
34d8b3ed5e | ||
|
e3acc49d5e | ||
|
c3da827222 | ||
|
1053fc5121 | ||
|
cec7a86b54 | ||
|
16c49306f3 | ||
|
32540dd0e6 | ||
|
a3c5b75368 | ||
|
f7a43f83e5 | ||
|
98ca0f163e | ||
|
5a3e6925e5 | ||
|
d270da74b4 | ||
|
204e0f636b | ||
|
1132d39118 | ||
|
f2227c8f65 | ||
|
55cfd6fa99 | ||
|
83b30f1c18 | ||
|
aa74d8160a | ||
|
f44868384e | ||
|
674eb36e15 | ||
|
e6882337c1 | ||
|
d48594220e | ||
|
64cdee6d36 | ||
|
a16c72b6a8 | ||
|
89839f433b | ||
|
3597759692 | ||
|
d4b001a74e | ||
|
d76b0d39cd | ||
|
92911f6714 | ||
|
380d3be819 | ||
|
6dfc5e0a41 | ||
|
d4f18c1731 | ||
|
e5a0d4094f | ||
|
163c1c5b98 | ||
|
912a30b28d | ||
|
de0546b251 | ||
|
1b375b81f3 | ||
|
b4bbae247d | ||
|
20ae4a7272 | ||
|
7f77a414ec | ||
|
2e5a3f4949 | ||
|
593726ecb8 | ||
|
9745904c5b | ||
|
8c53d95610 | ||
|
27604fef23 | ||
|
2c4dc3334d | ||
|
1ebb66f6ca | ||
|
aed9a868ab | ||
|
e6ca3af1ed | ||
|
edc01552f9 | ||
|
b4d29d4d4a | ||
|
b5785dab9c | ||
|
e67afb2611 | ||
|
f88e536ee0 | ||
|
509d609020 | ||
|
a4ec0f7959 | ||
|
884c887e0d | ||
|
e0059943df | ||
|
d11934beeb | ||
|
964271f9c7 | ||
|
97b773d257 | ||
|
b6a25518e3 | ||
|
b62c17f94b | ||
|
886fb45ad2 | ||
|
7b95e64a60 | ||
|
af42664d73 | ||
|
8af821d380 | ||
|
97ee6c6745 | ||
|
b5fb15cd9b | ||
|
998219ae9a | ||
|
e8f0f80f65 | ||
|
ff35c46d5d | ||
|
05c924a9df | ||
|
14ac309b6e | ||
|
8aec038c24 | ||
|
29b128e5e0 | ||
|
bda9f249bc | ||
|
c0f1581370 | ||
|
9420a52ec7 | ||
|
c113b3de13 | ||
|
29058c163a | ||
|
bb110ea230 | ||
|
785f220cd8 | ||
|
16570410a5 | ||
|
8085eda431 | ||
|
6503498f0a | ||
|
10ac7fc369 | ||
|
da787a9993 | ||
|
c873b57094 | ||
|
93974ccd92 | ||
|
d7aa792f97 | ||
|
375fa9da64 | ||
|
28c41e17ad | ||
|
da3ad40968 | ||
|
2464d9ad95 | ||
|
011b47a108 | ||
|
ea747711c3 | ||
|
19a8fa09a8 | ||
|
bea08706cc | ||
|
7950ee1241 | ||
|
a743764345 | ||
|
cc1c87387b | ||
|
1b5b3f7f88 | ||
|
2123514c76 | ||
|
379fbd7c7e | ||
|
efdc1b1a1d | ||
|
52bbd82e18 | ||
|
20a9c051be | ||
|
830e475969 | ||
|
ed4b98b598 | ||
|
f75e2f221c | ||
|
5a75440668 | ||
|
53e092e484 | ||
|
53d8b97fff | ||
|
c85667cc13 | ||
|
1a8b37b4e3 | ||
|
40a2d90e08 | ||
|
ee269caa4a | ||
|
d820686e5a | ||
|
aa2a585e00 | ||
|
f9e6bccd46 | ||
|
3230654ecd | ||
|
eab512ef22 | ||
|
a5b53ee373 | ||
|
ac420247ae | ||
|
61198bd7e3 | ||
|
9c511b6674 | ||
|
9d054543a7 | ||
|
fc3ec2a0d7 | ||
|
e7ef73222f | ||
|
6623e56a1e | ||
|
582eca1877 | ||
|
2783100f84 | ||
|
cb0c484579 | ||
|
a1bf270ba6 | ||
|
be5694f149 | ||
|
4ff364e2c3 | ||
|
2fa6f35873 | ||
|
a622d19ba7 | ||
|
9842d9116c | ||
|
19ea8f8515 | ||
|
dbd3f0f85b | ||
|
48a2876c48 | ||
|
10398b05d8 | ||
|
3a91fc17fd | ||
|
3952a23ba3 | ||
|
bffa923f05 | ||
|
27fc89ba33 | ||
|
bd2c020e84 | ||
|
da7c7ede02 | ||
|
34ed9c5cd8 | ||
|
47dd08e74a | ||
|
6aae50294f | ||
|
ec2f6ec46f | ||
|
36805b6872 | ||
|
d78cb2fec7 | ||
|
51edb1ef19 | ||
|
2ad3af1864 | ||
|
525d7356fe | ||
|
bdf37b0546 | ||
|
0d330332f1 | ||
|
a5f290fd47 | ||
|
46f4144172 | ||
|
356e332f66 | ||
|
d16060bdd9 | ||
|
3aeb4bd868 | ||
|
b8bcab109a | ||
|
b94045fd86 | ||
|
87992823d5 | ||
|
93b914d4b0 | ||
|
d115d6c241 | ||
|
e561efb5c5 | ||
|
cd95c63daf | ||
|
bc6a4a20d2 | ||
|
25205f7440 | ||
|
6317420d4a | ||
|
f53bdc8257 | ||
|
7bd61f2c96 | ||
|
76338d4d32 | ||
|
b9add237b2 | ||
|
b66af1c8e2 | ||
|
06bad61569 | ||
|
f58766eacf | ||
|
8e62b2a749 | ||
|
9b86874c2d | ||
|
805ed593fb | ||
|
c604ac2207 | ||
|
ca37d1ec9d | ||
|
3fd2d07c75 | ||
|
b76d692a65 | ||
|
6600910163 | ||
|
a6973bd7ed | ||
|
d58127730f | ||
|
5494c167fc | ||
|
c5ae0be7b1 | ||
|
b653914ee0 | ||
|
c107c5fc92 | ||
|
0980c03129 | ||
|
f6c3fdc806 | ||
|
2c2628d816 | ||
|
56fe2801eb | ||
|
87b1ee9642 | ||
|
e1c36d232b | ||
|
13ee8cec24 | ||
|
a977b87cb3 | ||
|
14dfb9aef8 | ||
|
d520cde57a | ||
|
70167d7d1d | ||
|
3389c8160b | ||
|
c214710f8e | ||
|
ac6a4945cb | ||
|
fd1a001a23 | ||
|
f3c561cd86 | ||
|
4e33e785fb | ||
|
edc5e88d5a | ||
|
47bf166a6e | ||
|
cf26209790 | ||
|
e55ebde170 | ||
|
a745ddc164 | ||
|
18d0fa2259 | ||
|
d706c9cb37 | ||
|
20d2450cac | ||
|
34345461f1 | ||
|
aa372a1707 | ||
|
0e0bba25c1 | ||
|
af701d65ac | ||
|
b02f69b77a | ||
|
598b0c84ab | ||
|
22cc8da088 | ||
|
a70618cdef | ||
|
faf142cf66 | ||
|
1a3cc06935 | ||
|
a712a9363b | ||
|
67e716466f | ||
|
3fae03da98 | ||
|
361391ceb8 | ||
|
bf0ca38350 | ||
|
437c28e2b8 | ||
|
c05d18ada1 | ||
|
cfb300ec06 | ||
|
236e668201 | ||
|
c9b902c2b4 | ||
|
ac8b1e19b7 | ||
|
595933d046 | ||
|
789426f80e | ||
|
148e64c3da | ||
|
c6289ebb2c | ||
|
5f4ece6813 | ||
|
c990ec39d6 | ||
|
1fdc600ecd | ||
|
e354d2ce29 | ||
|
d218af8619 | ||
|
d938e5fb6b | ||
|
3075b82792 | ||
|
240082481f | ||
|
ea95552285 | ||
|
5358b06123 | ||
|
99391431da | ||
|
d396f50a9a | ||
|
affa8ea42b | ||
|
d711b01fe5 | ||
|
6e7fa6f921 | ||
|
343cde75a2 | ||
|
2dc446e45b | ||
|
884b7fa16a | ||
|
173e065b68 | ||
|
66f4008bb8 | ||
|
e9efe493f9 | ||
|
3bd782e62a | ||
|
9b49cb2b50 | ||
|
963fe87f14 | ||
|
40060a470b | ||
|
a6e8fbb54a | ||
|
ab7e9f94fa | ||
|
28e9ccd372 | ||
|
9a66d9addd | ||
|
8843bda477 | ||
|
3278303eec | ||
|
f5fd6e3a36 | ||
|
a173e8e70f | ||
|
b20c5f3a8d | ||
|
014f206e9c | ||
|
068b93befa | ||
|
65d8872cea | ||
|
bffd1d61b2 | ||
|
4788b81220 | ||
|
9a07fc03c6 | ||
|
954f518030 | ||
|
9f8ff71757 | ||
|
06dd59dc81 | ||
|
2531a5283a | ||
|
4cc1a5d846 | ||
|
2d3e5f4ce0 | ||
|
5135545c6c | ||
|
fef93818c9 | ||
|
50baad9624 | ||
|
fc0041bd91 | ||
|
283f7f5992 | ||
|
1cf7b95891 | ||
|
7bdb8db5ff | ||
|
de27968e4e | ||
|
d31abda28f | ||
|
dcfb4c9a79 | ||
|
bf065ee11d | ||
|
5e3cbadffc | ||
|
535ef82e48 | ||
|
c368bfea3f | ||
|
4498e4100e | ||
|
4f1e4faede | ||
|
9a19a1113e | ||
|
1cd550022b | ||
|
bad08bafd7 | ||
|
f041a21f22 | ||
|
712d78ca39 | ||
|
93f2910bd2 | ||
|
d0ef12c486 | ||
|
241fd09053 | ||
|
208dd2a457 | ||
|
e34ee44b21 | ||
|
d5f59307b7 | ||
|
64136cc565 | ||
|
3e2508c740 | ||
|
0853cd65b2 | ||
|
01802c817b | ||
|
7e10093bb8 | ||
|
54c17c3175 | ||
|
80e60538e2 | ||
|
84a76909e2 | ||
|
033405fdbc | ||
|
9444009a9b | ||
|
29e9def314 | ||
|
8832a1aa20 | ||
|
5beb6dbeee | ||
|
1261d26b23 | ||
|
0b9dd11fff | ||
|
08a607aa6a | ||
|
e12efc320b | ||
|
3ded9de803 | ||
|
d5b424910f | ||
|
d94d13737f | ||
|
b1fa4918e3 | ||
|
742aa2fa0d | ||
|
ce133c1c04 | ||
|
e4dc1779c3 | ||
|
22b4ab6bb2 | ||
|
2dcff51125 | ||
|
b50e0533eb | ||
|
711545539f | ||
|
a6cbceed28 | ||
|
837d17ab65 | ||
|
eff31c4bdc | ||
|
6a8f653b73 | ||
|
0cdb36f73d | ||
|
db249356e6 | ||
|
d509c1a57c | ||
|
6802539ccc | ||
|
74efaa3c2d | ||
|
a5223709ba | ||
|
2291dc6132 | ||
|
b2548c158d | ||
|
5a48d6d4cd | ||
|
7ee2b93b10 | ||
|
cc611a7a02 | ||
|
1a9c34fe40 | ||
|
ff8eb0ec2b | ||
|
f66b48e586 | ||
|
931a2344b4 | ||
|
dd3c75d298 | ||
|
4a4a15de93 | ||
|
a007ab7f2e | ||
|
7b01457038 | ||
|
54e6d60fe5 | ||
|
c2710f4f6f | ||
|
20187b51b1 | ||
|
4be6d57d98 | ||
|
282d52cf0b | ||
|
a77f8cc3e9 | ||
|
ea4c0cdbee | ||
|
ba08cf0417 | ||
|
7197153fd5 | ||
|
b9c1dedab3 | ||
|
918943816f | ||
|
33cf34f7c7 | ||
|
febc769df5 | ||
|
ea483218ea | ||
|
c8f3ad8ac7 | ||
|
7916dc9c05 | ||
|
3123a5ee51 | ||
|
5b5b06cc06 | ||
|
f49f692ffa | ||
|
10ce681d46 | ||
|
08c6ea94cb | ||
|
fea1da5542 | ||
|
32e8f4eac6 | ||
|
bfe5a8a986 | ||
|
f2cb5ea44e | ||
|
c7335ed25b | ||
|
5fda57c730 | ||
|
3df3096bb4 | ||
|
bb10d5bb94 | ||
|
1704ab7454 | ||
|
3275a76fb0 | ||
|
81937ddc45 | ||
|
9fd929ac1e | ||
|
c48a15c915 | ||
|
eb940d6d57 | ||
|
9091935d77 | ||
|
34e8d2b051 | ||
|
0c2ab13c48 | ||
|
9489953a8f | ||
|
b0136d03ea | ||
|
9fe73645ad | ||
|
54d4079457 | ||
|
8e1a21e682 | ||
|
d84cdca43e | ||
|
1c6dcd373d | ||
|
4410ce1486 | ||
|
cef3a01042 | ||
|
0c042abcab | ||
|
f61971bc23 |
8
.github/workflows/tests.yml
vendored
@ -12,12 +12,11 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
permissions:
|
permissions:
|
||||||
checks: write # for coverallsapp/github-action to create new checks
|
|
||||||
contents: read # for actions/checkout to fetch code
|
contents: read # for actions/checkout to fetch code
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16, 18, 20]
|
node-version: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
@ -29,8 +28,3 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
npm run test
|
npm run test
|
||||||
# - name: Publish to coveralls.io
|
|
||||||
# if: ${{ matrix.node-version == 16 }}
|
|
||||||
# uses: coverallsapp/github-action@v1.1.2
|
|
||||||
# with:
|
|
||||||
# github-token: ${{ github.token }}
|
|
||||||
|
1
.gitignore
vendored
@ -28,3 +28,4 @@ docs
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
sync.ffs_db
|
sync.ffs_db
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
.editorconfig
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
/Gruntfile.js
|
|
||||||
/.git/*
|
|
||||||
*.backup
|
|
||||||
/public/*
|
|
923
CHANGELOG.md
@ -1,14 +1,193 @@
|
|||||||
#### 3.1.11: Maintenance Release
|
#### 4.0.9: Maintenance Release
|
||||||
|
|
||||||
- Add/Update German Translations for delay node (#4762) @dceejay
|
Editor
|
||||||
- Update ws dependency
|
|
||||||
|
|
||||||
#### 3.1.10: Maintenance Release
|
- Add details for the dynamic subscription to match the English docs (#5050) @aikitori
|
||||||
|
- Fix tooltip snapping based on `typedInput` type (#5051) @GogoVega
|
||||||
|
- Prevent symbol usage warning in monaco (#5049) @Steve-Mcl
|
||||||
|
- Show subflow flow context under node section of sidebar (#5025) @knolleary
|
||||||
|
- feat: Add custom label for default deploy button in settings.editorTheme (#5030) @matiseni51
|
||||||
|
- Handle long auto-complete suggests (#5042) @knolleary
|
||||||
|
- Handle undefined username when generating user icon (#5043) @knolleary
|
||||||
|
- Handle dragging node into group and splicing link at same time (#5027) @knolleary
|
||||||
|
- Remember context sidebar tree state when refreshing (#5021) @knolleary
|
||||||
|
- Update sf instance env vars when removed from template (#5023) @knolleary
|
||||||
|
- Do not select group when triggering quick-add within it (#5022) @knolleary
|
||||||
|
- Fix library icon handling within library browser component (#5017) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
- Allow env var access to context (#5016) @knolleary
|
||||||
|
- fix debug status reporting if null (#5018) @dceejay
|
||||||
|
- Fix grunt dev via better ndoemon ignore rules (#5015) @knolleary
|
||||||
|
- Fix typo in CHANGELOG (4.0.7-->4.0.8) (#5007) @natcl
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
- Switch: Avoid exceeding call stack when draining message group in Switch (#5014) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.8: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix config node sort order when importing (#5000) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.7: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix def can be undefined if the type is missing (#4997) @GogoVega
|
||||||
|
- Fix the user list of nested config node (#4995) @GogoVega
|
||||||
|
- Support custom login message and button (#4993) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.6: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Roll up various fixes on config node change history (#4975) @knolleary
|
||||||
|
- Add quotes when installing local tgz to fix spacing in the file path (#4949) @AGhorab-upland
|
||||||
|
- Validate json dropped into editor to avoid unhelpful error messages (#4964) @knolleary
|
||||||
|
- Fix junction insert position via context menu (#4974) @knolleary
|
||||||
|
- Apply zoom scale when calculating annotation positions (#4981) @knolleary
|
||||||
|
- Handle the import of an incomplete Subflow (#4811) @GogoVega
|
||||||
|
- Fix updating the Subflow name during a copy (#4809) @GogoVega
|
||||||
|
- Rename variable to avoid confusion in view.js (#4963) @knolleary
|
||||||
|
- Change groups.length to groups.size (#4959) @hungtcs
|
||||||
|
- Remove disabled node types from QuickAddDialog list (#4946) @GogoVega
|
||||||
|
- Fix `setModulePendingUpdated` with plugins (#4939) @GogoVega
|
||||||
|
- Missing getSubscriptions in the docs while its implemented (#4934) @ersinpw
|
||||||
|
- Apply `envVarExcludes` setting to `util.getSetting` into the function node (#4925) @GogoVega
|
||||||
|
- Fix `envVar` editable list should be sortable (#4932) @GogoVega
|
||||||
|
- Improve the node name auto-generated with the first available number (#4912) @GogoVega
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Get the env config node from the parent subflow (#4960) @GogoVega
|
||||||
|
- Update dependencies (#4987) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Performance : make reading single buffer / string file faster by not re-allocating and handling huge buffers (#4980) @Fadoli
|
||||||
|
- Make delay node rate limit reset consistent - not send on reset. (#4940) @dceejay
|
||||||
|
- Fix trigger node date handling for latest time type input (#4915) @dceejay
|
||||||
|
- Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973)
|
||||||
|
- Ensure node.sep is honoured when generating CSV (#4982) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.5: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Refix link call node can call out of a subflow (#4908) @GogoVega
|
||||||
|
|
||||||
|
#### 4.0.4: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix `link call` node can call out of a subflow (#4892) @GogoVega
|
||||||
|
- Fix wrong unlock state when event is triggered after deployment (#4889) @GogoVega
|
||||||
|
- i18n(App) update with latest language file changes (#4903) @joebordes
|
||||||
|
- fix typo: depreciated (#4895) @dxdc
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Update dev dependencies (#4893) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- MQTT: Allow msg.userProperties to have number values (#4900) @hardillb
|
||||||
|
|
||||||
|
#### 4.0.3: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Refresh page title after changing tab name (#4850) @kazuhitoyokoi
|
||||||
|
- Add Japanese translations for v4.0.2 (again) (#4853) @kazuhitoyokoi
|
||||||
|
- Stay in quick-add mode following context menu insert (#4883) @knolleary
|
||||||
|
- Do not include Junction type in quick-add for virtual links (#4879) @knolleary
|
||||||
|
- Multiplayer cursor tracking (#4845) @knolleary
|
||||||
|
- Hide add-flow options when disabled via editorTheme (#4869) @knolleary
|
||||||
|
- Fix env-var config select when multiple defined (#4872) @knolleary
|
||||||
|
- Fix subflow outbound-link filter (#4857) @GogoVega
|
||||||
|
- Add French translations for v4.0.2 (#4856) @GogoVega
|
||||||
|
- Fix moving link wires (#4851) @knolleary
|
||||||
|
- Adjust type search dialog position to prevent x-overflow (#4844) @Steve-Mcl
|
||||||
|
- fix: modulesInUse might be undefined (#4838) @lorenz-maurer
|
||||||
|
- Add Japanese translations for v4.0.2 (#4849) @kazuhitoyokoi
|
||||||
|
- Fix menu to enable/disable selection when it's a group (#4828) @GogoVega
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Update dependencies (#4874) @knolleary
|
||||||
|
- GitHub: Add citation file to enable "Cite this repository" feature (#4861) @lobis
|
||||||
|
- Remove use of util.log (#4875) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Fix invalid property error in range node example (#4855)
|
||||||
|
- Fix typo in flow example name (#4854) @kazuhitoyokoi
|
||||||
|
- Move SNI, ALPN and Verify Server cert out of check (#4882) @hardillb
|
||||||
|
- Set status of mqtt nodes to "disconnected" when deregistered from broker (#4878) @Steve-Mcl
|
||||||
|
- MQTT: Ensure will payload is a string (#4873) @knolleary
|
||||||
|
- Let batch node terminate "early" if msg.parts set to end of sequence (#4829) @dceejay
|
||||||
|
- Fix unintentional Capitalisation in Split node name (#4835) @dceejay
|
||||||
|
|
||||||
|
#### 4.0.2: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Use a more subtle border on the header (#4818) @bonanitech
|
||||||
|
- Improve the editor's French translations (#4824) @GogoVega
|
||||||
|
- Clean up orphaned editors (#4821) @Steve-Mcl
|
||||||
|
- Fix node validation if the property is not required (#4812) @GogoVega
|
||||||
|
- Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Allow auth cookie name to be customised (#4815) @knolleary
|
||||||
|
- Guard against undefined sessions in multiplayer (#4816) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.1: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Ensure subflow instance credential property values are extracted (#4802) @knolleary
|
||||||
|
- Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega
|
||||||
|
- Fix the config node select value assignment (#4788) @GogoVega
|
||||||
|
- Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi
|
||||||
|
- Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Ensure group nodes are properly exported in /flow api (#4803) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Joins: make using msg.parts optional in join node (#4796) @dceejay
|
||||||
|
- HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl
|
||||||
|
- HTTP Request: Remove default user agent (#4791) @Steve-Mcl
|
||||||
|
|
||||||
|
#### 4.0.0: Milestone Release
|
||||||
|
|
||||||
|
This marks the next major release of Node-RED. The following changes represent
|
||||||
|
those added since the last beta. Check the beta release details below for the complete
|
||||||
|
list.
|
||||||
|
|
||||||
|
Breaking Changes
|
||||||
|
|
||||||
|
- Node-RED now requires Node 18.x or later. At the time of release, we recommend
|
||||||
|
using Node 20.
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Add `httpStaticCors` (#4761) @knolleary
|
||||||
|
- Update dependencies (#4763) @knolleary
|
||||||
|
- Sync master to dev (#4756) @knolleary
|
||||||
|
- Add tooltip and message validation to `typedInput` (#4747) @GogoVega
|
||||||
|
- Replace bcrypt with @node-rs/bcrypt (#4744) @knolleary
|
||||||
|
- Export Nodes dialog refinement (#4746) @Steve-Mcl
|
||||||
|
|
||||||
|
#### 4.0.0-beta.4: Beta Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
- Include rewired nodes when calculating Modified Flows stop list (#4754) @knolleary
|
|
||||||
- Fix clone of group env var properties (#4753) @knolleary
|
|
||||||
- Fix losing links when importing a copy of links into a subflow (#4750) @GogoVega
|
|
||||||
- Ensure all CSS variables are in the output file (#3743) @bonanitech
|
|
||||||
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
|
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
|
||||||
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
|
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
|
||||||
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
|
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
|
||||||
@ -19,669 +198,109 @@
|
|||||||
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
|
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
|
||||||
- Add missing tooltips to Sidebar (#4713) @GogoVega
|
- Add missing tooltips to Sidebar (#4713) @GogoVega
|
||||||
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
|
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
|
||||||
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
|
- Update to Monaco 0.49.0 (#4725) @Steve-Mcl
|
||||||
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
|
- Add Japanese translations for 4.0.0-beta.3 (#4726) @kazuhitoyokoi
|
||||||
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
|
- Show lock on deploy if user is read-only (#4706) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Ensure all CSS variables are in the output file (#3743) @bonanitech
|
||||||
|
- Add httpAdminCookieOptions (#4718) @knolleary
|
||||||
|
- chore: migrate deprecated `util.isArray` (#4724) @Rotzbua
|
||||||
|
- Add --version cli args (#4707) @knolleary
|
||||||
|
- feat(grunt): fail if files are missing (#4739) @Rotzbua
|
||||||
|
- fix(node-red-pi): node-red not started by path (#4736) @Rotzbua
|
||||||
|
- fix(editor): remove trailing slash (#4735) @Rotzbua
|
||||||
|
- fix: remove deprecated mqtt.js (#4733) @Rotzbua
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Perform Proxy logic more like cURL (#4616) @Steve-Mcl
|
||||||
|
|
||||||
|
#### 4.0.0-beta.3: Beta Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Improve background-deploy notification handling (#4692) @knolleary
|
||||||
|
- Hide workspace tab on middle mouse click (#4657) @Steve-Mcl
|
||||||
|
- multiplayer: Add user presence indicators (#4666) @knolleary
|
||||||
|
- Enable updating dependency node of package.json in project feature (#4676) @kazuhitoyokoi
|
||||||
|
- Add French translations for 4.0.0-beta.2 (#4681) @GogoVega
|
||||||
|
- Add Japanese translations for 4.0.0-beta.2 (#4674) @kazuhitoyokoi
|
||||||
|
- Fix saving of conf-type properties in module packaged subflows (#4658) @knolleary
|
||||||
|
- Add npm install timeout notification (#4662) @hardillb
|
||||||
- Fix undo of subflow env property edits (#4667) @knolleary
|
- Fix undo of subflow env property edits (#4667) @knolleary
|
||||||
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
|
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
|
||||||
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
|
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
|
||||||
|
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
|
||||||
#### 3.1.9: Maintenance Release
|
|
||||||
|
|
||||||
- Prevent subflow being added to itself (#4654) @knolleary
|
|
||||||
- Fix use of spawn on windows with cmd files (#4652) @knolleary
|
|
||||||
- Guard refresh of unknown subflow (#4640) @knolleary
|
|
||||||
- Fix subflow module sending messages to debug sidebar (#4642) @knolleary
|
|
||||||
|
|
||||||
#### 3.1.8: Maintenance Release
|
|
||||||
|
|
||||||
- Add validation and error handling on subflow instance properties (#4632) @knolleary
|
|
||||||
- Hide import/export context menu if disabled in theme (#4633) @knolleary
|
|
||||||
- Show change indicator on subflow tabs (#4631) @knolleary
|
|
||||||
- Bump dependencies (#4630) @knolleary
|
|
||||||
- Reset workspace index when clearing nodes (#4619) @knolleary
|
|
||||||
- Remove typo in global config (#4613) @kazuhitoyokoi
|
|
||||||
|
|
||||||
#### 3.1.7: Maintenance Release
|
|
||||||
|
|
||||||
- Add Japanese translation for v3.1.6 (#4603) @kazuhitoyokoi
|
|
||||||
- Update jsonata version (#4593) @hardillb
|
|
||||||
|
|
||||||
#### 3.1.6: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Do not flag env var in num typedInput as error (#4582) @knolleary
|
|
||||||
- Fix example flow name in import dialog (#4578) @kazuhitoyokoi
|
|
||||||
- Fix missing node icons in workspace (#4570) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Handle undefined env vars (#4581) @knolleary
|
- Allow blank strings to be used for env var property substitutions (#4672) @knolleary
|
||||||
- fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 … (#4568) @JaysonHurst
|
- Use rfdc for cloning pure JSON values (#4679) @knolleary
|
||||||
- chore: remove never use import code (#4580) @giscafer
|
- fix: remove outdated Node 11+ check (#4314) @Rotzbua
|
||||||
|
- feat(ci): add new nodejs v22 (#4694) @Rotzbua
|
||||||
|
- fix(node): increase required node >=18.5 (#4690) @Rotzbua
|
||||||
|
- fix(dns): remove outdated node check (#4689) @Rotzbua
|
||||||
|
- fix(polyfill): remove import module polyfill (#4688) @Rotzbua
|
||||||
|
- Fix typo (#4686) @Rotzbua
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- fix: template node zh-CN translation (#4575) @giscafer
|
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
|
||||||
|
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
|
||||||
|
|
||||||
#### 3.1.5: Maintenance Release
|
#### 4.0.0-beta.2: Beta Release
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Fix require of dns module (#4562) @knolleary
|
|
||||||
- Ensure global creds object is initialised when adding first cred (#4561) @knolleary
|
|
||||||
|
|
||||||
#### 3.1.4: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Highlight errors in config node sidebar (#4529) @knolleary
|
- Introduce multiplayer feature (#4629) @knolleary
|
||||||
- Improve feedback in import dialog to show conflicted nodes (#4550) @knolleary
|
- Separate the "add new config-node" option into a new (+) button (#4627) @GogoVega
|
||||||
- Modify node users info in config editor footer (#4528) @knolleary
|
- Retain Palette categories collapsed and filter to localStorage (#4634) @knolleary
|
||||||
- Handle modified-nodes deploy after replacing unknown config node (#4556) @knolleary
|
- Ensure palette filter reapplies and clear up unknown categories (#4637) @knolleary
|
||||||
- Handle undefined default export when importing module (#4539) @knolleary
|
- Add support for plugin (only) modules to the palette manager (#4620) @knolleary
|
||||||
- Fix icon scaling for non .svg icons (#4491) @ralphwetzel
|
- Update monaco to latest and node types to 18 LTS (#4615) @Steve-Mcl
|
||||||
- (convertNode) Do not create the credentials object if there is nothing to export (#4544) @GogoVega
|
|
||||||
- Ensure subflow instance node has g property set (#4538) @knolleary
|
|
||||||
- Handle importing flow with existing subflow and instance node (#4546) @knolleary
|
|
||||||
- Update index.mst (#4483) @gorenje
|
|
||||||
- Include top level property name when copying path from context (#4527) @knolleary
|
|
||||||
- Add handling to disable items on context menu (#4500) @kazuhitoyokoi
|
|
||||||
- Focus Quick Add dialog from context menu (#4516) @kazuhitoyokoi
|
|
||||||
- Fix subflow ports in Quick Add dialog (#4518) @kazuhitoyokoi
|
|
||||||
- Fix location of subflow ports in palette (#4502) @kazuhitoyokoi
|
|
||||||
- Client/Editor Events: fix off-in-on pattern emulating once (#4484) @gorenje
|
|
||||||
- Restore caching busting functionality without using explict version number (#4512) @knolleary
|
|
||||||
- Do not translate the list of available languages (#4531) @GogoVega
|
|
||||||
- Add French translation of v3.1.3 changes (#4477) @GogoVega
|
|
||||||
- i18n(es-ES) Spanish Spain translation (#4495) @joebordes
|
|
||||||
- Add missing validation messages (#4487) @GogoVega
|
|
||||||
- Add Japanese translations for v3.1.3 (#4498) @kazuhitoyokoi
|
|
||||||
- Replace `rename` by `edit` for the menu flow label (#4506) @GogoVega
|
|
||||||
- Update editor.json fix typo in German translation (#4552) @guidoffm
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Bump the github-actions group with 1 update (#4554) @app/dependabot
|
- Fix handling of subflow config-node select type in sf module (#4643) @knolleary
|
||||||
- Clone objects types when getting env values (#4519) @knolleary
|
- Comms API updates (#4628) @knolleary
|
||||||
- Ensure global-config credential env vars are merged on deploy (#4526) @knolleary
|
- Add French translations for 4.0.0-beta.1 (#4621) @GogoVega
|
||||||
|
- Add Japanese translations for 4.0.0-beta.1 (#4612) @kazuhitoyokoi
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
- Fix change node handling of replacing with boolean (#4639) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.0-beta.1: Beta Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Click on id in debug panel highlights node or flow (#4439) @ralphwetzel
|
||||||
|
- Support config selection in a subflow env var (#4587) @Steve-Mcl
|
||||||
|
- Add timestamp formatting options to TypedInput (#4468) @knolleary
|
||||||
|
- Allow RED.view.select to select links (#4553) @lgrkvst
|
||||||
|
- Add auto-complete to flow/global/env typedInput types (#4480) @knolleary
|
||||||
|
- Improve the appearance of the Node-RED primary header (#4598) @joepavitt
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- let settings.httpNodeAuth accept single middleware or array of middlewares (#4572) @kevinGodell
|
||||||
|
- Upgrade to JSONata 2.x (#4590) @knolleary
|
||||||
|
- Bump minimum version to node 18 (#4571) @knolleary
|
||||||
|
- npm: Remove production flag on npm invocation (#4347) @ZJvandeWeg
|
||||||
|
- Timer testing fix (#4367) @hlovdal
|
||||||
|
- Bump to 4.0.0-dev (#4322) @knolleary
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- 21-httprequest.js remove unused code, because of broken use of toLowercase (#4522) @gorenje
|
- TCP node - when resetting, if no payload, stay disconnected @dceejay
|
||||||
|
- HTML node: add option for collecting attributes and content (#4513) @gorenje
|
||||||
#### 3.1.3: Maintenance Release
|
- let split node specify property to split on, and join auto join correctly (#4386) @dceejay
|
||||||
|
- Add RFC4180 compliant mode to CSV node (#4540) @Steve-Mcl
|
||||||
Editor
|
- Fix change node to return boolean if asked (#4525) @dceejay
|
||||||
|
- Let msg.reset reset Tcp request node connection when in stay connected mode (#4406) @dceejay
|
||||||
- Add missing en-us messages (#4475) @knolleary
|
- Let debug node status msg length be settable via settings (#4402) @dceejay
|
||||||
|
- Feat: Add ability to set headers for WebSocket client (#4436) @marcus-j-davies
|
||||||
#### 3.1.2: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Relax some node validators to allow undefined value (#4471) @knolleary
|
|
||||||
- Fix switch validation of typeof field (#4465) @knolleary
|
|
||||||
- Use move cursor when hovering on group border (#4467) @knolleary
|
|
||||||
- Added action list Chinese (Simplified and Traditional) translation + v3.1.1 changes (#4470) @wangyiyi2056
|
|
||||||
- Add French translation of `action-list` + v3.1.1 changes (#4466) @GogoVega
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Ensure nested groups inside subflows have their g props remapped (#4472) @knolleary
|
|
||||||
|
|
||||||
#### 3.1.1: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Fix debug filter (#4461) @knolleary
|
|
||||||
- Fix various issues with debug pop-out window (#4459) @knolleary
|
|
||||||
- Ensure subflow instances keep track of their groups (#4457) @knolleary
|
|
||||||
- Fix `validateNodeProperty` without validator provided (#4455) @GogoVega
|
|
||||||
- Debounce node-removed notifications (#4453) @knolleary
|
|
||||||
- Don't try to load the parents of the first commit (#4448) @bonanitech
|
|
||||||
- Allow a theme to specifiy which theme mermaid should use (#4441) @knolleary
|
|
||||||
- Update browser title with flow name if set (#4427) @knolleary
|
|
||||||
- Ensure typeSearch handles undefined node definitions (#4423) @knolleary
|
|
||||||
- Ensure group w/h are imported if present (#4426) @knolleary
|
|
||||||
- Hide node status background when there is no status to show (#4425) @knolleary
|
|
||||||
- Add a close button to the restart-required notification (#4407) @knolleary
|
|
||||||
- Extend typedInput "num" type validity check to NaN, binary, octal & hex (#4371) @ralphwetzel
|
|
||||||
- Fix unintended new line in node name (#4399) @kazuhitoyokoi
|
|
||||||
- Ctrl-Enter does not close tray (Monaco) #4377 (#4382) @hazymat
|
|
||||||
- fix buffer viewer to handle 0b style binary (#4393) @dceejay
|
|
||||||
- Rework mermaid integration to support off-DOM rendering (#4364) @knolleary
|
|
||||||
- Add missing nls labels to context menu (#4365) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Bump the github-actions group with 2 updates (#4404) @app/dependabot
|
|
||||||
- Handle unknown node reference inside subflow module (#4460) @knolleary
|
|
||||||
- Add modules.install audit event when external module installed (#4452) @knolleary
|
|
||||||
- Allow import of modules with subpath in specifier (#4451) @knolleary
|
|
||||||
- Update node-red-admin version (#4438) @knolleary
|
|
||||||
- Handle false-like env vars properly (#4411) @knolleary
|
|
||||||
- Only save settings once during node load process (#4409) @knolleary
|
|
||||||
- Ensure global-config nodes lookup cred values properly (#4405) @knolleary
|
|
||||||
- Handle credential env var evaluation when no value set (#4362) @knolleary
|
|
||||||
- Don't commit package-lock.json (#4354) @bonanitech
|
|
||||||
- Fix env evaluation when one env references another in the same object (#4361) @knolleary
|
|
||||||
- Add dependabot for Github Actions (#4312) @Rotzbua
|
|
||||||
- Update outdated Github Actions (#4311) @Rotzbua
|
|
||||||
- github: Request `npm run test` in PR template (#4348) @ZJvandeWeg
|
|
||||||
- Add French translation of v3.1.0-beta.4 changes + slight improvements (#4329) @GogoVega
|
|
||||||
- Handle nodes with multiple input handlers properly (#4332) @knolleary
|
|
||||||
- Soften the language around unrequited PRs (#4351) @knolleary
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- CSV: make CSV export way faster by not re-allocating and handling huge string (#4349) @Fadoli
|
|
||||||
- Delay: Fix regression in delay node to not pass on msg.reset (#4350) @dceejay
|
|
||||||
- Link Call: Handle undefined linkType value for existing link-call nodes (#4331) @knolleary
|
|
||||||
- MQTT: Guard against node.broker being undefined (#4454) @knolleary
|
|
||||||
- MQTT: check topic length > 0 before publish (#4416) @dceejay
|
|
||||||
- Switch/Change: Improve validation of switch/change node rules (#4368) @knolleary
|
|
||||||
- Template: Fix height of description editor in template node (#4346) @kazuhitoyokoi
|
|
||||||
- Various: Add validators to any fields using msg-typed Input (#4440) @knolleary
|
|
||||||
|
|
||||||
#### 3.1.0: Milestone Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Default filter to All Catalogues and show nodes for small lists (#4318) @knolleary
|
|
||||||
- Better distinguish between ctrl and meta keys on mac (#4310) @knolleary
|
|
||||||
- Ensure junction appears when filtering quick-add list (#4297) @knolleary
|
|
||||||
- Update message catalogs for JSONata Expression editor (#4287) @kazuhitoyokoi
|
|
||||||
- Add tooltip to relevance sort button in user settings UI (#4288) @kazuhitoyokoi
|
|
||||||
- Capture workspace dirty state when quick-adding junction (#4283) @knolleary
|
|
||||||
- Add docs for $clone function (#4284) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Dependency updates (#4317) @knolleary
|
|
||||||
- Ensure storage/util.writeFile handles concurrent write attempts (#4316) @knolleary
|
|
||||||
- Migrate http -> https for nodered.org (#4313) @Rotzbua
|
|
||||||
- Add Node 20 to GH Action test matrix (#4305) @Rotzbua
|
|
||||||
- Handle group-scoped nodes inside subflow (#4301) @knolleary
|
|
||||||
- Handle non-url-safe chars in context api (#4298) @knolleary
|
|
||||||
- Fix git pull operation in project feature (#4290) @kazuhitoyokoi
|
|
||||||
- Change linefeed codes in Korean message catalogs (#4286) @kazuhitoyokoi
|
|
||||||
- Fix file permissions of message catalogs (#4285) @kazuhitoyokoi
|
|
||||||
- Update tour (#4278) @knolleary
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- File: Fix handling in file nodes when number is specified as file name (#4267) @kazuhitoyokoi
|
|
||||||
- Function: Adding function timeout to settings file (#4265) (#4309) @knolleary
|
|
||||||
- Function: Fix function setup tab layout (#4299) @knolleary
|
|
||||||
- HTTP Request: Handle 204 in httprequest JSON (#4262) @sammachin
|
|
||||||
- JSON: Fix test cases of JSON node (#4275) @kazuhitoyokoi
|
|
||||||
- MQTT: Remove unnecessary check for clientid if autoUnsub set (#4302) @knolleary
|
|
||||||
|
|
||||||
##### 3.1.0-beta.4: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Add Japanese translation for 3.1.0 (#4252) @kazuhitoyokoi
|
|
||||||
- Improve Catalogue visibility (#4248) @Steve-Mcl
|
|
||||||
- Add support for wiring and moving junctions on touch device (#4244) @Steve-Mcl
|
|
||||||
- Show errors and statuses of config nodes in the sidebar when no catch node is available (#4231) @bvmensvoort
|
|
||||||
- Improve wiring for horizontally aligned nodes (#4232) @knolleary
|
|
||||||
- French translation of Welcome Tours (#4200) @GogoVega
|
|
||||||
- French translation of v3.1.0-beta.3 changes (#4199) @GogoVega
|
|
||||||
- add Japanese message for 3.1.0 beta 3 (#4209) @HiroyasuNishiyama
|
|
||||||
- Dont clone the group nodes `node` array when saving edits (#4208) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Add NR_SUBFLOW_NAME/ID/PATH env vars (#4250) @knolleary
|
|
||||||
- Evaluate all env vars as part of async flow start (#4230) @knolleary
|
|
||||||
- Add support for httpStatic middleware (#4229) @knolleary
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Fix JSONata in file nodes (#4246) @kazuhitoyokoi
|
|
||||||
- Fix timeout icon in function and link call nodes (#4253) @kazuhitoyokoi
|
|
||||||
- Fix connection keep-alive in http request node (#4228) @knolleary
|
|
||||||
- adding timeout attribute to function node (#4177) @k1ln
|
|
||||||
- Fix manual mode join when multiple sequences being handled (#4143) @BitCaesar
|
|
||||||
- Fix delay node flush issue (#4203) @dceejay
|
|
||||||
- Update status and catch node labels in group mode (#4207) @Steve-Mcl
|
|
||||||
|
|
||||||
##### 3.1.0-beta.3: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Select the item that is specified in a deep link URL (#4113) @Steve-Mcl
|
|
||||||
- Update to Monaco 0.38.0 (#4189) @Steve-Mcl
|
|
||||||
- Place subflow outputs/inputs relative to current view (#4183) @knolleary
|
|
||||||
- Enable RED.view.select to select group by id (#4184) @knolleary
|
|
||||||
- Combine existing env vars when merging groups (#4182) @knolleary
|
|
||||||
- Avoid creating empty global-config node if not needed (#4153) @knolleary
|
|
||||||
- Fix group selection when using lasso (#4108) @knolleary
|
|
||||||
- Use editor path in generating localStorage keys (#4151) @mw75
|
|
||||||
- Ensure no node credentials are included when exporting to clipboard (#4112) @knolleary
|
|
||||||
- Fix jsonata expression test ui (#4097) @knolleary
|
|
||||||
- Fix search button in palette popover (#4096) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Allow options object on each httpStatic configuration (#4109) @kevinGodell
|
|
||||||
- Ensure non-zero exit codes for errors (#4181) @knolleary
|
|
||||||
- Ensure external modules are installed synchronously (#4180) @knolleary
|
|
||||||
- Update dependecies include got (#4155) @knolleary
|
|
||||||
- Add Japanese translations for v3.1 beta.2 (#4158) @kazuhitoyokoi
|
|
||||||
- Ensure express server options are applied consistently (#4178) @knolleary
|
|
||||||
- Remove version info from theme endpoint (#4179) @knolleary
|
|
||||||
- Add Japanese translations for welcome tour of 3.1.0 beta.2 (#4145) @kazuhitoyokoi
|
|
||||||
- Added SHA-256 and SHA-512-256 digest authentication (#4100) @sroebert
|
|
||||||
- Add "timers" types to known types (#4103) @Steve-Mcl
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Allow Catch/Status nodes to be scoped to their group (#4185) @NetHans
|
|
||||||
- MQTT: Option to disable MQTT topic unsubscribe on disconnect (#4078) @flying7eleven
|
|
||||||
|
|
||||||
|
|
||||||
##### 3.1.0-beta.2: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- NEW: Add change icon to tabs (#4068) @knolleary
|
|
||||||
- NEW: Complete overhaul of Group UX (#4079) @knolleary
|
|
||||||
- NEW: Add link to node help in node edit dialog footer (#4065) @knolleary
|
|
||||||
- NEW: Added editor feature for connecting multiple nodes to single node (#4051) @sonntam
|
|
||||||
- NEW: Increase workspace size to 8000x8000 (#4094) @knolleary
|
|
||||||
- Ensure node buttons are redrawn when flow lock state is changed (#4091) @knolleary
|
|
||||||
- Prevent loops being created with junction nodes (#4087) @knolleary
|
|
||||||
- Prevent opening locked node's edit dialog (#4069) @knolleary
|
|
||||||
- Reverse direction of tab scroll to expected direction (#4064) @knolleary
|
|
||||||
- Add cancel operation to editableList (#4077) @HiroyasuNishiyama
|
|
||||||
- Apply Mermaid diagram for project settings UI (#4054) @kazuhitoyokoi
|
|
||||||
- Add tooltip for show/hide button on info sidebar (#4050) @kazuhitoyokoi
|
|
||||||
- Fix align nodes on locked tab (#4072) @HiroyasuNishiyama
|
|
||||||
- Fix importing connected link nodes into a subflow (#4082) @knolleary
|
|
||||||
- Fix to add empty marker to empty group (#4060) @HiroyasuNishiyama
|
|
||||||
- Fix image URLs for v3.0 tour (#4053) @kazuhitoyokoi
|
|
||||||
- Show scrollbar in notification dialog only when needed (#4048) @kazuhitoyokoi
|
|
||||||
- Update-monaco-and-typings (#4089) @Steve-Mcl
|
|
||||||
- Update jquery UI (#4088) @knolleary
|
|
||||||
- Support i18n of lock/unlock buttons in flow property UI (#4049) @kazuhitoyokoi
|
|
||||||
- Translation kr (#3895) @hae-iotplatform
|
|
||||||
- Translation zhcn (!!请懂中文的帮忙review) (#3952) @cliyr
|
|
||||||
- Add French translation of nodes (#3964) @GogoVega
|
|
||||||
- Add French translation (#3962) @GogoVega
|
|
||||||
- Portuguese Brazilian (pt-BR) translation (#3804) @FabsMuller
|
|
||||||
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- NEW: Generate stable ids for subflow instance internal nodes (#4093) @knolleary
|
|
||||||
- NEW: Change default file name to flows.json in project feature (#4073) @kazuhitoyokoi
|
|
||||||
- NEW: Deprecate synchronous access to jsonata (#4090) @knolleary
|
|
||||||
- Add Node 18 to test matrix (#4084) @knolleary
|
|
||||||
- Bump minimum nodejs version supported to match documented value (#4086) @knolleary
|
|
||||||
- Update monaco docs link in settings.js (#4075) @Steve-Mcl
|
|
||||||
- Remove duplicated messages in the message catalog (#4066) @kazuhitoyokoi
|
|
||||||
- Ensure errors in preDeliver callback are handled (#3911) @knolleary
|
|
||||||
- Fix "EADDRINUSE" error (#4046) @bggbr
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Link Call: Clear link-call timeouts when node is closed (#4085) @knolleary
|
|
||||||
- Join: ensure inflight status is cleared when in auto mode (#4083) @knolleary
|
|
||||||
- File Out: Fix extra newline append for multipart file write (#3915) @dceejay
|
|
||||||
- Add validators for complete and link call nodes (#4056) @kazuhitoyokoi
|
|
||||||
|
|
||||||
##### 3.1.0-beta.1: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- NEW: Locking Flows (#3938) @knolleary
|
|
||||||
- NEW: Improve UX around hiding flows via context menu (#3930) @knolleary
|
|
||||||
- NEW: Add support for inline image in markdown editor by drag and drop of an image file (#4006) @HiroyasuNishiyama
|
|
||||||
- NEW: Add support for mermaid diagram to markdown editor (#4007) @HiroyasuNishiyama
|
|
||||||
- NEW: Support uri fragments for nodes and groups including edit support (#3870) @knolleary
|
|
||||||
- NEW: Add global environment variable feature (#3941) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
- Remember compact/pretty flow export user choice (#3974) @Steve-Mcl
|
|
||||||
- fix .red-ui-notification class (#4035) @xiaobinqt
|
|
||||||
- Fix border radius on Modules list header (#4038) @bonanitech
|
|
||||||
- fix workspace reference error in case of empty tabs (#4029) @HiroyasuNishiyama
|
|
||||||
- Disable delete tab menu when single tab exists (#4030) @HiroyasuNishiyama
|
|
||||||
- Disable hide all menu if all tabs hidden (#4031) @HiroyasuNishiyama
|
|
||||||
- fix hide subflow tooltip (#4033) @HiroyasuNishiyama
|
|
||||||
- Fix disabled menu items in project feature (#4027) @kazuhitoyokoi
|
|
||||||
- Let themes change radialMenu text colors (#3995) @bonanitech
|
|
||||||
- Add Japanese translations for v3.0.3 (#4012) @kazuhitoyokoi
|
|
||||||
- Add Japanese translation for v3.1.0-beta.0 (#3997) @kazuhitoyokoi
|
|
||||||
- Add Japanese translation for v3.1.0-beta.0 (#3916) @kazuhitoyokoi
|
|
||||||
- Hide subflow category after deleting subflow (#3980) @kazuhitoyokoi
|
|
||||||
- Prevent dbl-click opening node edit dialog with text selected (#3970) @knolleary
|
|
||||||
- Handle replacing unknown node inside group or subflow (#3921) @knolleary
|
|
||||||
- Fix #3939, red border red-ui-typedInput-container (#3949) @Steveorevo
|
|
||||||
- i18n item URL copy notification & add Japanese message (#3946) @HiroyasuNishiyama
|
|
||||||
- add Japanese message for item url copy actions (#3947) @HiroyasuNishiyama
|
|
||||||
- Fix autocomplete entry for responseUrl (#3884) @knolleary
|
|
||||||
- Fix Japanese translation for JSONata editor (#3872) @HiroyasuNishiyama
|
|
||||||
- Fix search type with spaces (#3841) @Steve-Mcl
|
|
||||||
- Fix error hanndling of JSONata expression editor for extended functions (#3871) @HiroyasuNishiyama
|
|
||||||
- Add button type to the adding SSH key button (#3866) @kazuhitoyokoi
|
|
||||||
- Check radio button as default in project dialog (#3879) @kazuhitoyokoi
|
|
||||||
- Add $clone as supported function (#3874) @HiroyasuNishiyama
|
|
||||||
- Env var jsonata (#3807) @HiroyasuNishiyama
|
|
||||||
- Add Japanese translation for v3.0.2 (#3852) @kazuhitoyokoi
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Force IPv4 name resolution to have priority (#4019) @dceejay
|
|
||||||
- Fix async loading of modules containing both nodes and plugins (#3999) @knolleary
|
|
||||||
- Use main branch as default in project feature (#4036) @kazuhitoyokoi
|
|
||||||
- Rename package var to avoid strict mode error (#4020) @knolleary
|
|
||||||
- Fix typos in settings.js (#4013) @ypid
|
|
||||||
- Ensure credentials object is removed before returning node in getFlow request (#3971) @knolleary
|
|
||||||
- Ignore commit error in project feature (#3987) @kazuhitoyokoi
|
|
||||||
- Update dependencies (#3969) @knolleary
|
|
||||||
- Add check that node sends object rather than primitive type (#3909) @knolleary
|
|
||||||
- Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname (#3912) @knolleary
|
|
||||||
- Fix nodesDir scan when node package has js/html in sub dir to package.json (#3867) @Steve-Mcl
|
|
||||||
- Fix file permissions (#3917) @kazuhitoyokoi
|
|
||||||
- ci: add minimum GitHub token permissions for workflows (#3907) @boahc077
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Catch: fix typo in catch.html (#3965) @we11adam
|
|
||||||
- Change: Fix change node overwriting msg with itself (#3899) @dceejay
|
|
||||||
- Comment node: Clarify where the text will appear (#4004) @dirkjanfaber
|
|
||||||
- CSV: change replace to replaceAll (#3990) @dceejay
|
|
||||||
- CSV node: check header properties for ' and " (#3920) @dceejay
|
|
||||||
- CSV: Fix for CSV undefined property (#3906) @dceejay
|
|
||||||
- Delay: let delay node handle both flush then reset (#3898) @dceejay
|
|
||||||
- Function: Limit number of ports in function node (#3886) @kazuhitoyokoi
|
|
||||||
- Function: Remove dot from variable name for external module in function node (#3880) @kazuhitoyokoi
|
|
||||||
- Function: add function node monaco types util and promisify (#3868) @Steve-Mcl
|
|
||||||
- HTTP In: Ensure msg.req.headers is enumerable (#3908) @knolleary
|
|
||||||
- HTTP Request: Support form-data arrays (#3991) @hardillb
|
|
||||||
- HTTP Request: Fix httprequest tests to be more lenient on error message (#3922) @knolleary
|
|
||||||
- HTTP Request: Add missing property to node object HTTPRequest (#3842) @hardillb
|
|
||||||
- HTTP Request/Response: Support sortable list on property UI of http request and http response nodes (#3857) @kazuhitoyokoi
|
|
||||||
- HTTP Response: Ensure statusCode is a number (#3894) @hardillb
|
|
||||||
- Inject: Allow Inject node to work with async context stores (#4021) @knolleary
|
|
||||||
- Join/Batch: Add count to join and batch node labels (#4028) @dceejay
|
|
||||||
- MQTT: Fix birth topic handling in MQTT node (#3905) @Steve-Mcl
|
|
||||||
- MQTT: Fix pull-down menus of MQTT configuration node (#3890) @kazuhitoyokoi
|
|
||||||
- MQTT: Prevent invalid mqtt birth topic crashing node-red (#3869) @Steve-Mcl
|
|
||||||
- MQTT: ensure sessionExpiry(Interval) is applied (#3840) @Steve-Mcl
|
|
||||||
- MQTT: Fix mqtt nodes not reconnecting on modified-flows deploy (#3992) @knolleary
|
|
||||||
- MQTT: fix single subscription mqtt node status (#3966) @Steve-Mcl
|
|
||||||
- Range: Add drop mode to range node (#3935) @dceejay
|
|
||||||
- Remove done from describe (#3873) @HiroyasuNishiyama
|
|
||||||
- Split node: avoid duplicate done call for buffer split (#4000) @knolleary
|
|
||||||
- Status: Fix typo in 25-status.html (#3981) @kazuhitoyokoi
|
|
||||||
- TCP Node: ensure newline substitution applies to whole message (#4009) @dceejay
|
|
||||||
- Template: Add information about environment variable to template node (#3882) @kazuhitoyokoi
|
|
||||||
- Trigger: Hide trigger node repeat send option if sending nothing (#4023) @dceejay
|
|
||||||
- Watch: fix watch node test on MacOS/ARM (#3942) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.2: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Fix workspace chart bottom property (#3812) @bonanitech
|
|
||||||
- Update german translation (#3802) @Dennis14e
|
|
||||||
- Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi
|
|
||||||
- Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary
|
|
||||||
- Hide scrollbars until they're needed (#3808) @bonanitech
|
|
||||||
- Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary
|
|
||||||
- remove console.log (#3820) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Register subflow module instance node with parent flow (#3818) @knolleary
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb
|
|
||||||
|
|
||||||
#### 3.0.1: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl
|
|
||||||
- Sys info (diagnostics report) amendments (#3793) @Steve-Mcl
|
|
||||||
- Allow `mode` and `title` to be omitted in `options` argument for `createEditor` (#3791) @Steve-Mcl
|
|
||||||
- Fix focus issues (#3789) @Steve-Mcl
|
|
||||||
- Ensure all typedInput buttons have button type set (#3788) @knolleary
|
|
||||||
- Do not flag hasUsers=false nodes as unused in search (#3787) @knolleary
|
|
||||||
- Properly position quick-add dialog in all cases (#3786) @knolleary
|
|
||||||
- Ensure quick-add dialog does not obscure ghost node when shifted (#3785) @knolleary
|
|
||||||
- Remove use of Object.hasOwn (#3784) @knolleary
|
|
||||||
|
|
||||||
#### 3.0.0: Milestone Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Use theme page and header values if settings.js values are not present (#3767) @Steve-Mcl
|
|
||||||
- Focus editor for undo after some actions in menu (#3759) @kazuhitoyokoi
|
|
||||||
- Ensure node icon shade has properly rounded corners (#3763) @knolleary
|
|
||||||
- Fix storing subflow credential type when input has multiple types (#3762) @knolleary
|
|
||||||
- Ensure global-config and flow-config have info in the hierarchy popover (#3752) @Steve-Mcl
|
|
||||||
- Include dirty state in history event (#3748) @Steve-Mcl
|
|
||||||
- Fix display direction of context sub-menu (#3746) @knolleary
|
|
||||||
- Fix clear pinned paths of debug sidebar menu (#3745) @HiroyasuNishiyama
|
|
||||||
- prevent exception generating tooltip for deleted nodes (#3742) @Steve-Mcl
|
|
||||||
- Fix context menu issues ready for v3 beta.5 (#3741) @Steve-Mcl
|
|
||||||
- Do not generate new node-ids when pasting a cut flow (#3729) @knolleary
|
|
||||||
- Fix to prevent node from moving out of workspace (#3731) @HiroyasuNishiyama
|
|
||||||
- Don't let themes change disabled config node background color (#3736) @bonanitech
|
|
||||||
- Move colors left behind in #3692 to CSS variables (#3737) @bonanitech
|
|
||||||
- Fix handling of global debug message (#3733) @HiroyasuNishiyama
|
|
||||||
- Fix label overflow @ config-node palette (#3730) @ralphwetzel
|
|
||||||
- Fix defaulting to monaco if settings does not contain codeEditor (#3732) @knolleary
|
|
||||||
- Disable keyboard shortcut mapping when showing Edit[..]Dialog (#3700) @ralphwetzel
|
|
||||||
- Update add-junction menu to work in more cases (#3727) @knolleary
|
|
||||||
- Ensure importMap is not null when using import UI (#3723) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0-beta.4 (#3724) @kazuhitoyokoi
|
|
||||||
- Fix "split with" on virtual links (#3766) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Do not remove unknown credentials of Subflow Modules (#3728) @knolleary
|
|
||||||
- Add missing entries from beta.4 changelog (#3721) @knolleary
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Change: Fix change node, not handling from field properly when using context (#3754) @Fadoli
|
|
||||||
- Link Call: Fix linkcall registry bugs (#3751) @Steve-Mcl
|
|
||||||
- WebSocket: Fix close timeout of websocket node (#3734) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.0-beta.4: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Move all colours to CSS variables (#3692) @bonanitech
|
|
||||||
- Fix clicking on node in workspace to hide context menu (#3696) @knolleary
|
|
||||||
- Fix credential type input item of subflow template (#3703) @HiroyasuNishiyama
|
|
||||||
- Add option flag `reimport` to `importNodes` (#3718) @Steve-Mcl
|
|
||||||
- Update german translation (#3691) @Dennis14e
|
|
||||||
- List welcome tours in help sidebar (#3717) @knolleary
|
|
||||||
- Ensure 'hidden flow' count doesn't include subflows (#3715) @knolleary
|
|
||||||
- Fix Chinese translate (#3706) @hotlong
|
|
||||||
- Fix use default button for node icon (#3714) @kazuhitoyokoi
|
|
||||||
- Fix select boxes vertical alignment (#3698) @bonanitech
|
|
||||||
- Ensure workspace clean after undoing dropped node (#3708) @Steve-Mcl
|
|
||||||
- Use solid colour as config node icon background to hide text overflow (#3710) @Steve-Mcl
|
|
||||||
- Increase quick-add height to reveal 2 most recent entries (#3711) @Steve-Mcl
|
|
||||||
- Set default editor to monaco in absence of user preference (#3702) @knolleary
|
|
||||||
- Add Japanese translations for v3.0-beta.3 (#3688) @kazuhitoyokoi
|
|
||||||
- Fix handling of spacebar inside JSON visual editor (#3687) @knolleary
|
|
||||||
- Fix menu padding to handle both icons and submenus (#3686) @knolleary
|
|
||||||
- Include scroll offset when positioning quick-add dialog (#3685) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Allow flows to be stopped and started manually (#3719) @knolleary
|
|
||||||
- Import default export if node is a transpiled es module (#3669) @dschmidt
|
|
||||||
- Leave Monaco theme commented out by default (#3704) @bonanitech
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- CSV: Fix CSV node to handle when outputting text fields (#3716) @dceejay
|
|
||||||
- Delay: Fix delay rate limit last timing when empty (#3709) @dceejay
|
|
||||||
- Link: Ensure link-call cache is updated when link-in is modified (#3695) @Steve-Mcl
|
|
||||||
- Join: Join node in reduce mode doesn't keep existing msg properties (#3670) @dceejay
|
|
||||||
- Template: Add support for evalulating {{env.<var>}} within a template node (#3690) @cow0w
|
|
||||||
|
|
||||||
#### 3.0.0-beta.3: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Add Right-Click content menu (#3678) @knolleary
|
|
||||||
- Fix disable junction (#3671) @HiroyasuNishiyama
|
|
||||||
- Add Japanese translations for v2.2.3 (#3672) @kazuhitoyokoi
|
|
||||||
- Reset mouse state when switching tabs (#3643) @knolleary
|
|
||||||
- Fix uncorrect fix of junction to subflow conversion (#3666) @HiroyasuNishiyama
|
|
||||||
- Fix undoing junction to subflow (#3653) @HiroyasuNishiyama
|
|
||||||
- Fix conversion of junction to subflow (#3652) @HiroyasuNishiyama
|
|
||||||
- Fix to include junction to exported nodes (#3650) @HiroyasuNishiyama
|
|
||||||
- Fix z-index value for shade to cover nodes in palette (#3649) @kazuhitoyokoi
|
|
||||||
- Fix to extend escaped subflow category characters (#3647) @HiroyasuNishiyama
|
|
||||||
- Fix to sanitize tab name (#3646) @HiroyasuNishiyama
|
|
||||||
- Fix selector placement (#3644) @bonanitech
|
|
||||||
- Add Japanese translations for v3.0-beta.2 (#3622) @kazuhitoyokoi
|
|
||||||
- Fix new folder menu of save to library dialog (#3633) @HiroyasuNishiyama
|
|
||||||
- Fix layer of palette node (#3638) @HiroyasuNishiyama
|
|
||||||
- Fix to place a node dragged from palette within the workspace (#3637) @HiroyasuNishiyama
|
|
||||||
- Fix typo in CSS (#3628) @bonanitech
|
|
||||||
- Use the correct variable for the gutter text color (#3615) @bonanitech
|
|
||||||
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Support loading node modules from `nodesdir` (#3676) @Steve-Mcl
|
|
||||||
- fix buffer parse error message of evaluateNodeProperty (#3624) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- File: Further simplify file node filename entry UX (v3) (#3677) @Steve-Mcl
|
|
||||||
- Function: Fix initial cursor position of init/finalize tab of function node (#3674) @HiroyasuNishiyama
|
|
||||||
- Function: Fix ESM module loading in Function node (#3645) @knolleary
|
|
||||||
- Inject: Fix JSONata evaluation of inject button (#3632) @HiroyasuNishiyama
|
|
||||||
- TCP: Dont delete TCP socket twice (#3630) @Steve-Mcl
|
|
||||||
- MQTT Node: define noproxy variable (#3626) @Steve-Mcl
|
|
||||||
- Debug: i18n debug sidebar node label (#3623) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.0-beta.2: Beta Release
|
|
||||||
|
|
||||||
**Migration from 2.x**
|
|
||||||
|
|
||||||
- The 'slice wires' action has changed from Ctrl-RightMouseButton to Alt-LeftMouseButton
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Rework Junctions to be more node like in their event handling (#3607) @knolleary
|
|
||||||
- Change slicing / slice-junction operations over to mouse button 0 (Left Mouse Button) (#3609) @Steve-Mcl
|
|
||||||
- Do not slice-junction link node wires (#3608) @knolleary
|
|
||||||
- Handle many-to-one slicing of wires (#3604) @knolleary
|
|
||||||
- Ensure ACE worker options are set (#3611) @Steve-Mcl
|
|
||||||
- Remove duplicate history add of ungroup event (#3605) @knolleary
|
|
||||||
- use text width instead of number of characters for deciding select fi… (#3603) @HiroyasuNishiyama
|
|
||||||
- Update Japanese info of link call node reflecting update of English info (#3600) @HiroyasuNishiyama
|
|
||||||
- Fix typedInput label not visible on themes (#3580) @bonanitech
|
|
||||||
- Fix project switching when junctions are present (#3595) @Steve-Mcl
|
|
||||||
- Fix junction: when wiring from a regular nodes INPUT, backwards to a junction (#3591) @Steve-Mcl
|
|
||||||
- Fix error initialising flow tab editor (#3585) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0-beta.1 (#3576) @kazuhitoyokoi
|
|
||||||
- Fix image paths where `red/image/typedInput/XXXX.png` should be `red/image/typedInput/XXXX.svg` (#3592) @kazuhitoyokoi
|
|
||||||
- Fix browser console error Uncaught TypeError when searching certain terms (#3584) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- fix error on system-info action (#3589) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- I18n switch rule selector (#3602) @HiroyasuNishiyama
|
|
||||||
- Handle removal of event handlers to allow mqtt client.end() to work (#3594) @PhilDay-CT
|
|
||||||
- update link-call node info according to current behavior (#3597) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
|
|
||||||
#### 3.0.0-beta.1: Beta Release
|
|
||||||
|
|
||||||
**Migration from 2.x**
|
|
||||||
|
|
||||||
- Node-RED now requires Node.js 14.x or later.
|
|
||||||
- New installs of Node-RED will default to the monaco editor.
|
|
||||||
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Add Junctions (#3462) @knolleary
|
|
||||||
- Allow node name to be auto-generated when added (#3478, #3538) @knolleary
|
|
||||||
- Set monaco as default code editor as of v3.x (#3543) @Steve-Mcl
|
|
||||||
- Update Monaco to V0.33.0 (#3522) @Steve-Mcl
|
|
||||||
- Auto-complete Improvements (#3521) @Steve-Mcl
|
|
||||||
- Add a tooltip to debug sidebar messages to reveal full path to node (#3503) @knolleary
|
|
||||||
- Fix down arrow triggering menu in search box (#3507) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0 (#3512) @kazuhitoyokoi
|
|
||||||
- Add feature: Continuous search tools (search previous, search next) (#3405) @Steve-Mcl
|
|
||||||
- Add feature: split-wire-to-links (#3399, #3476) @Steve-Mcl
|
|
||||||
- Add copy button to node properties tables (#3390) @knolleary
|
|
||||||
- Add info-tab search options dropdown to the regular search (#3395) @Steve-Mcl
|
|
||||||
- New Feature: Add ability to find modified nodes/flows. (#3392) @Steve-Mcl
|
|
||||||
- Code editor ux improvements around remembering state of each code editor in a flow (#3553) @Steve-Mcl
|
|
||||||
- Make it easier to apply themes on SVG icons (#3515) @bonanitech
|
|
||||||
- Add support of property validation message (#3438) @HiroyasuNishiyama
|
|
||||||
- Ensure node validation tooltip is closed when field becomes valid (#3570) @knolleary
|
|
||||||
- Add "search for" buttons to notifications (#3567) @Steve-Mcl
|
|
||||||
- Don't let themes change node config colors (#3564) @bonanitech
|
|
||||||
- Fix gap between typedInput containers borders (#3560) @bonanitech
|
|
||||||
- Fix recording removed links in edit history (#3547) @knolleary
|
|
||||||
- Remove unused SASS vars (#3536) @bonanitech
|
|
||||||
- Add custom style for jQuery widgets borders (#3537) @bonanitech
|
|
||||||
- fix out of scope reference of hasUnusedConfig variable (#3535) @HiroyasuNishiyama
|
|
||||||
- correct "non string" check parenthesis (#3524) @Steve-Mcl
|
|
||||||
- Ensure i18n of scoped package name (#3516) @Steve-Mcl
|
|
||||||
- Prevent shortcut deploy when deploy button shaded (#3517) @Steve-Mcl
|
|
||||||
- Fix: Sidebar "Configuration" filter button tooltip (#3500) @ralphwetzel
|
|
||||||
- Add the ability to customize diff colors even more (#3499) @bonanitech
|
|
||||||
- Do JSON comparison of old value/new value in editor (#3481) @Steve-Mcl
|
|
||||||
- Fix nodes losing their wires when in an iframe (#3484) @zettca
|
|
||||||
- Improve scroll into view (#3468) @Steve-Mcl
|
|
||||||
- Do not show 1st tab if hidden when loading (#3464) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Fix importing external module from node-red module (#3541) @knolleary
|
|
||||||
- Add support for multiple static paths with optional static root (#3542) @Steve-Mcl
|
|
||||||
- Store external token when authenticating if provided (#3460) @ArFe
|
|
||||||
- Support OAuth/OpenID logout (#3388) @mw75
|
|
||||||
- Allow adminAuth to auto-login users when using passport strategy (#3519) @knolleary
|
|
||||||
- Add runtime diagnostics admin endpoint (#3511) @Steve-Mcl
|
|
||||||
- Don't start if user has no home directory (#3540) @hardillb
|
|
||||||
- Error on invalid encrypted credentials (#3498) @sammachin
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Debug: Add message count option to Debug status (#3544 #3551) @rafaelmuynarsk @knolleary
|
|
||||||
- File: Change basic Filename field to a typedInput (#3533) @Steve-Mcl
|
|
||||||
- HTTP Request: Add UI for Http Request node headers (#3488) @Steve-Mcl
|
|
||||||
- Inject: let inject optionally fire at start in only at time mode. (#3385) @dceejay
|
|
||||||
- Link Call: Dynamic link call (#3463) @Steve-Mcl
|
|
||||||
- Link Call: Display link targets of nodes in a regular flow, for Link Call nodes inside a subflow (#3528) @Steve-Mcl
|
|
||||||
- MQTT: MQTT payload auto parsing improvements (#3530) @Steve-Mcl
|
|
||||||
- MQTT: Add client and Runtime MQTT topic validation (#3563) @Steve-Mcl [dev]
|
|
||||||
- MQTT: save and restore v5 config user props (#3562) @Steve-Mcl
|
|
||||||
- MQTT: Fix incorrect MQTT status (#3552) @Steve-Mcl
|
|
||||||
- MQTT: fix reference error of msg.status in debug node (#3526) @HiroyasuNishiyama
|
|
||||||
- MQTT: Add unit tests for MQTT nodes (#3497) @Steve-Mcl
|
|
||||||
- MQTT: fix typo of will properties (#3502) @Steve-Mcl
|
|
||||||
- MQTT: ensure mqtt v5 props can be set false (#3472) @Steve-Mcl
|
|
||||||
- Switch: add check for NaN in is of type number to be false (#3409) @dceejay
|
|
||||||
- TCP: TCP node better split (#3465) @dceejay
|
|
||||||
- Watch: Update Watch node to use node-watch module (#3559 #3569) @knolleary
|
|
||||||
- WebSocket: call done after ws disconnects (#3531) @Steve-Mcl
|
|
||||||
|
|
||||||
#### Older Releases
|
#### Older Releases
|
||||||
|
|
||||||
|
7
CITATION.cff
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
cff-version: 1.2.0
|
||||||
|
message: "If you use this software, please cite it as below."
|
||||||
|
title: "Node-RED"
|
||||||
|
authors:
|
||||||
|
- family-names: "OpenJS Foundation"
|
||||||
|
- family-names: "Contributors"
|
||||||
|
url: "https://nodered.org"
|
73
Gruntfile.js
@ -143,6 +143,7 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
||||||
|
"packages/node_modules/@node-red/editor-client/src/js/multiplayer.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
||||||
@ -207,38 +208,52 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
||||||
],
|
],
|
||||||
|
nonull: true,
|
||||||
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
||||||
},
|
},
|
||||||
vendor: {
|
vendor: {
|
||||||
files: {
|
files: [
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [
|
{
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
|
src: [
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
|
||||||
"node_modules/marked/marked.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
||||||
"node_modules/dompurify/dist/purify.min.js",
|
"node_modules/marked/marked.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
"node_modules/dompurify/dist/purify.min.js",
|
||||||
"node_modules/i18next/i18next.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
||||||
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
"node_modules/i18next/i18next.min.js",
|
||||||
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
||||||
"node_modules/jsonata/jsonata-es5.min.js",
|
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
"node_modules/jsonata/jsonata-es5.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
|
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
||||||
],
|
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
|
||||||
// "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
|
],
|
||||||
// // TODO: resolve relative resource paths in
|
nonull: true,
|
||||||
// // bootstrap/FA/jquery
|
dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js"
|
||||||
// ],
|
},
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
|
// {
|
||||||
"node_modules/jsonata/jsonata-es5.min.js",
|
// src: [
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
// // TODO: resolve relative resource paths in
|
||||||
],
|
// // bootstrap/FA/jquery
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [
|
// ],
|
||||||
"node_modules/mermaid/dist/mermaid.min.js"
|
// dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css"
|
||||||
]
|
// },
|
||||||
}
|
{
|
||||||
|
src: [
|
||||||
|
"node_modules/jsonata/jsonata-es5.min.js",
|
||||||
|
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
||||||
|
],
|
||||||
|
nonull: true,
|
||||||
|
dest: "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "node_modules/mermaid/dist/mermaid.min.js",
|
||||||
|
nonull: true,
|
||||||
|
dest: "packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js",
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
uglify: {
|
uglify: {
|
||||||
|
16
nodemon.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"ignoreRoot": [
|
||||||
|
".git",
|
||||||
|
".nyc_output",
|
||||||
|
".sass-cache",
|
||||||
|
"bower-components",
|
||||||
|
"coverage"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"/Gruntfile.js",
|
||||||
|
"/.git/*",
|
||||||
|
"*.backup",
|
||||||
|
"/public/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
63
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red",
|
"name": "node-red",
|
||||||
"version": "3.1.11",
|
"version": "4.0.9",
|
||||||
"description": "Low-code programming for event-driven applications",
|
"description": "Low-code programming for event-driven applications",
|
||||||
"homepage": "https://nodered.org",
|
"homepage": "https://nodered.org",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -26,26 +26,26 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.8.2",
|
"acorn": "8.12.1",
|
||||||
"acorn-walk": "8.2.0",
|
"acorn-walk": "8.3.4",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.17.1",
|
||||||
"async-mutex": "0.4.0",
|
"async-mutex": "0.5.0",
|
||||||
"basic-auth": "2.0.1",
|
"basic-auth": "2.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"cheerio": "1.0.0-rc.10",
|
"cheerio": "1.0.0-rc.10",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"content-type": "1.0.5",
|
"content-type": "1.0.5",
|
||||||
"cookie": "0.5.0",
|
"cookie": "0.7.2",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.7",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"cronosjs": "1.7.1",
|
"cronosjs": "1.7.1",
|
||||||
"denque": "2.1.0",
|
"denque": "2.1.0",
|
||||||
"express": "4.19.2",
|
"express": "4.21.2",
|
||||||
"express-session": "1.17.3",
|
"express-session": "1.18.1",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"fs-extra": "11.1.1",
|
"fs-extra": "11.2.0",
|
||||||
"got": "12.6.0",
|
"got": "12.6.1",
|
||||||
"hash-sum": "2.0.0",
|
"hash-sum": "2.0.0",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
@ -54,41 +54,42 @@
|
|||||||
"is-utf8": "0.2.1",
|
"is-utf8": "0.2.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"json-stringify-safe": "5.0.1",
|
"json-stringify-safe": "5.0.1",
|
||||||
"jsonata": "1.8.7",
|
"jsonata": "2.0.5",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"media-typer": "1.1.0",
|
"media-typer": "1.1.0",
|
||||||
"memorystore": "1.6.7",
|
"memorystore": "1.6.7",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"moment": "2.29.4",
|
"moment": "2.30.1",
|
||||||
"moment-timezone": "0.5.43",
|
"moment-timezone": "0.5.46",
|
||||||
"mqtt": "4.3.7",
|
"mqtt": "5.7.0",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"node-red-admin": "^3.1.3",
|
"node-red-admin": "^4.0.1",
|
||||||
"node-watch": "0.7.4",
|
"node-watch": "0.7.4",
|
||||||
"nopt": "5.0.0",
|
"nopt": "5.0.0",
|
||||||
"oauth2orize": "1.11.1",
|
"oauth2orize": "1.12.0",
|
||||||
"on-headers": "1.0.2",
|
"on-headers": "1.0.2",
|
||||||
"passport": "0.6.0",
|
"passport": "0.7.0",
|
||||||
"passport-http-bearer": "1.0.1",
|
"passport-http-bearer": "1.0.1",
|
||||||
"passport-oauth2-client-password": "0.1.2",
|
"passport-oauth2-client-password": "0.1.2",
|
||||||
"raw-body": "2.5.2",
|
"raw-body": "3.0.0",
|
||||||
"semver": "7.5.4",
|
"rfdc": "^1.3.1",
|
||||||
"tar": "6.2.1",
|
"semver": "7.6.3",
|
||||||
"tough-cookie": "4.1.3",
|
"tar": "7.4.3",
|
||||||
|
"tough-cookie": "^5.0.0",
|
||||||
"uglify-js": "3.17.4",
|
"uglify-js": "3.17.4",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.1",
|
||||||
"ws": "7.5.10",
|
"ws": "7.5.10",
|
||||||
"xml2js": "0.6.2"
|
"xml2js": "0.6.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bcrypt": "5.1.1"
|
"@node-rs/bcrypt": "1.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dompurify": "2.4.1",
|
"dompurify": "2.5.7",
|
||||||
"grunt": "1.6.1",
|
"grunt": "1.6.1",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-cli": "~1.4.3",
|
"grunt-cli": "~1.5.0",
|
||||||
"grunt-concurrent": "3.0.0",
|
"grunt-concurrent": "3.0.0",
|
||||||
"grunt-contrib-clean": "2.0.1",
|
"grunt-contrib-clean": "2.0.1",
|
||||||
"grunt-contrib-compress": "2.0.0",
|
"grunt-contrib-compress": "2.0.0",
|
||||||
@ -99,7 +100,7 @@
|
|||||||
"grunt-contrib-watch": "1.1.0",
|
"grunt-contrib-watch": "1.1.0",
|
||||||
"grunt-jsdoc": "2.4.1",
|
"grunt-jsdoc": "2.4.1",
|
||||||
"grunt-jsdoc-to-markdown": "6.0.0",
|
"grunt-jsdoc-to-markdown": "6.0.0",
|
||||||
"grunt-jsonlint": "2.1.3",
|
"grunt-jsonlint": "3.0.0",
|
||||||
"grunt-mkdir": "~1.1.0",
|
"grunt-mkdir": "~1.1.0",
|
||||||
"grunt-npm-command": "~0.1.2",
|
"grunt-npm-command": "~0.1.2",
|
||||||
"grunt-sass": "~3.1.0",
|
"grunt-sass": "~3.1.0",
|
||||||
@ -109,11 +110,11 @@
|
|||||||
"jquery-i18next": "1.2.1",
|
"jquery-i18next": "1.2.1",
|
||||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||||
"marked": "4.3.0",
|
"marked": "4.3.0",
|
||||||
"mermaid": "^10.4.0",
|
"mermaid": "11.3.0",
|
||||||
"minami": "1.2.3",
|
"minami": "1.2.3",
|
||||||
"mocha": "9.2.2",
|
"mocha": "9.2.2",
|
||||||
"node-red-node-test-helper": "^0.3.3",
|
"node-red-node-test-helper": "^0.3.3",
|
||||||
"nodemon": "2.0.20",
|
"nodemon": "3.1.7",
|
||||||
"proxy": "^1.0.2",
|
"proxy": "^1.0.2",
|
||||||
"sass": "1.62.1",
|
"sass": "1.62.1",
|
||||||
"should": "13.2.3",
|
"should": "13.2.3",
|
||||||
@ -122,6 +123,6 @@
|
|||||||
"supertest": "6.3.3"
|
"supertest": "6.3.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=18.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,9 @@ module.exports = {
|
|||||||
store: req.query['store'],
|
store: req.query['store'],
|
||||||
req: apiUtils.getRequestLogObject(req)
|
req: apiUtils.getRequestLogObject(req)
|
||||||
}
|
}
|
||||||
|
if (req.query['keysOnly'] !== undefined) {
|
||||||
|
opts.keysOnly = true
|
||||||
|
}
|
||||||
runtimeAPI.context.getValue(opts).then(function(result) {
|
runtimeAPI.context.getValue(opts).then(function(result) {
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
|
@ -91,6 +91,7 @@ module.exports = {
|
|||||||
// Plugins
|
// Plugins
|
||||||
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
|
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
|
||||||
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
|
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
|
||||||
|
adminApp.get(/^\/plugins\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("plugins.read"),plugins.getConfig,apiUtil.errorHandler);
|
||||||
|
|
||||||
adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);
|
adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);
|
||||||
|
|
||||||
|
@ -40,5 +40,31 @@ module.exports = {
|
|||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
apiUtils.rejectHandler(req,res,err);
|
apiUtils.rejectHandler(req,res,err);
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
getConfig: function(req, res) {
|
||||||
|
|
||||||
|
let opts = {
|
||||||
|
user: req.user,
|
||||||
|
module: req.params[0],
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.get("accept") === "application/json") {
|
||||||
|
runtimeAPI.nodes.getNodeInfo(opts.module).then(function(result) {
|
||||||
|
res.send(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||||
|
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||||
|
opts.lang = "en-US";
|
||||||
|
}
|
||||||
|
runtimeAPI.plugins.getPluginConfig(opts).then(function(result) {
|
||||||
|
return res.send(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -126,6 +126,14 @@ async function login(req,res) {
|
|||||||
if (themeContext.login && themeContext.login.image) {
|
if (themeContext.login && themeContext.login.image) {
|
||||||
response.image = themeContext.login.image;
|
response.image = themeContext.login.image;
|
||||||
}
|
}
|
||||||
|
if (themeContext.login?.message) {
|
||||||
|
response.loginMessage = themeContext.login?.message
|
||||||
|
}
|
||||||
|
if (themeContext.login?.button) {
|
||||||
|
response.prompts = [
|
||||||
|
{ type: "button", ...themeContext.login.button }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.json(response);
|
res.json(response);
|
||||||
}
|
}
|
||||||
@ -160,20 +168,34 @@ function completeVerify(profile,done) {
|
|||||||
|
|
||||||
|
|
||||||
function genericStrategy(adminApp,strategy) {
|
function genericStrategy(adminApp,strategy) {
|
||||||
var crypto = require("crypto")
|
const crypto = require("crypto")
|
||||||
var session = require('express-session')
|
const session = require('express-session')
|
||||||
var MemoryStore = require('memorystore')(session)
|
const MemoryStore = require('memorystore')(session)
|
||||||
|
|
||||||
adminApp.use(session({
|
const sessionOptions = {
|
||||||
// As the session is only used across the life-span of an auth
|
// As the session is only used across the life-span of an auth
|
||||||
// hand-shake, we can use a instance specific random string
|
// hand-shake, we can use a instance specific random string
|
||||||
secret: crypto.randomBytes(20).toString('hex'),
|
secret: crypto.randomBytes(20).toString('hex'),
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
store: new MemoryStore({
|
store: new MemoryStore({
|
||||||
checkPeriod: 86400000 // prune expired entries every 24h
|
checkPeriod: 86400000 // prune expired entries every 24h
|
||||||
})
|
})
|
||||||
}));
|
}
|
||||||
|
if (settings.httpAdminCookieOptions) {
|
||||||
|
sessionOptions.cookie = {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false,
|
||||||
|
maxAge: null,
|
||||||
|
...settings.httpAdminCookieOptions
|
||||||
|
}
|
||||||
|
if (sessionOptions.cookie.name){
|
||||||
|
sessionOptions.name = sessionOptions.cookie.name
|
||||||
|
delete sessionOptions.cookie.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adminApp.use(session(sessionOptions));
|
||||||
//TODO: all passport references ought to be in ./auth
|
//TODO: all passport references ought to be in ./auth
|
||||||
adminApp.use(passport.initialize());
|
adminApp.use(passport.initialize());
|
||||||
adminApp.use(passport.session());
|
adminApp.use(passport.session());
|
||||||
@ -207,10 +229,10 @@ function genericStrategy(adminApp,strategy) {
|
|||||||
adminApp.get('/auth/strategy',
|
adminApp.get('/auth/strategy',
|
||||||
passport.authenticate(strategy.name, {
|
passport.authenticate(strategy.name, {
|
||||||
session:false,
|
session:false,
|
||||||
failureMessage: true,
|
failWithError: true,
|
||||||
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
|
failureMessage: true
|
||||||
}),
|
}),
|
||||||
completeGenerateStrategyAuth,
|
completeGenericStrategyAuth,
|
||||||
handleStrategyError
|
handleStrategyError
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -222,14 +244,14 @@ function genericStrategy(adminApp,strategy) {
|
|||||||
passport.authenticate(strategy.name, {
|
passport.authenticate(strategy.name, {
|
||||||
session:false,
|
session:false,
|
||||||
failureMessage: true,
|
failureMessage: true,
|
||||||
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
|
failWithError: true
|
||||||
}),
|
}),
|
||||||
completeGenerateStrategyAuth,
|
completeGenericStrategyAuth,
|
||||||
handleStrategyError
|
handleStrategyError
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
function completeGenerateStrategyAuth(req,res) {
|
function completeGenericStrategyAuth(req,res) {
|
||||||
var tokens = req.user.tokens;
|
var tokens = req.user.tokens;
|
||||||
delete req.user.tokens;
|
delete req.user.tokens;
|
||||||
// Successful authentication, redirect home.
|
// Successful authentication, redirect home.
|
||||||
@ -239,6 +261,8 @@ function handleStrategyError(err, req, res, next) {
|
|||||||
if (res.headersSent) {
|
if (res.headersSent) {
|
||||||
return next(err)
|
return next(err)
|
||||||
}
|
}
|
||||||
|
// Remove the header that passport auto-adds as we don't need it
|
||||||
|
res.removeHeader('WWW-Authenticate')
|
||||||
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
|
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
|
||||||
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
|
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ function hasPermission(userScope,permission) {
|
|||||||
}
|
}
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
if (util.isArray(permission)) {
|
if (Array.isArray(permission)) {
|
||||||
// Multiple permissions requested - check each one
|
// Multiple permissions requested - check each one
|
||||||
for (i=0;i<permission.length;i++) {
|
for (i=0;i<permission.length;i++) {
|
||||||
if (!hasPermission(userScope,permission[i])) {
|
if (!hasPermission(userScope,permission[i])) {
|
||||||
@ -36,7 +36,7 @@ function hasPermission(userScope,permission) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util.isArray(userScope)) {
|
if (Array.isArray(userScope)) {
|
||||||
if (userScope.length === 0) {
|
if (userScope.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
var util = require("util");
|
var util = require("util");
|
||||||
var clone = require("clone");
|
var clone = require("clone");
|
||||||
var bcrypt;
|
var bcrypt;
|
||||||
try { bcrypt = require('bcrypt'); }
|
try { bcrypt = require('@node-rs/bcrypt'); }
|
||||||
catch(e) { bcrypt = require('bcryptjs'); }
|
catch(e) { bcrypt = require('bcryptjs'); }
|
||||||
var users = {};
|
var users = {};
|
||||||
var defaultUser = null;
|
var defaultUser = null;
|
||||||
@ -33,11 +33,11 @@ function authenticate() {
|
|||||||
if (args.length === 2) {
|
if (args.length === 2) {
|
||||||
// Username/password authentication
|
// Username/password authentication
|
||||||
var password = args[1];
|
var password = args[1];
|
||||||
return new Promise(function(resolve,reject) {
|
return bcrypt.compare(password, user.password).then(res => {
|
||||||
bcrypt.compare(password, user.password, function(err, res) {
|
return res ? cleanUser(user) : null
|
||||||
resolve(res?cleanUser(user):null);
|
}).catch(err => {
|
||||||
});
|
return null
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
// Try to extract common profile information
|
// Try to extract common profile information
|
||||||
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
|
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
|
||||||
@ -74,7 +74,7 @@ function init(config) {
|
|||||||
} else {
|
} else {
|
||||||
var us = config.users;
|
var us = config.users;
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (!util.isArray(us)) {
|
if (!Array.isArray(us)) {
|
||||||
us = [us];
|
us = [us];
|
||||||
}
|
}
|
||||||
for (var i=0;i<us.length;i++) {
|
for (var i=0;i<us.length;i++) {
|
||||||
|
@ -77,6 +77,53 @@ function CommsConnection(ws, user) {
|
|||||||
log.trace("comms.close "+self.session);
|
log.trace("comms.close "+self.session);
|
||||||
removeActiveConnection(self);
|
removeActiveConnection(self);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleAuthPacket = function(msg) {
|
||||||
|
Tokens.get(msg.auth).then(function(client) {
|
||||||
|
if (client) {
|
||||||
|
Users.get(client.user).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
self.user = user;
|
||||||
|
log.audit({event: "comms.auth",user:self.user});
|
||||||
|
completeConnection(msg, client.scope,msg.auth,true);
|
||||||
|
} else {
|
||||||
|
log.audit({event: "comms.auth.fail"});
|
||||||
|
completeConnection(msg, null,null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Users.tokens(msg.auth).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
self.user = user;
|
||||||
|
log.audit({event: "comms.auth",user:self.user});
|
||||||
|
completeConnection(msg, user.permissions,msg.auth,true);
|
||||||
|
} else {
|
||||||
|
log.audit({event: "comms.auth.fail"});
|
||||||
|
completeConnection(msg, null,null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const completeConnection = function(msg, userScope, session, sendAck) {
|
||||||
|
try {
|
||||||
|
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
||||||
|
ws.send(JSON.stringify({auth:"fail"}));
|
||||||
|
ws.close();
|
||||||
|
} else {
|
||||||
|
pendingAuth = false;
|
||||||
|
addActiveConnection(self);
|
||||||
|
self.token = msg.auth;
|
||||||
|
if (sendAck) {
|
||||||
|
ws.send(JSON.stringify({auth:"ok"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
// Just in case the socket closes before we attempt
|
||||||
|
// to send anything.
|
||||||
|
}
|
||||||
|
}
|
||||||
ws.on('message', function(data,flags) {
|
ws.on('message', function(data,flags) {
|
||||||
var msg = null;
|
var msg = null;
|
||||||
try {
|
try {
|
||||||
@ -86,68 +133,34 @@ function CommsConnection(ws, user) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!pendingAuth) {
|
if (!pendingAuth) {
|
||||||
if (msg.subscribe) {
|
if (msg.auth) {
|
||||||
|
handleAuthPacket(msg)
|
||||||
|
} else if (msg.subscribe) {
|
||||||
self.subscribe(msg.subscribe);
|
self.subscribe(msg.subscribe);
|
||||||
// handleRemoteSubscription(ws,msg.subscribe);
|
// handleRemoteSubscription(ws,msg.subscribe);
|
||||||
|
} else if (msg.topic) {
|
||||||
|
runtimeAPI.comms.receive({
|
||||||
|
user: self.user,
|
||||||
|
client: self,
|
||||||
|
topic: msg.topic,
|
||||||
|
data: msg.data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var completeConnection = function(userScope,session,sendAck) {
|
|
||||||
try {
|
|
||||||
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
|
||||||
ws.send(JSON.stringify({auth:"fail"}));
|
|
||||||
ws.close();
|
|
||||||
} else {
|
|
||||||
pendingAuth = false;
|
|
||||||
addActiveConnection(self);
|
|
||||||
self.token = msg.auth;
|
|
||||||
if (sendAck) {
|
|
||||||
ws.send(JSON.stringify({auth:"ok"}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
console.log(err.stack);
|
|
||||||
// Just in case the socket closes before we attempt
|
|
||||||
// to send anything.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.auth) {
|
if (msg.auth) {
|
||||||
Tokens.get(msg.auth).then(function(client) {
|
handleAuthPacket(msg)
|
||||||
if (client) {
|
|
||||||
Users.get(client.user).then(function(user) {
|
|
||||||
if (user) {
|
|
||||||
self.user = user;
|
|
||||||
log.audit({event: "comms.auth",user:self.user});
|
|
||||||
completeConnection(client.scope,msg.auth,true);
|
|
||||||
} else {
|
|
||||||
log.audit({event: "comms.auth.fail"});
|
|
||||||
completeConnection(null,null,false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Users.tokens(msg.auth).then(function(user) {
|
|
||||||
if (user) {
|
|
||||||
self.user = user;
|
|
||||||
log.audit({event: "comms.auth",user:self.user});
|
|
||||||
completeConnection(user.permissions,msg.auth,true);
|
|
||||||
} else {
|
|
||||||
log.audit({event: "comms.auth.fail"});
|
|
||||||
completeConnection(null,null,false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (anonymousUser) {
|
if (anonymousUser) {
|
||||||
log.audit({event: "comms.auth",user:anonymousUser});
|
log.audit({event: "comms.auth",user:anonymousUser});
|
||||||
self.user = anonymousUser;
|
self.user = anonymousUser;
|
||||||
completeConnection(anonymousUser.permissions,null,false);
|
completeConnection(msg, anonymousUser.permissions, null, false);
|
||||||
//TODO: duplicated code - pull non-auth message handling out
|
//TODO: duplicated code - pull non-auth message handling out
|
||||||
if (msg.subscribe) {
|
if (msg.subscribe) {
|
||||||
self.subscribe(msg.subscribe);
|
self.subscribe(msg.subscribe);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.audit({event: "comms.auth.fail"});
|
log.audit({event: "comms.auth.fail"});
|
||||||
completeConnection(null,null,false);
|
completeConnection(msg, null,null,false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
|
|||||||
var result = [];
|
var result = [];
|
||||||
if (themeValue) {
|
if (themeValue) {
|
||||||
var array = themeValue;
|
var array = themeValue;
|
||||||
if (!util.isArray(array)) {
|
if (!Array.isArray(array)) {
|
||||||
array = [array];
|
array = [array];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,13 +185,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.deployButton) {
|
if (theme.deployButton) {
|
||||||
|
themeSettings.deployButton = {};
|
||||||
|
if (theme.deployButton.label) {
|
||||||
|
themeSettings.deployButton.label = theme.deployButton.label;
|
||||||
|
}
|
||||||
if (theme.deployButton.type == "simple") {
|
if (theme.deployButton.type == "simple") {
|
||||||
themeSettings.deployButton = {
|
themeSettings.deployButton.type = theme.deployButton.type;
|
||||||
type: "simple"
|
|
||||||
}
|
|
||||||
if (theme.deployButton.label) {
|
|
||||||
themeSettings.deployButton.label = theme.deployButton.label;
|
|
||||||
}
|
|
||||||
if (theme.deployButton.icon) {
|
if (theme.deployButton.icon) {
|
||||||
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
||||||
if (url) {
|
if (url) {
|
||||||
@ -206,14 +205,26 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.login) {
|
if (theme.login) {
|
||||||
|
let themeContextLogin = {}
|
||||||
|
let hasLoginTheme = false
|
||||||
if (theme.login.image) {
|
if (theme.login.image) {
|
||||||
url = serveFile(themeApp,"/login/",theme.login.image);
|
url = serveFile(themeApp,"/login/",theme.login.image);
|
||||||
if (url) {
|
if (url) {
|
||||||
themeContext.login = {
|
themeContextLogin.image = url
|
||||||
image: url
|
hasLoginTheme = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (theme.login.message) {
|
||||||
|
themeContextLogin.message = theme.login.message
|
||||||
|
hasLoginTheme = true
|
||||||
|
}
|
||||||
|
if (theme.login.button) {
|
||||||
|
themeContextLogin.button = theme.login.button
|
||||||
|
hasLoginTheme = true
|
||||||
|
}
|
||||||
|
if (hasLoginTheme) {
|
||||||
|
themeContext.login = themeContextLogin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
themeApp.get("/", async function(req,res) {
|
themeApp.get("/", async function(req,res) {
|
||||||
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
||||||
@ -233,6 +244,10 @@ module.exports = {
|
|||||||
themeSettings.projects = theme.projects;
|
themeSettings.projects = theme.projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (theme.hasOwnProperty("multiplayer")) {
|
||||||
|
themeSettings.multiplayer = theme.multiplayer;
|
||||||
|
}
|
||||||
|
|
||||||
if (theme.hasOwnProperty("keymap")) {
|
if (theme.hasOwnProperty("keymap")) {
|
||||||
themeSettings.keymap = theme.keymap;
|
themeSettings.keymap = theme.keymap;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-api",
|
"name": "@node-red/editor-api",
|
||||||
"version": "3.1.11",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -16,25 +16,25 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/util": "3.1.11",
|
"@node-red/util": "4.0.9",
|
||||||
"@node-red/editor-client": "3.1.11",
|
"@node-red/editor-client": "4.0.9",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"express-session": "1.17.3",
|
"express-session": "1.18.1",
|
||||||
"express": "4.19.2",
|
"express": "4.21.2",
|
||||||
"memorystore": "1.6.7",
|
"memorystore": "1.6.7",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"oauth2orize": "1.11.1",
|
"oauth2orize": "1.12.0",
|
||||||
"passport-http-bearer": "1.0.1",
|
"passport-http-bearer": "1.0.1",
|
||||||
"passport-oauth2-client-password": "0.1.2",
|
"passport-oauth2-client-password": "0.1.2",
|
||||||
"passport": "0.6.0",
|
"passport": "0.7.0",
|
||||||
"ws": "7.5.10"
|
"ws": "7.5.10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bcrypt": "5.1.0"
|
"@node-rs/bcrypt": "1.10.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -590,6 +590,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ Node",
|
"nodeCount": "__label__ Node",
|
||||||
"nodeCount_plural": "__label__ Nodes",
|
"nodeCount_plural": "__label__ Nodes",
|
||||||
|
"pluginCount": "__count__ Plugin",
|
||||||
|
"pluginCount_plural": "__count__ Plugins",
|
||||||
"moduleCount": "__count__ Modul verfügbar",
|
"moduleCount": "__count__ Modul verfügbar",
|
||||||
"moduleCount_plural": "__count__ Module verfügbar",
|
"moduleCount_plural": "__count__ Module verfügbar",
|
||||||
"inuse": "In Gebrauch",
|
"inuse": "In Gebrauch",
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"lock": "Lock",
|
"lock": "Lock",
|
||||||
"unlock": "Unlock",
|
"unlock": "Unlock",
|
||||||
"locked": "Locked",
|
"locked": "Locked",
|
||||||
"unlocked": "Unlocked"
|
"unlocked": "Unlocked",
|
||||||
|
"format": "Format"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "string",
|
"string": "string",
|
||||||
@ -372,8 +373,12 @@
|
|||||||
"deleted": "deleted",
|
"deleted": "deleted",
|
||||||
"flowDeleted": "flow deleted",
|
"flowDeleted": "flow deleted",
|
||||||
"flowAdded": "flow added",
|
"flowAdded": "flow added",
|
||||||
|
"moved": "moved",
|
||||||
"movedTo": "moved to __id__",
|
"movedTo": "moved to __id__",
|
||||||
"movedFrom": "moved from __id__"
|
"movedFrom": "moved from __id__",
|
||||||
|
"none": "none",
|
||||||
|
"position": "position",
|
||||||
|
"wires": "wires"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ node",
|
"nodeCount": "__count__ node",
|
||||||
"nodeCount_plural": "__count__ nodes",
|
"nodeCount_plural": "__count__ nodes",
|
||||||
@ -382,9 +387,14 @@
|
|||||||
"reviewChanges": "Review Changes",
|
"reviewChanges": "Review Changes",
|
||||||
"noBinaryFileShowed": "Cannot show binary file contents",
|
"noBinaryFileShowed": "Cannot show binary file contents",
|
||||||
"viewCommitDiff": "View Commit Changes",
|
"viewCommitDiff": "View Commit Changes",
|
||||||
|
"commit": "Commit",
|
||||||
"compareChanges": "Compare Changes",
|
"compareChanges": "Compare Changes",
|
||||||
"saveConflict": "Save conflict resolution",
|
"saveConflict": "Save conflict resolution",
|
||||||
"conflictHeader": "<span>__resolved__</span> of <span>__unresolved__</span> conflicts resolved",
|
"conflictHeader": "<span>__resolved__</span> of <span>__unresolved__</span> conflicts resolved",
|
||||||
|
"localChanges": "Local Changes",
|
||||||
|
"remoteChanges": "Remote Changes",
|
||||||
|
"useLocalChanges": "use local changes",
|
||||||
|
"useRemoteChanges": "use remote changes",
|
||||||
"commonVersionError": "Common Version doesn't contain valid JSON:",
|
"commonVersionError": "Common Version doesn't contain valid JSON:",
|
||||||
"oldVersionError": "Old Version doesn't contain valid JSON:",
|
"oldVersionError": "Old Version doesn't contain valid JSON:",
|
||||||
"newVersionError": "New Version doesn't contain valid JSON:"
|
"newVersionError": "New Version doesn't contain valid JSON:"
|
||||||
@ -552,7 +562,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"examples": "Examples"
|
"examples": "Examples"
|
||||||
}
|
},
|
||||||
|
"type": "Type",
|
||||||
|
"name": "Name"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "no information available",
|
"noInfo": "no information available",
|
||||||
@ -614,6 +626,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ node",
|
"nodeCount": "__label__ node",
|
||||||
"nodeCount_plural": "__label__ nodes",
|
"nodeCount_plural": "__label__ nodes",
|
||||||
|
"pluginCount": "__count__ plugin",
|
||||||
|
"pluginCount_plural": "__count__ plugins",
|
||||||
"moduleCount": "__count__ module available",
|
"moduleCount": "__count__ module available",
|
||||||
"moduleCount_plural": "__count__ modules available",
|
"moduleCount_plural": "__count__ modules available",
|
||||||
"inuse": "in use",
|
"inuse": "in use",
|
||||||
@ -641,6 +655,7 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
|
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
|
||||||
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
|
"installTimeout": "<p>Install continuing the background.</p><p>Nodes will appear in palette when complete. Check the log for more information.</p>",
|
||||||
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
@ -655,6 +670,9 @@
|
|||||||
"body": "<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
|
"body": "<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
|
||||||
"title": "Remove nodes"
|
"title": "Remove nodes"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>Removed plugin __module__. Please reload the editor to clear left-overs.</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body": "<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
|
"body": "<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
|
||||||
"title": "Update nodes"
|
"title": "Update nodes"
|
||||||
@ -666,7 +684,8 @@
|
|||||||
"review": "Open node information",
|
"review": "Open node information",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"update": "Update"
|
"update": "Update",
|
||||||
|
"understood": "Understood"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -794,6 +813,7 @@
|
|||||||
"branches": "Branches",
|
"branches": "Branches",
|
||||||
"noBranches": "No branches",
|
"noBranches": "No branches",
|
||||||
"deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
|
"deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
|
||||||
|
"deleteBranch": "Delete branch",
|
||||||
"unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
|
"unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
|
||||||
"deleteUnmergedBranch": "Delete unmerged branch",
|
"deleteUnmergedBranch": "Delete unmerged branch",
|
||||||
"gitRemotes": "Git remotes",
|
"gitRemotes": "Git remotes",
|
||||||
@ -927,7 +947,14 @@
|
|||||||
"date": "timestamp",
|
"date": "timestamp",
|
||||||
"jsonata": "expression",
|
"jsonata": "expression",
|
||||||
"env": "env variable",
|
"env": "env variable",
|
||||||
"cred": "credential"
|
"cred": "credential",
|
||||||
|
"conf-types": "config node"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "milliseconds since epoch",
|
||||||
|
"object": "JavaScript Date Object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"lock": "Bloquear",
|
"lock": "Bloquear",
|
||||||
"unlock": "Desbloquear",
|
"unlock": "Desbloquear",
|
||||||
"locked": "Bloqueado",
|
"locked": "Bloqueado",
|
||||||
"unlocked": "Desbloqueado"
|
"unlocked": "Desbloqueado",
|
||||||
|
"format": "Formato"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "texto",
|
"string": "texto",
|
||||||
@ -303,7 +304,8 @@
|
|||||||
"missingType": "La entrada no es un flujo válido - elemento __index__ falta la propiedad 'type'"
|
"missingType": "La entrada no es un flujo válido - elemento __index__ falta la propiedad 'type'"
|
||||||
},
|
},
|
||||||
"conflictNotification1": "Algunos de los nodos que estás importando ya existen en tu espacio de trabajo.",
|
"conflictNotification1": "Algunos de los nodos que estás importando ya existen en tu espacio de trabajo.",
|
||||||
"conflictNotification2": "Selecciona qué nodos importar y si reemplazar los nodos existentes o importar una copia de los mismos."
|
"conflictNotification2": "Selecciona qué nodos importar y si reemplazar los nodos existentes o importar una copia de los mismos.",
|
||||||
|
"alreadyExists": "Este nodo ya existe"
|
||||||
},
|
},
|
||||||
"copyMessagePath": "Ruta copiada",
|
"copyMessagePath": "Ruta copiada",
|
||||||
"copyMessageValue": "Valor copiado",
|
"copyMessageValue": "Valor copiado",
|
||||||
@ -371,8 +373,12 @@
|
|||||||
"deleted": "eliminado",
|
"deleted": "eliminado",
|
||||||
"flowDeleted": "flujo eliminado",
|
"flowDeleted": "flujo eliminado",
|
||||||
"flowAdded": "flujo añadido",
|
"flowAdded": "flujo añadido",
|
||||||
|
"moved": "movido",
|
||||||
"movedTo": "movido a __id__",
|
"movedTo": "movido a __id__",
|
||||||
"movedFrom": "movido desde __id__"
|
"movedFrom": "movido desde __id__",
|
||||||
|
"none": "ninguno",
|
||||||
|
"position": "posición",
|
||||||
|
"wires": "conectores"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ nodo",
|
"nodeCount": "__count__ nodo",
|
||||||
"nodeCount_plural": "__count__ nodos",
|
"nodeCount_plural": "__count__ nodos",
|
||||||
@ -381,9 +387,14 @@
|
|||||||
"reviewChanges": "Revisar Cambios",
|
"reviewChanges": "Revisar Cambios",
|
||||||
"noBinaryFileShowed": "No se puede mostrar el contenido del archivo binario",
|
"noBinaryFileShowed": "No se puede mostrar el contenido del archivo binario",
|
||||||
"viewCommitDiff": "Ver cambios de commit",
|
"viewCommitDiff": "Ver cambios de commit",
|
||||||
|
"commit": "Commit",
|
||||||
"compareChanges": "Comparar Cambios",
|
"compareChanges": "Comparar Cambios",
|
||||||
"saveConflict": "Guardar resolución de conflictos",
|
"saveConflict": "Guardar resolución de conflictos",
|
||||||
"conflictHeader": "<span>__resolved__</span> de <span>__unresolved__</span> conflictos resueltos",
|
"conflictHeader": "<span>__resolved__</span> de <span>__unresolved__</span> conflictos resueltos",
|
||||||
|
"localChanges": "Cambios Locales",
|
||||||
|
"remoteChanges": "Cambios Remotos",
|
||||||
|
"useLocalChanges": "utilizar cambios locales",
|
||||||
|
"useRemoteChanges": "utilizar cambios remotos",
|
||||||
"commonVersionError": "La versión común no contiene JSON válido:",
|
"commonVersionError": "La versión común no contiene JSON válido:",
|
||||||
"oldVersionError": "La versión anterior no contiene JSON válido:",
|
"oldVersionError": "La versión anterior no contiene JSON válido:",
|
||||||
"newVersionError": "La versión nueva no contiene JSON válido:"
|
"newVersionError": "La versión nueva no contiene JSON válido:"
|
||||||
@ -551,7 +562,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"examples": "Ejemplos"
|
"examples": "Ejemplos"
|
||||||
}
|
},
|
||||||
|
"type": "Tipo",
|
||||||
|
"name": "Nombre"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "no hay información disponible",
|
"noInfo": "no hay información disponible",
|
||||||
@ -613,6 +626,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ nodo",
|
"nodeCount": "__label__ nodo",
|
||||||
"nodeCount_plural": "__label__ nodos",
|
"nodeCount_plural": "__label__ nodos",
|
||||||
|
"pluginCount": "__count__ extensión",
|
||||||
|
"pluginCount_plural": "__count__ extensiones",
|
||||||
"moduleCount": "__count__ módulo disponible",
|
"moduleCount": "__count__ módulo disponible",
|
||||||
"moduleCount_plural": "__count__ módulos disponibles",
|
"moduleCount_plural": "__count__ módulos disponibles",
|
||||||
"inuse": "en uso",
|
"inuse": "en uso",
|
||||||
@ -640,6 +655,7 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>La carga del catálogo de nodos ha fallado</p><p>Revise la consola del navegador para mas información</p>",
|
"catalogLoadFailed": "<p>La carga del catálogo de nodos ha fallado</p><p>Revise la consola del navegador para mas información</p>",
|
||||||
"installFailed": "<p>Fallo al instalar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"installFailed": "<p>Fallo al instalar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
|
"installTimeout": "<p>La instalación continúa en segundo plano.</p><p>Los nodos aparecerán en la paleta cuando finalice. Consulta el registro para obtener más información.</p>",
|
||||||
"removeFailed": "<p>Fallo al eliminar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"removeFailed": "<p>Fallo al eliminar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
"updateFailed": "<p>Fallo al actualizar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"updateFailed": "<p>Fallo al actualizar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
"enableFailed": "<p>Fallo al activar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"enableFailed": "<p>Fallo al activar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
@ -654,6 +670,9 @@
|
|||||||
"body":"<p>Eliminando '__module__'</p><p>La eliminación del nodo lo desinstalará de Node-RED. Es posible que el nodo siga utilizando recursos hasta que Node-RED sea reiniciado.</p>",
|
"body":"<p>Eliminando '__module__'</p><p>La eliminación del nodo lo desinstalará de Node-RED. Es posible que el nodo siga utilizando recursos hasta que Node-RED sea reiniciado.</p>",
|
||||||
"title": "Eliminar nodos"
|
"title": "Eliminar nodos"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>Extensión __module__ eliminada. Vuelve a cargar el editor para borrar los elementos sobrantes.</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body":"<p>Actualizando '__module__'</p><p>La actualización del nodo requerirá un reinicio manual de Node-RED para completarse. Debe ser reiniciado manualmente.</p>",
|
"body":"<p>Actualizando '__module__'</p><p>La actualización del nodo requerirá un reinicio manual de Node-RED para completarse. Debe ser reiniciado manualmente.</p>",
|
||||||
"title": "Actualizar nodos"
|
"title": "Actualizar nodos"
|
||||||
@ -665,7 +684,8 @@
|
|||||||
"review": "Abrir información del nodo",
|
"review": "Abrir información del nodo",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"update": "Actualizar"
|
"update": "Actualizar",
|
||||||
|
"understood": "Entendido"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -718,6 +738,7 @@
|
|||||||
"nodeHelp": "Ayuda de nodo",
|
"nodeHelp": "Ayuda de nodo",
|
||||||
"showHelp": "Mostrar ayuda",
|
"showHelp": "Mostrar ayuda",
|
||||||
"showInOutline": "Mostrar en controno",
|
"showInOutline": "Mostrar en controno",
|
||||||
|
"hideTopics": "Esconder temas",
|
||||||
"showTopics": "Mostrar temas",
|
"showTopics": "Mostrar temas",
|
||||||
"noHelp": "No hay ningun tema de ayuda seleccionado",
|
"noHelp": "No hay ningun tema de ayuda seleccionado",
|
||||||
"changeLog": "Registro de Cambios"
|
"changeLog": "Registro de Cambios"
|
||||||
@ -792,6 +813,7 @@
|
|||||||
"branches": "Ramas",
|
"branches": "Ramas",
|
||||||
"noBranches": "Sin ramas",
|
"noBranches": "Sin ramas",
|
||||||
"deleteConfirm": "¿Estás seguro de que quieres eliminar la rama local '__name__'? Esta acción no puede deshacerse.",
|
"deleteConfirm": "¿Estás seguro de que quieres eliminar la rama local '__name__'? Esta acción no puede deshacerse.",
|
||||||
|
"deleteBranch": "Eliminar rama",
|
||||||
"unmergedConfirm": "La rama local '__name__' tiene cambios no fusionados que se perderán. ¿Estás seguro de que quieres eliminarla?",
|
"unmergedConfirm": "La rama local '__name__' tiene cambios no fusionados que se perderán. ¿Estás seguro de que quieres eliminarla?",
|
||||||
"deleteUnmergedBranch": "Eliminar rama no fusionada",
|
"deleteUnmergedBranch": "Eliminar rama no fusionada",
|
||||||
"gitRemotes": "Git remotes",
|
"gitRemotes": "Git remotes",
|
||||||
@ -913,6 +935,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedInput": {
|
"typedInput": {
|
||||||
|
"selected": "__count__ seleccionado",
|
||||||
|
"selected_plural": "__count__ seleccionados",
|
||||||
"type": {
|
"type": {
|
||||||
"str": "texto",
|
"str": "texto",
|
||||||
"num": "número",
|
"num": "número",
|
||||||
@ -923,7 +947,14 @@
|
|||||||
"date": "marca tiempo",
|
"date": "marca tiempo",
|
||||||
"jsonata": "expresión",
|
"jsonata": "expresión",
|
||||||
"env": "variable de entorno",
|
"env": "variable de entorno",
|
||||||
"cred": "credencial"
|
"cred": "credencial",
|
||||||
|
"conf-types": "nodo configuración"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "milisegundos desde epoch",
|
||||||
|
"object": "Objeto de fecha de JavaScript"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
@ -1205,6 +1236,18 @@
|
|||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"title": "Información Sistema"
|
"title": "Información Sistema"
|
||||||
},
|
},
|
||||||
|
"languages": {
|
||||||
|
"de": "Deutsch",
|
||||||
|
"en-US": "English",
|
||||||
|
"es-ES": "Español (España)",
|
||||||
|
"fr": "Français",
|
||||||
|
"ja": "日本語",
|
||||||
|
"ko": "Korean",
|
||||||
|
"pt-BR": "Português (Brasil)",
|
||||||
|
"ru": "Русский",
|
||||||
|
"zh-CN": "简体中文",
|
||||||
|
"zh-TW": "繁體中文"
|
||||||
|
},
|
||||||
"validator": {
|
"validator": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-json": "Datos JSON inválidos: __error__",
|
"invalid-json": "Datos JSON inválidos: __error__",
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"lock": "Verrouiller",
|
"lock": "Verrouiller",
|
||||||
"unlock": "Déverrouiller",
|
"unlock": "Déverrouiller",
|
||||||
"locked": "Verrouillé",
|
"locked": "Verrouillé",
|
||||||
"unlocked": "Déverrouillé"
|
"unlocked": "Déverrouillé",
|
||||||
|
"format": "Format"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "chaîne de caractères",
|
"string": "chaîne de caractères",
|
||||||
@ -54,10 +55,10 @@
|
|||||||
"workspace": {
|
"workspace": {
|
||||||
"defaultName": "Flux __number__",
|
"defaultName": "Flux __number__",
|
||||||
"editFlow": "Modifier le flux : __name__",
|
"editFlow": "Modifier le flux : __name__",
|
||||||
"confirmDelete": "Confirmation de la suppression",
|
"confirmDelete": "Confirmer la suppression",
|
||||||
"delete": "Etes-vous sûr de vouloir supprimer '__label__'?",
|
"delete": "Êtes-vous sûr de vouloir supprimer '__label__' ?",
|
||||||
"dropFlowHere": "Déposer le flux ici",
|
"dropFlowHere": "Lâchez le flux ici",
|
||||||
"dropImageHere": "Déposer l'image ici",
|
"dropImageHere": "Lâchez l'image ici",
|
||||||
"addFlow": "Ajouter un flux",
|
"addFlow": "Ajouter un flux",
|
||||||
"addFlowToRight": "Ajouter un flux à droite",
|
"addFlowToRight": "Ajouter un flux à droite",
|
||||||
"closeFlow": "Fermer le flux",
|
"closeFlow": "Fermer le flux",
|
||||||
@ -74,7 +75,7 @@
|
|||||||
"enabled": "Activé",
|
"enabled": "Activé",
|
||||||
"disabled": "Désactivé",
|
"disabled": "Désactivé",
|
||||||
"info": "Description",
|
"info": "Description",
|
||||||
"selectNodes": "Cliquer sur les noeuds pour sélectionner",
|
"selectNodes": "Cliquer pour sélectionner",
|
||||||
"enableFlow": "Activer le flux",
|
"enableFlow": "Activer le flux",
|
||||||
"disableFlow": "Désactiver le flux",
|
"disableFlow": "Désactiver le flux",
|
||||||
"lockFlow": "Verrouiller le flux",
|
"lockFlow": "Verrouiller le flux",
|
||||||
@ -98,7 +99,7 @@
|
|||||||
"rtl": "De droite à gauche",
|
"rtl": "De droite à gauche",
|
||||||
"auto": "Contextuel",
|
"auto": "Contextuel",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"browserDefault": "Navigateur par défaut"
|
"browserDefault": "Par défaut du Navigateur"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"show": "Afficher la barre latérale"
|
"show": "Afficher la barre latérale"
|
||||||
@ -134,7 +135,7 @@
|
|||||||
"disableSelectedNodes": "Désactiver les noeuds sélectionnés",
|
"disableSelectedNodes": "Désactiver les noeuds sélectionnés",
|
||||||
"showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés",
|
"showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés",
|
||||||
"hideSelectedNodeLabels": "Masquer les étiquettes des noeuds sélectionnés",
|
"hideSelectedNodeLabels": "Masquer les étiquettes des noeuds sélectionnés",
|
||||||
"showWelcomeTours": "Afficher les visites guidées pour les nouvelles versions",
|
"showWelcomeTours": "Afficher les visites guidées des nouvelles versions",
|
||||||
"help": "Site web de Node-RED",
|
"help": "Site web de Node-RED",
|
||||||
"projects": "Projets",
|
"projects": "Projets",
|
||||||
"projects-new": "Nouveau projet",
|
"projects-new": "Nouveau projet",
|
||||||
@ -143,7 +144,7 @@
|
|||||||
"showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés",
|
"showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés",
|
||||||
"codeEditor": "Éditeur de code",
|
"codeEditor": "Éditeur de code",
|
||||||
"groups": "Groupes",
|
"groups": "Groupes",
|
||||||
"groupSelection": "Grouper cette sélection",
|
"groupSelection": "Grouper la sélection",
|
||||||
"ungroupSelection": "Dégrouper la sélection",
|
"ungroupSelection": "Dégrouper la sélection",
|
||||||
"groupMergeSelection": "Fusionner la sélection",
|
"groupMergeSelection": "Fusionner la sélection",
|
||||||
"groupRemoveSelection": "Supprimer du groupe",
|
"groupRemoveSelection": "Supprimer du groupe",
|
||||||
@ -155,7 +156,7 @@
|
|||||||
"alignMiddle": "Aligner au milieu",
|
"alignMiddle": "Aligner au milieu",
|
||||||
"alignBottom": "Aligner en bas",
|
"alignBottom": "Aligner en bas",
|
||||||
"distributeHorizontally": "Répartir horizontalement",
|
"distributeHorizontally": "Répartir horizontalement",
|
||||||
"distributeVertically": "Distribuer verticalement",
|
"distributeVertically": "Répartir verticalement",
|
||||||
"moveToBack": "Déplacer vers l'arrière",
|
"moveToBack": "Déplacer vers l'arrière",
|
||||||
"moveToFront": "Déplacer vers l'avant",
|
"moveToFront": "Déplacer vers l'avant",
|
||||||
"moveBackwards": "Reculer",
|
"moveBackwards": "Reculer",
|
||||||
@ -163,21 +164,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"toggle-navigator": "Basculer de navigateur",
|
"toggle-navigator": "Basculer l'affichage du navigateur",
|
||||||
"zoom-out": "Dézoomer",
|
"zoom-out": "Réduire",
|
||||||
"zoom-reset": "Réinitialiser le zoom",
|
"zoom-reset": "Réinitialiser",
|
||||||
"zoom-in": "Agrandir",
|
"zoom-in": "Agrandir",
|
||||||
"search-flows": "Rechercher le flux",
|
"search-flows": "Rechercher le flux",
|
||||||
"search-prev": "Précédent",
|
"search-prev": "Précédent",
|
||||||
"search-next": "Suivant",
|
"search-next": "Suivant",
|
||||||
"search-counter": "\"__term__\" __result__ de __count__"
|
"search-counter": "\"__term__\" __result__ sur __count__"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"loggedInAs": "Connecté en tant que __name__",
|
"loggedInAs": "Connecté en tant que __name__",
|
||||||
"username": "Nom d'utilisateur",
|
"username": "Nom d'utilisateur",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"login": "Connexion",
|
"login": "Se connecter",
|
||||||
"loginFailed": "Échec de la connexion",
|
"loginFailed": "Échec de connexion",
|
||||||
"notAuthorized": "Pas autorisé",
|
"notAuthorized": "Pas autorisé",
|
||||||
"errors": {
|
"errors": {
|
||||||
"settings": "Vous devez être connecté pour accéder aux paramètres",
|
"settings": "Vous devez être connecté pour accéder aux paramètres",
|
||||||
@ -193,16 +194,16 @@
|
|||||||
"warning": "<strong>Attention</strong> : __message__",
|
"warning": "<strong>Attention</strong> : __message__",
|
||||||
"warnings": {
|
"warnings": {
|
||||||
"undeployedChanges": "Le noeud a des modifications non déployées",
|
"undeployedChanges": "Le noeud a des modifications non déployées",
|
||||||
"nodeActionDisabled": "Actions de noeud désactivées",
|
"nodeActionDisabled": "Les actions du noeud sont désactivées",
|
||||||
"nodeActionDisabledSubflow": "Actions de noeud désactivées dans le sous-flux",
|
"nodeActionDisabledSubflow": "Les actions de noeud sont désactivées à l'intérieur du sous-flux",
|
||||||
"missing-types": "<p>Flux arrêtés en raison de types de noeuds manquants.</p>",
|
"missing-types": "<p>Flux arrêtés en raison de types de noeuds manquants.</p>",
|
||||||
"missing-modules": "<p>Flux arrêtés en raison de modules manquants.</p>",
|
"missing-modules": "<p>Flux arrêtés en raison de modules manquants.</p>",
|
||||||
"safe-mode": "<p>Flux arrêtés en mode sans échec.</p><p>Vous pouvez modifier vos flux et déployer les changements pour redémarrer.</p>",
|
"safe-mode": "<p>Flux arrêtés en mode sans échec.</p><p>Vous pouvez modifier vos flux et déployer ensuite les changements afin de démarrer vos flux.</p>",
|
||||||
"restartRequired": "Node-RED doit être redémarré pour mettre à jour les modules",
|
"restartRequired": "Node-RED doit être redémarré pour mettre à jour les modules",
|
||||||
"credentials_load_failed": "<p>Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.</p><p>Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.</p>",
|
"credentials_load_failed": "<p>Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.</p><p>Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.</p>",
|
||||||
"credentials_load_failed_reset": "<p>Les informations d'identification n'ont pas pu être déchiffrées</p><p>Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.</p><p>Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification de flux existantes seront perdues.</p>",
|
"credentials_load_failed_reset": "<p>Les informations d'identification n'ont pas pu être déchiffrées</p><p>Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.</p><p>Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification des flux existants seront perdues.</p>",
|
||||||
"missing_flow_file": "<p>Fichier contenant les flux introuvable.</p><p>Le projet n'est pas configuré avec un fichier de flux.</p>",
|
"missing_flow_file": "<p>Fichier contenant les flux introuvable.</p><p>Le projet n'est pas configuré avec un fichier de flux.</p>",
|
||||||
"missing_package_file": "<p>Fichier de paquetage du projet introuvable.</p><p>Il manque au projet un fichier package.json.</p>",
|
"missing_package_file": "<p>Fichier de paquetage du projet introuvable.</p><p>Il manque au projet le fichier <code>package.json</code>.</p>",
|
||||||
"project_empty": "<p>Le projet est vide.</p><p>Voulez-vous créer un ensemble de fichiers de projet par défaut ?<br/>Sinon, vous devrez ajouter manuellement des fichiers au projet (en dehors de l'éditeur).</p>",
|
"project_empty": "<p>Le projet est vide.</p><p>Voulez-vous créer un ensemble de fichiers de projet par défaut ?<br/>Sinon, vous devrez ajouter manuellement des fichiers au projet (en dehors de l'éditeur).</p>",
|
||||||
"project_not_found": "<p>Le projet '__project__' est introuvable.</p>",
|
"project_not_found": "<p>Le projet '__project__' est introuvable.</p>",
|
||||||
"git_merge_conflict": "<p>La fusion automatique des modifications a échoué.</p><p>Corriger les conflits non fusionnés, puis valider le résultat.</p>"
|
"git_merge_conflict": "<p>La fusion automatique des modifications a échoué.</p><p>Corriger les conflits non fusionnés, puis valider le résultat.</p>"
|
||||||
@ -219,7 +220,7 @@
|
|||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"change-branch": "Changer pour une branche locale '__project__'",
|
"change-branch": "Changer pour une branche locale '__project__'",
|
||||||
"merge-abort": "Git fusion abandonnée",
|
"merge-abort": "Fusion Git abandonnée",
|
||||||
"loaded": "Projet '__project__' chargé",
|
"loaded": "Projet '__project__' chargé",
|
||||||
"updated": "Projet '__project__' mis à jour",
|
"updated": "Projet '__project__' mis à jour",
|
||||||
"pull": "Projet '__project__' rechargé",
|
"pull": "Projet '__project__' rechargé",
|
||||||
@ -352,7 +353,7 @@
|
|||||||
"backgroundUpdate": "Les flux sur le serveur ont été mis à jour.",
|
"backgroundUpdate": "Les flux sur le serveur ont été mis à jour.",
|
||||||
"conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement",
|
"conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement",
|
||||||
"conflictAutoMerge": "Les modifications n'incluent aucun conflit et peuvent être fusionnées automatiquement.",
|
"conflictAutoMerge": "Les modifications n'incluent aucun conflit et peuvent être fusionnées automatiquement.",
|
||||||
"conflictManualMerge": "Les changements incluent des conflits qui doivent être résolus avant de pouvoir être déployés.",
|
"conflictManualMerge": "Les modifications incluent des conflits qui doivent être résolus avant de pouvoir être déployées.",
|
||||||
"plusNMore": "+ __count__ en plus"
|
"plusNMore": "+ __count__ en plus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -372,19 +373,28 @@
|
|||||||
"deleted": "supprimé",
|
"deleted": "supprimé",
|
||||||
"flowDeleted": "flux supprimé",
|
"flowDeleted": "flux supprimé",
|
||||||
"flowAdded": "flux ajouté",
|
"flowAdded": "flux ajouté",
|
||||||
|
"moved": "déplacé",
|
||||||
"movedTo": "déplacé vers __id__",
|
"movedTo": "déplacé vers __id__",
|
||||||
"movedFrom": "déplacé depuis __id__"
|
"movedFrom": "déplacé depuis __id__",
|
||||||
|
"none": "aucun",
|
||||||
|
"position": "position",
|
||||||
|
"wires": "câbles"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ noeud",
|
"nodeCount": "__count__ noeud",
|
||||||
"nodeCount_plural": "__count__ noeuds",
|
"nodeCount_plural": "__count__ noeuds",
|
||||||
"local": "Changements locaux",
|
"local": "Changements locaux",
|
||||||
"remote": "Modifications à distance",
|
"remote": "Changements distants",
|
||||||
"reviewChanges": "Examiner les modifications",
|
"reviewChanges": "Examiner les modifications",
|
||||||
"noBinaryFileShowed": "Impossible d'afficher le contenu du fichier binaire",
|
"noBinaryFileShowed": "Impossible d'afficher le contenu du fichier binaire",
|
||||||
"viewCommitDiff": "Afficher les modifications de validation",
|
"viewCommitDiff": "Afficher les modifications de la validation",
|
||||||
|
"commit": "Validation",
|
||||||
"compareChanges": "Comparer les modifications",
|
"compareChanges": "Comparer les modifications",
|
||||||
"saveConflict": "Enregistrer la résolution des conflits",
|
"saveConflict": "Enregistrer la résolution des conflits",
|
||||||
"conflictHeader": "<span>__resolved__</span> sur <span>__unresolved__</span> conflit(s) résolu(s)",
|
"conflictHeader": "<span>__resolved__</span> sur <span>__unresolved__</span> conflit(s) résolu(s)",
|
||||||
|
"localChanges": "Modifications locales",
|
||||||
|
"remoteChanges": "Modifications distantes",
|
||||||
|
"useLocalChanges": "utiliser les modifications locales",
|
||||||
|
"useRemoteChanges": "utiliser les modifications distantes",
|
||||||
"commonVersionError": "La version commune ne contient pas de JSON valide :",
|
"commonVersionError": "La version commune ne contient pas de JSON valide :",
|
||||||
"oldVersionError": "L'ancienne version ne contient pas de JSON valide :",
|
"oldVersionError": "L'ancienne version ne contient pas de JSON valide :",
|
||||||
"newVersionError": "La nouvelle version ne contient pas de JSON valide :"
|
"newVersionError": "La nouvelle version ne contient pas de JSON valide :"
|
||||||
@ -395,9 +405,9 @@
|
|||||||
"edit": "Modifier le modèle du sous-flux",
|
"edit": "Modifier le modèle du sous-flux",
|
||||||
"subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux",
|
"subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux",
|
||||||
"subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux",
|
"subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux",
|
||||||
"editSubflowProperties": "modifier les propriétés",
|
"editSubflowProperties": "Modifier les propriétés",
|
||||||
"input": "Entrées:",
|
"input": "Entrées :",
|
||||||
"output": "Sorties:",
|
"output": "Sorties :",
|
||||||
"status": "Statut du noeud",
|
"status": "Statut du noeud",
|
||||||
"deleteSubflow": "Supprimer le sous-flux",
|
"deleteSubflow": "Supprimer le sous-flux",
|
||||||
"confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?",
|
"confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?",
|
||||||
@ -411,7 +421,7 @@
|
|||||||
"version": "Version",
|
"version": "Version",
|
||||||
"versionPlaceholder": "x.y.z",
|
"versionPlaceholder": "x.y.z",
|
||||||
"keys": "Mots clés",
|
"keys": "Mots clés",
|
||||||
"keysPlaceholder": "Mots clés séparés par des virgules",
|
"keysPlaceholder": "Mots clés séparés par une virgule",
|
||||||
"author": "Auteur",
|
"author": "Auteur",
|
||||||
"authorPlaceholder": "Votre nom <email@exemple.com>",
|
"authorPlaceholder": "Votre nom <email@exemple.com>",
|
||||||
"desc": "Description",
|
"desc": "Description",
|
||||||
@ -468,7 +478,7 @@
|
|||||||
"select": "sélection",
|
"select": "sélection",
|
||||||
"checkbox": "case à cocher",
|
"checkbox": "case à cocher",
|
||||||
"spinner": "valeurs à défiler",
|
"spinner": "valeurs à défiler",
|
||||||
"none": "aucune",
|
"none": "aucun",
|
||||||
"hidden": "masquer la propriété"
|
"hidden": "masquer la propriété"
|
||||||
},
|
},
|
||||||
"types": {
|
"types": {
|
||||||
@ -496,7 +506,7 @@
|
|||||||
"max": "Maximum"
|
"max": "Maximum"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"scopeChange": "La modification de la portée la rendra indisponible pour les noeuds d'autres flux qui l'utilisent",
|
"scopeChange": "La modification de la portée rendra indisponible ce noeud de configuration aux noeuds d'autres flux qui l'utilisent",
|
||||||
"invalidProperties": "Propriétés invalides :",
|
"invalidProperties": "Propriétés invalides :",
|
||||||
"credentialLoadFailed": "Échec du chargement des identifiants du noeud"
|
"credentialLoadFailed": "Échec du chargement des identifiants du noeud"
|
||||||
}
|
}
|
||||||
@ -510,7 +520,7 @@
|
|||||||
"unassigned": "Non attribué",
|
"unassigned": "Non attribué",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"workspace": "Espace de travail",
|
"workspace": "Espace de travail",
|
||||||
"editor": "Boîte de dialogue d'édition",
|
"editor": "Boîte d'édition",
|
||||||
"selectAll": "Tout sélectionner",
|
"selectAll": "Tout sélectionner",
|
||||||
"selectNone": "Ne rien sélectionner",
|
"selectNone": "Ne rien sélectionner",
|
||||||
"selectAllConnected": "Sélectionner tous les éléments connectés",
|
"selectAllConnected": "Sélectionner tous les éléments connectés",
|
||||||
@ -541,7 +551,7 @@
|
|||||||
"openLibrary": "Ouvrir la bibliothèque...",
|
"openLibrary": "Ouvrir la bibliothèque...",
|
||||||
"saveToLibrary": "Enregistrer dans la bibliothèque...",
|
"saveToLibrary": "Enregistrer dans la bibliothèque...",
|
||||||
"typeLibrary": "__type__ bibliothèque",
|
"typeLibrary": "__type__ bibliothèque",
|
||||||
"unnamedType": "Innomé __type__",
|
"unnamedType": "Sans nom __type__",
|
||||||
"exportedToLibrary": "Noeuds exportés vers la bibliothèque",
|
"exportedToLibrary": "Noeuds exportés vers la bibliothèque",
|
||||||
"dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?",
|
"dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?",
|
||||||
"invalidFilename": "Nom de fichier non valide",
|
"invalidFilename": "Nom de fichier non valide",
|
||||||
@ -552,13 +562,15 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"examples": "Exemples"
|
"examples": "Exemples"
|
||||||
}
|
},
|
||||||
|
"type": "Type",
|
||||||
|
"name": "Nom"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "Pas d'information disponible",
|
"noInfo": "Pas d'information disponible",
|
||||||
"filter": "Rechercher le noeud",
|
"filter": "Rechercher le noeud",
|
||||||
"search": "Rechercher les modules",
|
"search": "Rechercher les modules",
|
||||||
"addCategory": "Ajouter un nouveau...",
|
"addCategory": "Ajouter une nouvelle...",
|
||||||
"label": {
|
"label": {
|
||||||
"subflows": "Sous-flux",
|
"subflows": "Sous-flux",
|
||||||
"network": "Réseau",
|
"network": "Réseau",
|
||||||
@ -614,6 +626,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ noeud",
|
"nodeCount": "__label__ noeud",
|
||||||
"nodeCount_plural": "__label__ noeuds",
|
"nodeCount_plural": "__label__ noeuds",
|
||||||
|
"pluginCount": "__count__ plugin",
|
||||||
|
"pluginCount_plural": "__count__ plugins",
|
||||||
"moduleCount": "__count__ module disponible",
|
"moduleCount": "__count__ module disponible",
|
||||||
"moduleCount_plural": "__count__ modules disponibles",
|
"moduleCount_plural": "__count__ modules disponibles",
|
||||||
"inuse": "En cours d'utilisation",
|
"inuse": "En cours d'utilisation",
|
||||||
@ -636,11 +650,12 @@
|
|||||||
"sortAZ": "A-Z",
|
"sortAZ": "A-Z",
|
||||||
"sortRecent": "Récent",
|
"sortRecent": "Récent",
|
||||||
"more": "+ __count__ en plus",
|
"more": "+ __count__ en plus",
|
||||||
"upload": "Charger le fichier tgz du module",
|
"upload": "Charger le fichier .tgz du module",
|
||||||
"refresh": "Actualiser la liste des modules",
|
"refresh": "Actualiser la liste des modules",
|
||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>Échec du chargement du catalogue de noeuds.</p><p>Vérifier la console du navigateur pour plus d'informations</p>",
|
"catalogLoadFailed": "<p>Échec du chargement du catalogue de noeuds.</p><p>Vérifier la console du navigateur pour plus d'informations</p>",
|
||||||
"installFailed": "<p>Échec lors de l'installation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
"installFailed": "<p>Échec lors de l'installation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
||||||
|
"installTimeout": "<p>L'installation continue en arrière-plan.</p><p>Les noeuds apparaîtront dans la palette une fois l'installation terminée. Consulter le journal pour plus d'informations.</p>",
|
||||||
"removeFailed": "<p>Échec lors de la suppression : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
"removeFailed": "<p>Échec lors de la suppression : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
||||||
"updateFailed": "<p>Échec lors de la mise à jour : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
"updateFailed": "<p>Échec lors de la mise à jour : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
||||||
"enableFailed": "<p>Échec lors de l'activation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
"enableFailed": "<p>Échec lors de l'activation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
|
||||||
@ -648,25 +663,29 @@
|
|||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"install": {
|
"install": {
|
||||||
"body": "<p>Installation de '__module__'</p><p>Avant l'installation, veuiller lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.</p>",
|
"body": "<p>Installation de '__module__'</p><p>Avant l'installation, veuillez lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.</p>",
|
||||||
"title": "Installer les noeuds"
|
"title": "Installer les noeuds"
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"body": "<p>Suppression de '__module__'</p><p>La suppression du noeud le désinstallera de Node-RED. Le noeud peut continuer à utiliser des ressources jusqu'au redémarrage de Node-RED.</p>",
|
"body": "<p>Suppression de '__module__'</p><p>La suppression du noeud le désinstallera de Node-RED. Le noeud peut continuer à utiliser ses ressources jusqu'au redémarrage de Node-RED.</p>",
|
||||||
"title": "Supprimer les noeuds"
|
"title": "Supprimer les noeuds"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>Suppression du plugin '__module__'. Veuillez recharger l'éditeur afin d'appliquer les changements.</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body": "<p>Mise à jour de '__module__'</p><p>La mise à jour du noeud nécessitera un redémarrage de Node-RED pour terminer la mise à jour. Cela doit être fait manuellement.</p>",
|
"body": "<p>Mise à jour de '__module__'</p><p>La mise à jour du noeud nécessitera un redémarrage de Node-RED pour terminer la mise à jour. Cela doit être fait manuellement.</p>",
|
||||||
"title": "Mettre à jour les noeuds"
|
"title": "Mettre à jour les noeuds"
|
||||||
},
|
},
|
||||||
"cannotUpdate": {
|
"cannotUpdate": {
|
||||||
"body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.<br/><br/>Veuiller vous référer à la documentation pour savoir comment mettre à jour ce noeud."
|
"body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.<br/><br/>Veuillez vous référer à la documentation pour savoir comment mettre à jour ce noeud."
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"review": "Ouvrir la documentation",
|
"review": "Ouvrir la documentation",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
"remove": "Supprimer",
|
"remove": "Supprimer",
|
||||||
"update": "Mettre à jour"
|
"update": "Mettre à jour",
|
||||||
|
"understood": "Compris"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -701,8 +720,8 @@
|
|||||||
"nodeHelp": "Aide sur les noeuds",
|
"nodeHelp": "Aide sur les noeuds",
|
||||||
"none": "Aucun",
|
"none": "Aucun",
|
||||||
"arrayItems": "__count__ éléments",
|
"arrayItems": "__count__ éléments",
|
||||||
"showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres",
|
"showTips": "Vous pouvez afficher les astuces à partir du panneau des paramètres",
|
||||||
"outline": "Plan",
|
"outline": "Contour",
|
||||||
"empty": "Vide",
|
"empty": "Vide",
|
||||||
"globalConfig": "Noeuds de configuration globale",
|
"globalConfig": "Noeuds de configuration globale",
|
||||||
"triggerAction": "Déclencher une action",
|
"triggerAction": "Déclencher une action",
|
||||||
@ -715,7 +734,7 @@
|
|||||||
"help": {
|
"help": {
|
||||||
"name": "Aide",
|
"name": "Aide",
|
||||||
"label": "Aide",
|
"label": "Aide",
|
||||||
"search": "Aide à la recherche",
|
"search": "Rechercher l'aide",
|
||||||
"nodeHelp": "Aide sur les noeuds",
|
"nodeHelp": "Aide sur les noeuds",
|
||||||
"showHelp": "Afficher l'aide",
|
"showHelp": "Afficher l'aide",
|
||||||
"showInOutline": "Afficher dans les grandes lignes",
|
"showInOutline": "Afficher dans les grandes lignes",
|
||||||
@ -794,7 +813,8 @@
|
|||||||
"branches": "Branches",
|
"branches": "Branches",
|
||||||
"noBranches": "Pas de branche",
|
"noBranches": "Pas de branche",
|
||||||
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la branche locale '__name__' ? Ça ne peut pas être annulé.",
|
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la branche locale '__name__' ? Ça ne peut pas être annulé.",
|
||||||
"unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Etes-vous sûr de vouloir la supprimer?",
|
"deleteBranch": "Supprimer la branche",
|
||||||
|
"unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Êtes-vous sûr de vouloir la supprimer?",
|
||||||
"deleteUnmergedBranch": "Supprimer la branche non fusionnée",
|
"deleteUnmergedBranch": "Supprimer la branche non fusionnée",
|
||||||
"gitRemotes": "Git distant",
|
"gitRemotes": "Git distant",
|
||||||
"addRemote": "Ajout distant",
|
"addRemote": "Ajout distant",
|
||||||
@ -838,17 +858,17 @@
|
|||||||
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé."
|
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé."
|
||||||
},
|
},
|
||||||
"versionControl": {
|
"versionControl": {
|
||||||
"unstagedChanges": "Abandon des changements",
|
"unstagedChanges": "Changements non indexés",
|
||||||
"stagedChanges": "Changement mis en place",
|
"stagedChanges": "Changements indexés",
|
||||||
"unstageChange": "Ne pas mettre en place le changement",
|
"unstageChange": "Annuler l'indexation des changements",
|
||||||
"stageChange": "Mettre en place le changement",
|
"stageChange": "Indexer les changements",
|
||||||
"unstageAllChange": "Ne pas mettre en place tous les changements",
|
"unstageAllChange": "Annuler l'indexation de tous les changements",
|
||||||
"stageAllChange": "Mettre en place tous les changements",
|
"stageAllChange": "Indexer tous les changements",
|
||||||
"commitChanges": "Valider les changements",
|
"commitChanges": "Valider les changements",
|
||||||
"resolveConflicts": "Résoudre les conflits",
|
"resolveConflicts": "Résoudre les conflits",
|
||||||
"head": "En-tête",
|
"head": "En-tête",
|
||||||
"staged": "Mis en place",
|
"staged": "Indexé",
|
||||||
"unstaged": "Non mis en place",
|
"unstaged": "Non indexé",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"remote": "Distant",
|
"remote": "Distant",
|
||||||
"revert": "Voulez-vous vraiment annuler les modifications apportées à '__file__' ? Ça ne peut pas être annulé.",
|
"revert": "Voulez-vous vraiment annuler les modifications apportées à '__file__' ? Ça ne peut pas être annulé.",
|
||||||
@ -882,11 +902,11 @@
|
|||||||
"pushFailed": "L'envoi a échoué car la branche a des validations plus récentes. Tirer et fusionner d'abord, puis envoyer à nouveau.",
|
"pushFailed": "L'envoi a échoué car la branche a des validations plus récentes. Tirer et fusionner d'abord, puis envoyer à nouveau.",
|
||||||
"push": "Envoyer",
|
"push": "Envoyer",
|
||||||
"pull": "Tirer",
|
"pull": "Tirer",
|
||||||
"unablePull": "<p>Impossible d'extraire les modifications à distance ; vos modifications locales non mises en place seraient écrasées.</p><p>Valider vos modifications et réessayer.</p>",
|
"unablePull": "<p>Impossible d'extraire les modifications à distance; vos modifications locales non mises en place seraient écrasées.</p><p>Valider vos modifications et réessayer.</p>",
|
||||||
"showUnstagedChanges": "Afficher les modifications non mise en place",
|
"showUnstagedChanges": "Afficher les modifications non indexées",
|
||||||
"connectionFailed": "Impossible de se connecter au référentiel distant: ",
|
"connectionFailed": "Impossible de se connecter au référentiel distant: ",
|
||||||
"pullUnrelatedHistory": "<p>Le réferentiel distant a un historique de validations sans rapport.</p><p>Êtes-vous sûr de vouloir extraire les modifications dans votre référentiel local ?</p>",
|
"pullUnrelatedHistory": "<p>Le réferentiel distant a un historique de validations sans rapport.</p><p>Êtes-vous sûr de vouloir extraire les modifications dans votre référentiel local ?</p>",
|
||||||
"pullChanges": "Tirer les changements",
|
"pullChanges": "Tirer les changements distants",
|
||||||
"history": "Historique",
|
"history": "Historique",
|
||||||
"projectHistory": "Historique du projet",
|
"projectHistory": "Historique du projet",
|
||||||
"daysAgo": "il y a __count__ jour",
|
"daysAgo": "il y a __count__ jour",
|
||||||
@ -927,7 +947,14 @@
|
|||||||
"date": "horodatage",
|
"date": "horodatage",
|
||||||
"jsonata": "expression",
|
"jsonata": "expression",
|
||||||
"env": "variable d'environnement",
|
"env": "variable d'environnement",
|
||||||
"cred": "identifiant"
|
"cred": "identifiant",
|
||||||
|
"conf-types": "noeud de configuration"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "millisecondes depuis l'époque",
|
||||||
|
"object": "Objet de date JavaScript"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
@ -960,7 +987,7 @@
|
|||||||
"result": "Résultat",
|
"result": "Résultat",
|
||||||
"format": "Format",
|
"format": "Format",
|
||||||
"compatMode": "Mode de compatibilité activé",
|
"compatMode": "Mode de compatibilité activé",
|
||||||
"compatModeDesc": "<h3>Mode de compatibilité JSONata</h3><p> L'expression actuelle semble toujours faire référence à <code>msg</code> et sera donc évaluée en mode de compatibilité. Veuiller mettre à jour l'expression pour ne pas utiliser <code>msg</code> car ce mode sera supprimé à l'avenir.</p><p> Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet <code>msg</code>. Par exemple, <code>msg.payload</code> serait utilisé pour accéder à la charge utile.</p><p> Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement <code>charge utile</code>.</p>",
|
"compatModeDesc": "<h3>Mode de compatibilité JSONata</h3><p> L'expression actuelle semble toujours faire référence à <code>msg</code> et sera donc évaluée en mode de compatibilité. Veuillez mettre à jour l'expression pour ne pas utiliser <code>msg</code> car ce mode sera supprimé à l'avenir.</p><p> Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet <code>msg</code>. Par exemple, <code>msg.payload</code> serait utilisé pour accéder à la charge utile.</p><p> Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement <code>charge utile</code>.</p>",
|
||||||
"noMatch": "Aucun résultat correspondant",
|
"noMatch": "Aucun résultat correspondant",
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-expr": "Expression JSONata non valide :\n __message__",
|
"invalid-expr": "Expression JSONata non valide :\n __message__",
|
||||||
@ -983,7 +1010,7 @@
|
|||||||
},
|
},
|
||||||
"jsonEditor": {
|
"jsonEditor": {
|
||||||
"title": "Éditeur JSON",
|
"title": "Éditeur JSON",
|
||||||
"format": "Format JSON",
|
"format": "Formatter JSON",
|
||||||
"rawMode": "Modifier JSON",
|
"rawMode": "Modifier JSON",
|
||||||
"uiMode": "Afficher l'éditeur",
|
"uiMode": "Afficher l'éditeur",
|
||||||
"rawMode-readonly": "JSON",
|
"rawMode-readonly": "JSON",
|
||||||
@ -1002,7 +1029,7 @@
|
|||||||
"markdownEditor": {
|
"markdownEditor": {
|
||||||
"title": "Éditeur Markdown",
|
"title": "Éditeur Markdown",
|
||||||
"expand": "Développer",
|
"expand": "Développer",
|
||||||
"format": "Formaté avec Markdown",
|
"format": "Formatter avec Markdown",
|
||||||
"heading1": "Rubrique 1",
|
"heading1": "Rubrique 1",
|
||||||
"heading2": "Rubrique 2",
|
"heading2": "Rubrique 2",
|
||||||
"heading3": "Rubrique 3",
|
"heading3": "Rubrique 3",
|
||||||
@ -1076,7 +1103,7 @@
|
|||||||
"credential-key": "Clé de chiffrement des identifiants",
|
"credential-key": "Clé de chiffrement des identifiants",
|
||||||
"cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
|
"cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
|
||||||
"already-exists2": "Existe déjà",
|
"already-exists2": "Existe déjà",
|
||||||
"git-error": "Erreur git",
|
"git-error": "Erreur Git",
|
||||||
"connection-failed": "La connexion a échoué",
|
"connection-failed": "La connexion a échoué",
|
||||||
"not-git-repo": "Ce n'est pas un dépôt Git",
|
"not-git-repo": "Ce n'est pas un dépôt Git",
|
||||||
"repo-not-found": "Référentiel introuvable"
|
"repo-not-found": "Référentiel introuvable"
|
||||||
@ -1090,7 +1117,7 @@
|
|||||||
"credentials-file": "Fichier d'identifiants"
|
"credentials-file": "Fichier d'identifiants"
|
||||||
},
|
},
|
||||||
"encryption-config": {
|
"encryption-config": {
|
||||||
"setup": "Configuration du chiffrage de votre fichier d'informations d'identification",
|
"setup": "Configuration du chiffrement de votre fichier d'informations d'identification",
|
||||||
"desc0": "Votre fichier d'informations d'identification de flux peut être chiffré pour sécuriser son contenu.",
|
"desc0": "Votre fichier d'informations d'identification de flux peut être chiffré pour sécuriser son contenu.",
|
||||||
"desc1": "Si vous souhaitez stocker ces identifiants dans un référentiel Git public, vous devez les chiffrer en fournissant une phrase clé secrète.",
|
"desc1": "Si vous souhaitez stocker ces identifiants dans un référentiel Git public, vous devez les chiffrer en fournissant une phrase clé secrète.",
|
||||||
"desc2": "Votre fichier d'identifiants de flux n'est actuellement pas chiffré.",
|
"desc2": "Votre fichier d'identifiants de flux n'est actuellement pas chiffré.",
|
||||||
@ -1147,9 +1174,9 @@
|
|||||||
"add-ssh-key": "Ajouter une clé ssh",
|
"add-ssh-key": "Ajouter une clé ssh",
|
||||||
"credentials-encryption-key": "Clé de chiffrement des identifiants",
|
"credentials-encryption-key": "Clé de chiffrement des identifiants",
|
||||||
"already-exists-2": "Existe déjà",
|
"already-exists-2": "Existe déjà",
|
||||||
"git-error": "Erreur git",
|
"git-error": "Erreur Git",
|
||||||
"con-failed": "La connexion a échoué",
|
"con-failed": "La connexion a échoué",
|
||||||
"not-git": "Ce n'est pas un dépôt git",
|
"not-git": "Ce n'est pas un dépôt Git",
|
||||||
"no-resource": "Référentiel introuvable",
|
"no-resource": "Référentiel introuvable",
|
||||||
"cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
|
"cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
|
||||||
"unexpected_error": "Erreur inattendue",
|
"unexpected_error": "Erreur inattendue",
|
||||||
@ -1187,7 +1214,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"no-username-email": "Votre client Git n'est pas configuré avec un nom d'utilisateur/e-mail.",
|
"no-username-email": "Votre client Git n'est pas configuré avec un nom d'utilisateur/e-mail.",
|
||||||
"unexpected": "Une erreur inattendue est apparue",
|
"unexpected": "Une erreur inattendue est survenue",
|
||||||
"code": "Code"
|
"code": "Code"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1256,7 +1283,7 @@
|
|||||||
"list-modified-nodes": "Afficher les flux modifiés",
|
"list-modified-nodes": "Afficher les flux modifiés",
|
||||||
"list-hidden-flows": "Afficher les flux cachés",
|
"list-hidden-flows": "Afficher les flux cachés",
|
||||||
"list-flows": "Lister les flux",
|
"list-flows": "Lister les flux",
|
||||||
"list-subflows": "Liste les sous-flux",
|
"list-subflows": "Lister les sous-flux",
|
||||||
"go-to-previous-location": "Aller à l'emplacement précédent",
|
"go-to-previous-location": "Aller à l'emplacement précédent",
|
||||||
"go-to-next-location": "Aller à l'emplacement suivant",
|
"go-to-next-location": "Aller à l'emplacement suivant",
|
||||||
"copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers",
|
"copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers",
|
||||||
@ -1316,8 +1343,8 @@
|
|||||||
"align-selection-to-bottom": "Aligner la sélection vers le bas",
|
"align-selection-to-bottom": "Aligner la sélection vers le bas",
|
||||||
"align-selection-to-middle": "Aligner la sélection au centre verticalement",
|
"align-selection-to-middle": "Aligner la sélection au centre verticalement",
|
||||||
"align-selection-to-center": "Aligner la sélection au centre horizontalement",
|
"align-selection-to-center": "Aligner la sélection au centre horizontalement",
|
||||||
"distribute-selection-horizontally": "Distribuer la sélection horizontalement",
|
"distribute-selection-horizontally": "Répartir la sélection horizontalement",
|
||||||
"distribute-selection-vertical": "Distribuer la sélection verticalement",
|
"distribute-selection-vertical": "Répartir la sélection verticalement",
|
||||||
"wire-series-of-nodes": "Connecter les noeuds en série",
|
"wire-series-of-nodes": "Connecter les noeuds en série",
|
||||||
"wire-node-to-multiple": "Connecter les noeuds à plusieurs",
|
"wire-node-to-multiple": "Connecter les noeuds à plusieurs",
|
||||||
"wire-multiple-to-node": "Connecter plusieurs au noeud",
|
"wire-multiple-to-node": "Connecter plusieurs au noeud",
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"lock": "固定",
|
"lock": "固定",
|
||||||
"unlock": "固定を解除",
|
"unlock": "固定を解除",
|
||||||
"locked": "固定済み",
|
"locked": "固定済み",
|
||||||
"unlocked": "固定なし"
|
"unlocked": "固定なし",
|
||||||
|
"format": "形式"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "文字列",
|
"string": "文字列",
|
||||||
@ -281,8 +282,8 @@
|
|||||||
"selected": "選択したフロー",
|
"selected": "選択したフロー",
|
||||||
"current": "現在のタブ",
|
"current": "現在のタブ",
|
||||||
"all": "全てのタブ",
|
"all": "全てのタブ",
|
||||||
"compact": "インデントのないJSONフォーマット",
|
"compact": "インデントなし",
|
||||||
"formatted": "インデント付きのJSONフォーマット",
|
"formatted": "インデント付き",
|
||||||
"copy": "書き出し",
|
"copy": "書き出し",
|
||||||
"export": "ライブラリに書き出し",
|
"export": "ライブラリに書き出し",
|
||||||
"exportAs": "書き出し先",
|
"exportAs": "書き出し先",
|
||||||
@ -372,8 +373,12 @@
|
|||||||
"deleted": "削除",
|
"deleted": "削除",
|
||||||
"flowDeleted": "削除されたフロー",
|
"flowDeleted": "削除されたフロー",
|
||||||
"flowAdded": "追加されたフロー",
|
"flowAdded": "追加されたフロー",
|
||||||
|
"moved": "移動",
|
||||||
"movedTo": "__id__ へ移動",
|
"movedTo": "__id__ へ移動",
|
||||||
"movedFrom": "__id__ から移動"
|
"movedFrom": "__id__ から移動",
|
||||||
|
"none": "なし",
|
||||||
|
"position": "位置",
|
||||||
|
"wires": "ワイヤー"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ 個のノード",
|
"nodeCount": "__count__ 個のノード",
|
||||||
"nodeCount_plural": "__count__ 個のノード",
|
"nodeCount_plural": "__count__ 個のノード",
|
||||||
@ -382,9 +387,14 @@
|
|||||||
"reviewChanges": "変更を表示",
|
"reviewChanges": "変更を表示",
|
||||||
"noBinaryFileShowed": "バイナリファイルの中身は表示することができません",
|
"noBinaryFileShowed": "バイナリファイルの中身は表示することができません",
|
||||||
"viewCommitDiff": "コミットの内容を表示",
|
"viewCommitDiff": "コミットの内容を表示",
|
||||||
|
"commit": "コミット",
|
||||||
"compareChanges": "変更を比較",
|
"compareChanges": "変更を比較",
|
||||||
"saveConflict": "解決して保存",
|
"saveConflict": "解決して保存",
|
||||||
"conflictHeader": "<span>__unresolved__</span> 個中 <span>__resolved__</span> 個のコンフリクトを解決",
|
"conflictHeader": "<span>__unresolved__</span> 個中 <span>__resolved__</span> 個のコンフリクトを解決",
|
||||||
|
"localChanges": "ローカルの変更",
|
||||||
|
"remoteChanges": "リモートの変更",
|
||||||
|
"useLocalChanges": "ローカルの変更を使用",
|
||||||
|
"useRemoteChanges": "リモートの変更を使用",
|
||||||
"commonVersionError": "共通バージョンは正しいJSON形式ではありません:",
|
"commonVersionError": "共通バージョンは正しいJSON形式ではありません:",
|
||||||
"oldVersionError": "古いバージョンは正しいJSON形式ではありません:",
|
"oldVersionError": "古いバージョンは正しいJSON形式ではありません:",
|
||||||
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
|
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
|
||||||
@ -552,7 +562,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "ローカル",
|
"local": "ローカル",
|
||||||
"examples": "サンプル"
|
"examples": "サンプル"
|
||||||
}
|
},
|
||||||
|
"type": "型",
|
||||||
|
"name": "名前"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "情報がありません",
|
"noInfo": "情報がありません",
|
||||||
@ -614,6 +626,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ 個のノード",
|
"nodeCount": "__label__ 個のノード",
|
||||||
"nodeCount_plural": "__label__ 個のノード",
|
"nodeCount_plural": "__label__ 個のノード",
|
||||||
|
"pluginCount": "__count__ 個のプラグイン",
|
||||||
|
"pluginCount_plural": "__count__ 個のプラグイン",
|
||||||
"moduleCount": "__count__ 個のモジュール",
|
"moduleCount": "__count__ 個のモジュール",
|
||||||
"moduleCount_plural": "__count__ 個のモジュール",
|
"moduleCount_plural": "__count__ 個のモジュール",
|
||||||
"inuse": "使用中",
|
"inuse": "使用中",
|
||||||
@ -641,6 +655,7 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
|
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
|
||||||
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
|
"installTimeout": "<p>バックグラウンドでインストールが継続されます。</p><p>完了した時にノードが表示されます。詳細はログを確認してください。</p>",
|
||||||
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
@ -655,6 +670,9 @@
|
|||||||
"body": "<p>__module__ を削除します。</p><p>Node-REDからノードを削除します。ノードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>",
|
"body": "<p>__module__ を削除します。</p><p>Node-REDからノードを削除します。ノードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>",
|
||||||
"title": "ノードを削除"
|
"title": "ノードを削除"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>プラグイン __module__ を削除しました。ブラウザを再読み込みして残った表示を消してください。</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>",
|
"body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>",
|
||||||
"title": "ノードの更新"
|
"title": "ノードの更新"
|
||||||
@ -666,7 +684,8 @@
|
|||||||
"review": "ノードの情報を参照",
|
"review": "ノードの情報を参照",
|
||||||
"install": "追加",
|
"install": "追加",
|
||||||
"remove": "削除",
|
"remove": "削除",
|
||||||
"update": "更新"
|
"update": "更新",
|
||||||
|
"understood": "了解"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -794,6 +813,7 @@
|
|||||||
"branches": "ブランチ",
|
"branches": "ブランチ",
|
||||||
"noBranches": "ブランチなし",
|
"noBranches": "ブランチなし",
|
||||||
"deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
|
"deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
|
||||||
|
"deleteBranch": "ブランチを削除",
|
||||||
"unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
|
"unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
|
||||||
"deleteUnmergedBranch": "マージされていないブランチを削除",
|
"deleteUnmergedBranch": "マージされていないブランチを削除",
|
||||||
"gitRemotes": "Gitリモート",
|
"gitRemotes": "Gitリモート",
|
||||||
@ -915,6 +935,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedInput": {
|
"typedInput": {
|
||||||
|
"selected": "__count__個を選択",
|
||||||
|
"selected_plural": "__count__個を選択",
|
||||||
"type": {
|
"type": {
|
||||||
"str": "文字列",
|
"str": "文字列",
|
||||||
"num": "数値",
|
"num": "数値",
|
||||||
@ -925,7 +947,14 @@
|
|||||||
"date": "日時",
|
"date": "日時",
|
||||||
"jsonata": "JSONata式",
|
"jsonata": "JSONata式",
|
||||||
"env": "環境変数",
|
"env": "環境変数",
|
||||||
"cred": "認証情報"
|
"cred": "認証情報",
|
||||||
|
"conf-types": "設定ノード"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "エポックからの経過ミリ秒",
|
||||||
|
"object": "JavaScript日付オブジェクト"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
@ -1231,7 +1260,7 @@
|
|||||||
},
|
},
|
||||||
"env-var": {
|
"env-var": {
|
||||||
"environment": "環境変数",
|
"environment": "環境変数",
|
||||||
"header": "大域環境変数",
|
"header": "グローバル環境変数",
|
||||||
"revert": "破棄"
|
"revert": "破棄"
|
||||||
},
|
},
|
||||||
"action-list": {
|
"action-list": {
|
||||||
@ -1383,7 +1412,7 @@
|
|||||||
"copy-item-edit-url": "要素の編集URLをコピー",
|
"copy-item-edit-url": "要素の編集URLをコピー",
|
||||||
"move-flow-to-start": "フローを先頭に移動",
|
"move-flow-to-start": "フローを先頭に移動",
|
||||||
"move-flow-to-end": "フローを末尾に移動",
|
"move-flow-to-end": "フローを末尾に移動",
|
||||||
"show-global-env": "大域環境変数を表示",
|
"show-global-env": "グローバル環境変数を表示",
|
||||||
"lock-flow": "フローを固定",
|
"lock-flow": "フローを固定",
|
||||||
"unlock-flow": "フローの固定を解除",
|
"unlock-flow": "フローの固定を解除",
|
||||||
"show-node-help": "ノードのヘルプを表示"
|
"show-node-help": "ノードのヘルプを表示"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-client",
|
"name": "@node-red/editor-client",
|
||||||
"version": "3.1.11",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -26,6 +26,15 @@ RED.comms = (function() {
|
|||||||
var reconnectAttempts = 0;
|
var reconnectAttempts = 0;
|
||||||
var active = false;
|
var active = false;
|
||||||
|
|
||||||
|
RED.events.on('login', function(username) {
|
||||||
|
// User has logged in
|
||||||
|
// Need to upgrade the connection to be authenticated
|
||||||
|
if (ws && ws.readyState == 1) {
|
||||||
|
const auth_tokens = RED.settings.get("auth-tokens");
|
||||||
|
ws.send(JSON.stringify({auth:auth_tokens.access_token}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function connectWS() {
|
function connectWS() {
|
||||||
active = true;
|
active = true;
|
||||||
var wspath;
|
var wspath;
|
||||||
@ -56,6 +65,7 @@ RED.comms = (function() {
|
|||||||
ws.send(JSON.stringify({subscribe:t}));
|
ws.send(JSON.stringify({subscribe:t}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emit('connect')
|
||||||
}
|
}
|
||||||
|
|
||||||
ws = new WebSocket(wspath);
|
ws = new WebSocket(wspath);
|
||||||
@ -180,9 +190,53 @@ RED.comms = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function send(topic, msg) {
|
||||||
|
if (ws && ws.readyState == 1) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
topic,
|
||||||
|
data: msg
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventHandlers = {};
|
||||||
|
function on(evt,func) {
|
||||||
|
eventHandlers[evt] = eventHandlers[evt]||[];
|
||||||
|
eventHandlers[evt].push(func);
|
||||||
|
}
|
||||||
|
function off(evt,func) {
|
||||||
|
const handler = eventHandlers[evt];
|
||||||
|
if (handler) {
|
||||||
|
for (let i=0;i<handler.length;i++) {
|
||||||
|
if (handler[i] === func) {
|
||||||
|
handler.splice(i,1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function emit() {
|
||||||
|
const evt = arguments[0]
|
||||||
|
const args = Array.prototype.slice.call(arguments,1);
|
||||||
|
if (eventHandlers[evt]) {
|
||||||
|
let cpyHandlers = [...eventHandlers[evt]];
|
||||||
|
for (let i=0;i<cpyHandlers.length;i++) {
|
||||||
|
try {
|
||||||
|
cpyHandlers[i].apply(null, args);
|
||||||
|
} catch(err) {
|
||||||
|
console.warn("RED.comms.emit error: ["+evt+"] "+(err.toString()));
|
||||||
|
console.warn(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connect: connectWS,
|
connect: connectWS,
|
||||||
subscribe: subscribe,
|
subscribe: subscribe,
|
||||||
unsubscribe:unsubscribe
|
unsubscribe:unsubscribe,
|
||||||
|
on,
|
||||||
|
off,
|
||||||
|
send
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -29,7 +29,14 @@ RED.history = (function() {
|
|||||||
}
|
}
|
||||||
return RED.nodes.junction(id);
|
return RED.nodes.junction(id);
|
||||||
}
|
}
|
||||||
|
function ensureUnlocked(id, flowsToLock) {
|
||||||
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
|
const isLocked = flow ? flow.locked : false;
|
||||||
|
if (flow && isLocked) {
|
||||||
|
flow.locked = false;
|
||||||
|
flowsToLock.add(flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
function undoEvent(ev) {
|
function undoEvent(ev) {
|
||||||
var i;
|
var i;
|
||||||
var len;
|
var len;
|
||||||
@ -59,18 +66,46 @@ RED.history = (function() {
|
|||||||
t: 'replace',
|
t: 'replace',
|
||||||
config: RED.nodes.createCompleteNodeSet(),
|
config: RED.nodes.createCompleteNodeSet(),
|
||||||
changed: {},
|
changed: {},
|
||||||
rev: RED.nodes.version()
|
moved: {},
|
||||||
|
complete: true,
|
||||||
|
rev: RED.nodes.version(),
|
||||||
|
dirty: RED.nodes.dirty()
|
||||||
};
|
};
|
||||||
|
var selectedTab = RED.workspaces.active();
|
||||||
|
inverseEv.config.forEach(n => {
|
||||||
|
const node = RED.nodes.node(n.id)
|
||||||
|
if (node) {
|
||||||
|
inverseEv.changed[n.id] = node.changed
|
||||||
|
inverseEv.moved[n.id] = node.moved
|
||||||
|
}
|
||||||
|
})
|
||||||
RED.nodes.clear();
|
RED.nodes.clear();
|
||||||
var imported = RED.nodes.import(ev.config);
|
var imported = RED.nodes.import(ev.config);
|
||||||
|
// Clear all change flags from the import
|
||||||
|
RED.nodes.dirty(false);
|
||||||
|
|
||||||
|
const flowsToLock = new Set()
|
||||||
|
|
||||||
imported.nodes.forEach(function(n) {
|
imported.nodes.forEach(function(n) {
|
||||||
if (ev.changed[n.id]) {
|
if (ev.changed[n.id]) {
|
||||||
|
ensureUnlocked(n.z, flowsToLock)
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
inverseEv.changed[n.id] = true;
|
|
||||||
}
|
}
|
||||||
|
if (ev.moved[n.id]) {
|
||||||
|
ensureUnlocked(n.z, flowsToLock)
|
||||||
|
n.moved = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
flowsToLock.forEach(flow => {
|
||||||
|
flow.locked = true
|
||||||
})
|
})
|
||||||
|
|
||||||
RED.nodes.version(ev.rev);
|
RED.nodes.version(ev.rev);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
RED.palette.refresh();
|
||||||
|
RED.workspaces.refresh();
|
||||||
|
RED.workspaces.show(selectedTab, true);
|
||||||
|
RED.sidebar.config.refresh();
|
||||||
} else {
|
} else {
|
||||||
var importMap = {};
|
var importMap = {};
|
||||||
ev.config.forEach(function(n) {
|
ev.config.forEach(function(n) {
|
||||||
@ -418,10 +453,61 @@ RED.history = (function() {
|
|||||||
RED.events.emit("nodes:change",newConfigNode);
|
RED.events.emit("nodes:change",newConfigNode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (i === "env" && ev.node.type.indexOf("subflow:") === 0) {
|
||||||
|
// Subflow can have config node in node.env
|
||||||
|
let nodeList = ev.node.env || [];
|
||||||
|
nodeList = nodeList.reduce((list, prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
list.push(prop.value);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
nodeList.forEach(function(id) {
|
||||||
|
const configNode = RED.nodes.node(id);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(ev.node) !== -1) {
|
||||||
|
configNode.users.splice(configNode.users.indexOf(ev.node), 1);
|
||||||
|
RED.events.emit("nodes:change", configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeList = ev.changes.env || [];
|
||||||
|
nodeList = nodeList.reduce((list, prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
list.push(prop.value);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
nodeList.forEach(function(id) {
|
||||||
|
const configNode = RED.nodes.node(id);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(ev.node) === -1) {
|
||||||
|
configNode.users.push(ev.node);
|
||||||
|
RED.events.emit("nodes:change", configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (i === "credentials" && ev.changes[i]) {
|
||||||
|
// Reset - Only want to keep the changes
|
||||||
|
inverseEv.changes[i] = {};
|
||||||
|
for (const [key, value] of Object.entries(ev.changes[i])) {
|
||||||
|
// Edge case: node.credentials is cleared after a deploy, so we can't
|
||||||
|
// capture values for the inverse event when undoing past a deploy
|
||||||
|
if (ev.node.credentials) {
|
||||||
|
inverseEv.changes[i][key] = ev.node.credentials[key];
|
||||||
|
}
|
||||||
|
ev.node.credentials[key] = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ev.node[i] = ev.changes[i];
|
||||||
}
|
}
|
||||||
ev.node[i] = ev.changes[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.node.dirty = true;
|
ev.node.dirty = true;
|
||||||
ev.node.changed = ev.changed;
|
ev.node.changed = ev.changed;
|
||||||
|
|
||||||
@ -501,6 +587,24 @@ RED.history = (function() {
|
|||||||
RED.editor.updateNodeProperties(ev.node,outputMap);
|
RED.editor.updateNodeProperties(ev.node,outputMap);
|
||||||
RED.editor.validateNode(ev.node);
|
RED.editor.validateNode(ev.node);
|
||||||
}
|
}
|
||||||
|
// If it's a Config Node, validate user nodes too.
|
||||||
|
// NOTE: The Config Node must be validated before validating users.
|
||||||
|
if (ev.node.users) {
|
||||||
|
const validatedNodes = new Set();
|
||||||
|
const userStack = ev.node.users.slice();
|
||||||
|
|
||||||
|
validatedNodes.add(ev.node.id);
|
||||||
|
while (userStack.length) {
|
||||||
|
const node = userStack.pop();
|
||||||
|
if (!validatedNodes.has(node.id)) {
|
||||||
|
validatedNodes.add(node.id);
|
||||||
|
if (node.users) {
|
||||||
|
userStack.push(...node.users);
|
||||||
|
}
|
||||||
|
RED.editor.validateNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ev.links) {
|
if (ev.links) {
|
||||||
inverseEv.createdLinks = [];
|
inverseEv.createdLinks = [];
|
||||||
for (i=0;i<ev.links.length;i++) {
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
560
packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
vendored
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
RED.multiplayer = (function () {
|
||||||
|
|
||||||
|
// activeSessionId - used to identify sessions across websocket reconnects
|
||||||
|
let activeSessionId
|
||||||
|
|
||||||
|
let headerWidget
|
||||||
|
// Map of session id to { session:'', user:{}, location:{}}
|
||||||
|
let sessions = {}
|
||||||
|
// Map of username to { user:{}, sessions:[] }
|
||||||
|
let users = {}
|
||||||
|
|
||||||
|
function addUserSession (session) {
|
||||||
|
if (sessions[session.session]) {
|
||||||
|
// This is an existing connection that has been authenticated
|
||||||
|
const existingSession = sessions[session.session]
|
||||||
|
if (existingSession.user.username !== session.user.username) {
|
||||||
|
removeUserHeaderButton(users[existingSession.user.username])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessions[session.session] = session
|
||||||
|
const user = users[session.user.username] = users[session.user.username] || {
|
||||||
|
user: session.user,
|
||||||
|
sessions: []
|
||||||
|
}
|
||||||
|
if (session.user.profileColor === undefined) {
|
||||||
|
session.user.profileColor = (1 + Math.floor(Math.random() * 5))
|
||||||
|
}
|
||||||
|
session.location = session.location || {}
|
||||||
|
user.sessions.push(session)
|
||||||
|
|
||||||
|
if (session.session === activeSessionId) {
|
||||||
|
// This is the current user session - do not add a extra button for them
|
||||||
|
} else {
|
||||||
|
if (user.sessions.length === 1) {
|
||||||
|
if (user.button) {
|
||||||
|
clearTimeout(user.inactiveTimeout)
|
||||||
|
clearTimeout(user.removeTimeout)
|
||||||
|
user.button.removeClass('inactive')
|
||||||
|
} else {
|
||||||
|
addUserHeaderButton(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessions[session.session].location = session.location
|
||||||
|
updateUserLocation(session.session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserSession (sessionId, isDisconnected) {
|
||||||
|
removeUserLocation(sessionId)
|
||||||
|
const session = sessions[sessionId]
|
||||||
|
delete sessions[sessionId]
|
||||||
|
const user = users[session.user.username]
|
||||||
|
const i = user.sessions.indexOf(session)
|
||||||
|
user.sessions.splice(i, 1)
|
||||||
|
if (isDisconnected) {
|
||||||
|
removeUserHeaderButton(user)
|
||||||
|
} else {
|
||||||
|
if (user.sessions.length === 0) {
|
||||||
|
// Give the user 5s to reconnect before marking inactive
|
||||||
|
user.inactiveTimeout = setTimeout(() => {
|
||||||
|
user.button.addClass('inactive')
|
||||||
|
// Give the user further 20 seconds to reconnect before removing them
|
||||||
|
// from the user toolbar entirely
|
||||||
|
user.removeTimeout = setTimeout(() => {
|
||||||
|
removeUserHeaderButton(user)
|
||||||
|
}, 20000)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserHeaderButton (user) {
|
||||||
|
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon"></button></li>')
|
||||||
|
.attr('data-username', user.user.username)
|
||||||
|
.prependTo("#red-ui-multiplayer-user-list");
|
||||||
|
var button = user.button.find("button")
|
||||||
|
RED.popover.tooltip(button, user.user.username)
|
||||||
|
button.on('click', function () {
|
||||||
|
const location = user.sessions[0].location
|
||||||
|
revealUser(location)
|
||||||
|
})
|
||||||
|
|
||||||
|
const userProfile = RED.user.generateUserIcon(user.user)
|
||||||
|
userProfile.appendTo(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserHeaderButton (user) {
|
||||||
|
user.button.remove()
|
||||||
|
delete user.button
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocation () {
|
||||||
|
const location = {
|
||||||
|
workspace: RED.workspaces.active()
|
||||||
|
}
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
for (let i = editStack.length - 1; i >= 0; i--) {
|
||||||
|
if (editStack[i].id) {
|
||||||
|
location.node = editStack[i].id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInWorkspace) {
|
||||||
|
const chart = $('#red-ui-workspace-chart')
|
||||||
|
const chartOffset = chart.offset()
|
||||||
|
const scaleFactor = RED.view.scale()
|
||||||
|
location.cursor = {
|
||||||
|
x: (lastPosition[0] - chartOffset.left + chart.scrollLeft()) / scaleFactor,
|
||||||
|
y: (lastPosition[1] - chartOffset.top + chart.scrollTop()) / scaleFactor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
|
||||||
|
let publishLocationTimeout
|
||||||
|
let lastPosition = [0,0]
|
||||||
|
let isInWorkspace = false
|
||||||
|
|
||||||
|
function publishLocation () {
|
||||||
|
if (!publishLocationTimeout) {
|
||||||
|
publishLocationTimeout = setTimeout(() => {
|
||||||
|
const location = getLocation()
|
||||||
|
if (location.workspace !== 0) {
|
||||||
|
log('send', 'multiplayer/location', location)
|
||||||
|
RED.comms.send('multiplayer/location', location)
|
||||||
|
}
|
||||||
|
publishLocationTimeout = null
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function revealUser(location, skipWorkspace) {
|
||||||
|
if (location.node) {
|
||||||
|
// Need to check if this is a known node, so we can fall back to revealing
|
||||||
|
// the workspace instead
|
||||||
|
const node = RED.nodes.node(location.node)
|
||||||
|
if (node) {
|
||||||
|
RED.view.reveal(location.node)
|
||||||
|
} else if (!skipWorkspace && location.workspace) {
|
||||||
|
RED.view.reveal(location.workspace)
|
||||||
|
}
|
||||||
|
} else if (!skipWorkspace && location.workspace) {
|
||||||
|
RED.view.reveal(location.workspace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceTrays = {}
|
||||||
|
function getWorkspaceTray(workspaceId) {
|
||||||
|
// console.log('get tray for',workspaceId)
|
||||||
|
if (!workspaceTrays[workspaceId]) {
|
||||||
|
const tray = $('<div class="red-ui-multiplayer-users-tray"></div>')
|
||||||
|
const users = []
|
||||||
|
const userIcons = {}
|
||||||
|
|
||||||
|
const userCountIcon = $(`<div class="red-ui-multiplayer-user-location"><span class="red-ui-user-profile red-ui-multiplayer-user-count"><span></span></span></div>`)
|
||||||
|
const userCountSpan = userCountIcon.find('span span')
|
||||||
|
userCountIcon.hide()
|
||||||
|
userCountSpan.text('')
|
||||||
|
userCountIcon.appendTo(tray)
|
||||||
|
const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
|
||||||
|
const content = $('<div>')
|
||||||
|
users.forEach(sessionId => {
|
||||||
|
$('<div>').append($('<a href="#">').text(sessions[sessionId].user.username).on('click', function (evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
revealUser(sessions[sessionId].location, true)
|
||||||
|
userCountTooltip.close()
|
||||||
|
})).appendTo(content)
|
||||||
|
})
|
||||||
|
return content
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateUserCount = function () {
|
||||||
|
const maxShown = 2
|
||||||
|
const children = tray.children()
|
||||||
|
children.each(function (index, element) {
|
||||||
|
const i = users.length - index
|
||||||
|
if (i > maxShown) {
|
||||||
|
$(this).hide()
|
||||||
|
} else if (i >= 0) {
|
||||||
|
$(this).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (users.length < maxShown + 1) {
|
||||||
|
userCountIcon.hide()
|
||||||
|
} else {
|
||||||
|
userCountSpan.text('+'+(users.length - maxShown))
|
||||||
|
userCountIcon.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workspaceTrays[workspaceId] = {
|
||||||
|
attached: false,
|
||||||
|
tray,
|
||||||
|
users,
|
||||||
|
userIcons,
|
||||||
|
addUser: function (sessionId) {
|
||||||
|
if (users.indexOf(sessionId) === -1) {
|
||||||
|
// console.log(`addUser ws:${workspaceId} session:${sessionId}`)
|
||||||
|
users.push(sessionId)
|
||||||
|
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||||
|
const userLocationIcon = $(`<div class="red-ui-multiplayer-user-location" id="${userLocationId}"></div>`)
|
||||||
|
RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
|
||||||
|
userLocationIcon.prependTo(tray)
|
||||||
|
RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
|
||||||
|
userIcons[sessionId] = userLocationIcon
|
||||||
|
updateUserCount()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeUser: function (sessionId) {
|
||||||
|
// console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
|
||||||
|
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||||
|
const index = users.indexOf(sessionId)
|
||||||
|
if (index > -1) {
|
||||||
|
users.splice(index, 1)
|
||||||
|
userIcons[sessionId].remove()
|
||||||
|
delete userIcons[sessionId]
|
||||||
|
}
|
||||||
|
updateUserCount()
|
||||||
|
},
|
||||||
|
updateUserCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const trayDef = workspaceTrays[workspaceId]
|
||||||
|
if (!trayDef.attached) {
|
||||||
|
const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
|
||||||
|
if (workspaceTab.length > 0) {
|
||||||
|
trayDef.attached = true
|
||||||
|
trayDef.tray.appendTo(workspaceTab)
|
||||||
|
trayDef.users.forEach(sessionId => {
|
||||||
|
trayDef.userIcons[sessionId].on('click', function (evt) {
|
||||||
|
revealUser(sessions[sessionId].location, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return workspaceTrays[workspaceId]
|
||||||
|
}
|
||||||
|
function attachWorkspaceTrays () {
|
||||||
|
let viewTouched = false
|
||||||
|
for (let sessionId of Object.keys(sessions)) {
|
||||||
|
const location = sessions[sessionId].location
|
||||||
|
if (location) {
|
||||||
|
if (location.workspace) {
|
||||||
|
getWorkspaceTray(location.workspace).updateUserCount()
|
||||||
|
}
|
||||||
|
if (location.node) {
|
||||||
|
addUserToNode(sessionId, location.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (viewTouched) {
|
||||||
|
RED.view.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserToNode(sessionId, nodeId) {
|
||||||
|
const node = RED.nodes.node(nodeId)
|
||||||
|
if (node) {
|
||||||
|
if (!node._multiplayer) {
|
||||||
|
node._multiplayer = {
|
||||||
|
users: [sessionId]
|
||||||
|
}
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
} else {
|
||||||
|
if (node._multiplayer.users.indexOf(sessionId) === -1) {
|
||||||
|
node._multiplayer.users.push(sessionId)
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removeUserFromNode(sessionId, nodeId) {
|
||||||
|
const node = RED.nodes.node(nodeId)
|
||||||
|
if (node && node._multiplayer) {
|
||||||
|
const i = node._multiplayer.users.indexOf(sessionId)
|
||||||
|
if (i > -1) {
|
||||||
|
node._multiplayer.users.splice(i, 1)
|
||||||
|
}
|
||||||
|
if (node._multiplayer.users.length === 0) {
|
||||||
|
delete node._multiplayer
|
||||||
|
} else {
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserLocation (sessionId) {
|
||||||
|
updateUserLocation(sessionId, {})
|
||||||
|
removeUserCursor(sessionId)
|
||||||
|
}
|
||||||
|
function removeUserCursor (sessionId) {
|
||||||
|
// return
|
||||||
|
if (sessions[sessionId]?.cursor) {
|
||||||
|
sessions[sessionId].cursor.parentNode.removeChild(sessions[sessionId].cursor)
|
||||||
|
delete sessions[sessionId].cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUserLocation (sessionId, location) {
|
||||||
|
let viewTouched = false
|
||||||
|
const oldLocation = sessions[sessionId].location
|
||||||
|
if (location) {
|
||||||
|
if (oldLocation.workspace !== location.workspace) {
|
||||||
|
// console.log('removing', sessionId, oldLocation.workspace)
|
||||||
|
workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
|
||||||
|
}
|
||||||
|
if (oldLocation.node !== location.node) {
|
||||||
|
removeUserFromNode(sessionId, oldLocation.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
sessions[sessionId].location = location
|
||||||
|
} else {
|
||||||
|
location = sessions[sessionId].location
|
||||||
|
}
|
||||||
|
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
|
||||||
|
if (location.workspace) {
|
||||||
|
getWorkspaceTray(location.workspace).addUser(sessionId)
|
||||||
|
if (location.cursor && location.workspace === RED.workspaces.active()) {
|
||||||
|
if (!sessions[sessionId].cursor) {
|
||||||
|
const user = sessions[sessionId].user
|
||||||
|
const cursorIcon = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
cursorIcon.setAttribute("class", "red-ui-multiplayer-annotation")
|
||||||
|
cursorIcon.appendChild(createAnnotationUser(user, true))
|
||||||
|
$(cursorIcon).css({
|
||||||
|
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`,
|
||||||
|
transition: 'transform 0.1s linear'
|
||||||
|
})
|
||||||
|
$("#red-ui-workspace-chart svg").append(cursorIcon)
|
||||||
|
sessions[sessionId].cursor = cursorIcon
|
||||||
|
} else {
|
||||||
|
const cursorIcon = sessions[sessionId].cursor
|
||||||
|
$(cursorIcon).css({
|
||||||
|
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (sessions[sessionId].cursor) {
|
||||||
|
removeUserCursor(sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (location.node) {
|
||||||
|
addUserToNode(sessionId, location.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
if (viewTouched) {
|
||||||
|
RED.view.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function refreshUserLocations () {
|
||||||
|
// for (const session of Object.keys(sessions)) {
|
||||||
|
// if (session !== activeSessionId) {
|
||||||
|
// updateUserLocation(session)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
function createAnnotationUser(user, pointer = false) {
|
||||||
|
const radius = 20
|
||||||
|
const halfRadius = radius/2
|
||||||
|
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
const badge = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
let shapePath
|
||||||
|
if (!pointer) {
|
||||||
|
shapePath = `M 0 ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 ${radius} 0 a ${halfRadius} ${halfRadius} 0 1 1 -${radius} 0 z`
|
||||||
|
} else {
|
||||||
|
shapePath = `M 0 0 h ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 -${halfRadius} ${halfRadius} z`
|
||||||
|
}
|
||||||
|
badge.setAttribute('d', shapePath)
|
||||||
|
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
|
||||||
|
group.appendChild(badge)
|
||||||
|
if (user && user.profileColor !== undefined) {
|
||||||
|
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
|
||||||
|
}
|
||||||
|
if (user && user.image) {
|
||||||
|
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
|
||||||
|
image.setAttribute("width", radius)
|
||||||
|
image.setAttribute("height", radius)
|
||||||
|
image.setAttribute("href", user.image)
|
||||||
|
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
|
||||||
|
group.appendChild(image)
|
||||||
|
} else if (user && user.anonymous) {
|
||||||
|
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||||
|
anonIconHead.setAttribute("cx", radius/2)
|
||||||
|
anonIconHead.setAttribute("cy", radius/2 - 2)
|
||||||
|
anonIconHead.setAttribute("r", 2.4)
|
||||||
|
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||||
|
group.appendChild(anonIconHead)
|
||||||
|
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||||
|
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
|
||||||
|
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
||||||
|
group.appendChild(anonIconBody)
|
||||||
|
} else {
|
||||||
|
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||||
|
if (user.username || user.email) {
|
||||||
|
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
||||||
|
label.textContent = (user.username || user.email).substring(0,2)
|
||||||
|
} else {
|
||||||
|
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
||||||
|
label.textContent = 'nr'
|
||||||
|
}
|
||||||
|
label.setAttribute("text-anchor", "middle")
|
||||||
|
label.setAttribute("x",radius/2);
|
||||||
|
label.setAttribute("y",radius/2 + 3);
|
||||||
|
group.appendChild(label)
|
||||||
|
}
|
||||||
|
const border = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
border.setAttribute('d', shapePath)
|
||||||
|
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
|
||||||
|
group.appendChild(border)
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function () {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RED.view.annotations.register("red-ui-multiplayer",{
|
||||||
|
type: 'badge',
|
||||||
|
align: 'left',
|
||||||
|
class: "red-ui-multiplayer-annotation",
|
||||||
|
show: "_multiplayer",
|
||||||
|
refresh: "_multiplayer_refresh",
|
||||||
|
element: function(node) {
|
||||||
|
const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
containerGroup.setAttribute("transform","translate(0,-4)")
|
||||||
|
if (node._multiplayer) {
|
||||||
|
let y = 0
|
||||||
|
for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
|
||||||
|
const user = sessions[node._multiplayer.users[i]].user
|
||||||
|
const group = createAnnotationUser(user)
|
||||||
|
group.setAttribute("transform","translate("+y+",0)")
|
||||||
|
y += 15
|
||||||
|
containerGroup.appendChild(group)
|
||||||
|
}
|
||||||
|
if (node._multiplayer.users.length > 2) {
|
||||||
|
const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
|
||||||
|
group.setAttribute("transform","translate("+y+",0)")
|
||||||
|
y += 12
|
||||||
|
containerGroup.appendChild(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return containerGroup;
|
||||||
|
},
|
||||||
|
tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
|
||||||
|
// if (!activeSessionId) {
|
||||||
|
activeSessionId = RED.nodes.id()
|
||||||
|
// RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
|
||||||
|
// log('Session ID (new)', activeSessionId)
|
||||||
|
// } else {
|
||||||
|
log('Session ID', activeSessionId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
|
||||||
|
|
||||||
|
RED.comms.on('connect', () => {
|
||||||
|
const location = getLocation()
|
||||||
|
const connectInfo = {
|
||||||
|
session: activeSessionId
|
||||||
|
}
|
||||||
|
if (location.workspace !== 0) {
|
||||||
|
connectInfo.location = location
|
||||||
|
}
|
||||||
|
RED.comms.send('multiplayer/connect', connectInfo)
|
||||||
|
})
|
||||||
|
RED.comms.subscribe('multiplayer/#', (topic, msg) => {
|
||||||
|
log('recv', topic, msg)
|
||||||
|
if (topic === 'multiplayer/init') {
|
||||||
|
// We have just reconnected, runtime has sent state to
|
||||||
|
// initialise the world
|
||||||
|
sessions = {}
|
||||||
|
users = {}
|
||||||
|
$('#red-ui-multiplayer-user-list').empty()
|
||||||
|
|
||||||
|
msg.sessions.forEach(session => {
|
||||||
|
addUserSession(session)
|
||||||
|
})
|
||||||
|
} else if (topic === 'multiplayer/connection-added') {
|
||||||
|
addUserSession(msg)
|
||||||
|
} else if (topic === 'multiplayer/connection-removed') {
|
||||||
|
removeUserSession(msg.session, msg.disconnected)
|
||||||
|
} else if (topic === 'multiplayer/location') {
|
||||||
|
const session = msg.session
|
||||||
|
delete msg.session
|
||||||
|
updateUserLocation(session, msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
RED.events.on('workspace:change', (event) => {
|
||||||
|
getWorkspaceTray(event.workspace)
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('editor:open', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('editor:close', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('editor:change', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('login', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('flows:loaded', () => {
|
||||||
|
attachWorkspaceTrays()
|
||||||
|
})
|
||||||
|
RED.events.on('workspace:close', (event) => {
|
||||||
|
// A subflow tab has been closed. Need to mark its tray as detached
|
||||||
|
if (workspaceTrays[event.workspace]) {
|
||||||
|
workspaceTrays[event.workspace].attached = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
RED.events.on('logout', () => {
|
||||||
|
const disconnectInfo = {
|
||||||
|
session: activeSessionId
|
||||||
|
}
|
||||||
|
RED.comms.send('multiplayer/disconnect', disconnectInfo)
|
||||||
|
RED.settings.removeLocal('multiplayer:sessionId')
|
||||||
|
})
|
||||||
|
|
||||||
|
const chart = $('#red-ui-workspace-chart')
|
||||||
|
chart.on('mousemove', function (evt) {
|
||||||
|
lastPosition[0] = evt.clientX
|
||||||
|
lastPosition[1] = evt.clientY
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
chart.on('scroll', function (evt) {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
chart.on('mouseenter', function () {
|
||||||
|
isInWorkspace = true
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
chart.on('mouseleave', function () {
|
||||||
|
isInWorkspace = false
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
if (RED.multiplayer.DEBUG) {
|
||||||
|
console.log('[multiplayer]', ...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -73,7 +73,13 @@ RED.nodes = (function() {
|
|||||||
|
|
||||||
var exports = {
|
var exports = {
|
||||||
setModulePendingUpdated: function(module,version) {
|
setModulePendingUpdated: function(module,version) {
|
||||||
moduleList[module].pending_version = version;
|
if (!!RED.plugins.getModule(module)) {
|
||||||
|
// The module updated is a plugin
|
||||||
|
RED.plugins.getModule(module).pending_version = version;
|
||||||
|
} else {
|
||||||
|
moduleList[module].pending_version = version;
|
||||||
|
}
|
||||||
|
|
||||||
RED.events.emit("registry:module-updated",{module:module,version:version});
|
RED.events.emit("registry:module-updated",{module:module,version:version});
|
||||||
},
|
},
|
||||||
getModule: function(module) {
|
getModule: function(module) {
|
||||||
@ -91,6 +97,31 @@ RED.nodes = (function() {
|
|||||||
getNodeTypes: function() {
|
getNodeTypes: function() {
|
||||||
return Object.keys(nodeDefinitions);
|
return Object.keys(nodeDefinitions);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Get an array of node definitions
|
||||||
|
* @param {Object} options - options object
|
||||||
|
* @param {boolean} [options.configOnly] - if true, only return config nodes
|
||||||
|
* @param {function} [options.filter] - a filter function to apply to the list of nodes
|
||||||
|
* @returns array of node definitions
|
||||||
|
*/
|
||||||
|
getNodeDefinitions: function(options) {
|
||||||
|
const result = []
|
||||||
|
const configOnly = (options && options.configOnly)
|
||||||
|
const filter = (options && options.filter)
|
||||||
|
const keys = Object.keys(nodeDefinitions)
|
||||||
|
for (const key of keys) {
|
||||||
|
const def = nodeDefinitions[key]
|
||||||
|
if(!def) { continue }
|
||||||
|
if (configOnly && def.category !== "config") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (filter && !filter(nodeDefinitions[key])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.push(nodeDefinitions[key])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
setNodeList: function(list) {
|
setNodeList: function(list) {
|
||||||
nodeList = [];
|
nodeList = [];
|
||||||
for(var i=0;i<list.length;i++) {
|
for(var i=0;i<list.length;i++) {
|
||||||
@ -124,6 +155,8 @@ RED.nodes = (function() {
|
|||||||
},
|
},
|
||||||
removeNodeSet: function(id) {
|
removeNodeSet: function(id) {
|
||||||
var ns = nodeSets[id];
|
var ns = nodeSets[id];
|
||||||
|
if (!ns) { return {} }
|
||||||
|
|
||||||
for (var j=0;j<ns.types.length;j++) {
|
for (var j=0;j<ns.types.length;j++) {
|
||||||
delete typeToId[ns.types[j]];
|
delete typeToId[ns.types[j]];
|
||||||
}
|
}
|
||||||
@ -674,12 +707,15 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
n["_"] = RED._;
|
n["_"] = RED._;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Both node and config node can use a config node
|
||||||
|
updateConfigNodeUsers(newNode, { action: "add" });
|
||||||
|
|
||||||
if (n._def.category == "config") {
|
if (n._def.category == "config") {
|
||||||
configNodes[n.id] = n;
|
configNodes[n.id] = newNode;
|
||||||
} else {
|
} else {
|
||||||
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
updateConfigNodeUsers(n);
|
|
||||||
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
||||||
var nextId = 0;
|
var nextId = 0;
|
||||||
RED.nodes.eachNode(function(node) {
|
RED.nodes.eachNode(function(node) {
|
||||||
@ -741,9 +777,11 @@ RED.nodes = (function() {
|
|||||||
var removedLinks = [];
|
var removedLinks = [];
|
||||||
var removedNodes = [];
|
var removedNodes = [];
|
||||||
var node;
|
var node;
|
||||||
|
|
||||||
if (id in configNodes) {
|
if (id in configNodes) {
|
||||||
node = configNodes[id];
|
node = configNodes[id];
|
||||||
delete configNodes[id];
|
delete configNodes[id];
|
||||||
|
updateConfigNodeUsers(node, { action: "remove" });
|
||||||
RED.events.emit('nodes:remove',node);
|
RED.events.emit('nodes:remove',node);
|
||||||
RED.workspaces.refresh();
|
RED.workspaces.refresh();
|
||||||
} else if (allNodes.hasNode(id)) {
|
} else if (allNodes.hasNode(id)) {
|
||||||
@ -752,6 +790,9 @@ RED.nodes = (function() {
|
|||||||
delete nodeLinks[id];
|
delete nodeLinks[id];
|
||||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||||
removedLinks.forEach(removeLink);
|
removedLinks.forEach(removeLink);
|
||||||
|
updateConfigNodeUsers(node, { action: "remove" });
|
||||||
|
|
||||||
|
// TODO: Legacy code for exclusive config node
|
||||||
var updatedConfigNode = false;
|
var updatedConfigNode = false;
|
||||||
for (var d in node._def.defaults) {
|
for (var d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@ -765,10 +806,6 @@ RED.nodes = (function() {
|
|||||||
if (configNode._def.exclusive) {
|
if (configNode._def.exclusive) {
|
||||||
removeNode(node[d]);
|
removeNode(node[d]);
|
||||||
removedNodes.push(configNode);
|
removedNodes.push(configNode);
|
||||||
} else {
|
|
||||||
var users = configNode.users;
|
|
||||||
users.splice(users.indexOf(node),1);
|
|
||||||
RED.events.emit('nodes:change',configNode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1005,23 +1042,34 @@ RED.nodes = (function() {
|
|||||||
return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
|
return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Subflow to the Workspace
|
||||||
|
*
|
||||||
|
* @param {object} sf The Subflow to add.
|
||||||
|
* @param {boolean|undefined} createNewIds Whether to update the name.
|
||||||
|
*/
|
||||||
function addSubflow(sf, createNewIds) {
|
function addSubflow(sf, createNewIds) {
|
||||||
if (createNewIds) {
|
if (createNewIds) {
|
||||||
var subflowNames = Object.keys(subflows).map(function(sfid) {
|
// Update the Subflow name to highlight that this is a copy
|
||||||
return subflows[sfid].name;
|
const subflowNames = Object.keys(subflows).map(function (sfid) {
|
||||||
});
|
return subflows[sfid].name || "";
|
||||||
|
})
|
||||||
|
subflowNames.sort()
|
||||||
|
|
||||||
subflowNames.sort();
|
let copyNumber = 1;
|
||||||
var copyNumber = 1;
|
let subflowName = sf.name;
|
||||||
var subflowName = sf.name;
|
|
||||||
subflowNames.forEach(function(name) {
|
subflowNames.forEach(function(name) {
|
||||||
if (subflowName == name) {
|
if (subflowName == name) {
|
||||||
|
subflowName = sf.name + " (" + copyNumber + ")";
|
||||||
copyNumber++;
|
copyNumber++;
|
||||||
subflowName = sf.name+" ("+copyNumber+")";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sf.name = subflowName;
|
sf.name = subflowName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sf.instances = [];
|
||||||
|
|
||||||
subflows[sf.id] = sf;
|
subflows[sf.id] = sf;
|
||||||
allNodes.addTab(sf.id);
|
allNodes.addTab(sf.id);
|
||||||
linkTabMap[sf.id] = [];
|
linkTabMap[sf.id] = [];
|
||||||
@ -1074,7 +1122,7 @@ RED.nodes = (function() {
|
|||||||
module: "node-red"
|
module: "node-red"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sf.instances = [];
|
|
||||||
sf._def = RED.nodes.getType("subflow:"+sf.id);
|
sf._def = RED.nodes.getType("subflow:"+sf.id);
|
||||||
RED.events.emit("subflows:add",sf);
|
RED.events.emit("subflows:add",sf);
|
||||||
}
|
}
|
||||||
@ -1716,7 +1764,8 @@ RED.nodes = (function() {
|
|||||||
// Remove the old subflow definition - but leave the instances in place
|
// Remove the old subflow definition - but leave the instances in place
|
||||||
var removalResult = RED.subflow.removeSubflow(n.id, true);
|
var removalResult = RED.subflow.removeSubflow(n.id, true);
|
||||||
// Create the list of nodes for the new subflow def
|
// Create the list of nodes for the new subflow def
|
||||||
var subflowNodes = [n].concat(zMap[n.id]);
|
// Need to sort the list in order to remove missing nodes
|
||||||
|
var subflowNodes = [n].concat(zMap[n.id]).filter((s) => !!s);
|
||||||
// Import the new subflow - no clashes should occur as we've removed
|
// Import the new subflow - no clashes should occur as we've removed
|
||||||
// the old version
|
// the old version
|
||||||
var result = importNodes(subflowNodes);
|
var result = importNodes(subflowNodes);
|
||||||
@ -1753,9 +1802,20 @@ RED.nodes = (function() {
|
|||||||
// Replace config nodes
|
// Replace config nodes
|
||||||
//
|
//
|
||||||
configNodeIds.forEach(function(id) {
|
configNodeIds.forEach(function(id) {
|
||||||
removedNodes = removedNodes.concat(convertNode(getNode(id)));
|
const configNode = getNode(id);
|
||||||
|
const currentUserCount = configNode.users;
|
||||||
|
|
||||||
|
// Add a snapshot of the Config Node
|
||||||
|
removedNodes = removedNodes.concat(convertNode(configNode));
|
||||||
|
|
||||||
|
// Remove the Config Node instance
|
||||||
removeNode(id);
|
removeNode(id);
|
||||||
importNodes([newConfigNodes[id]])
|
|
||||||
|
// Import the new one
|
||||||
|
importNodes([newConfigNodes[id]]);
|
||||||
|
|
||||||
|
// Re-attributes the user count
|
||||||
|
getNode(id).users = currentUserCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1996,6 +2056,8 @@ RED.nodes = (function() {
|
|||||||
if (matchingSubflow) {
|
if (matchingSubflow) {
|
||||||
subflow_denylist[n.id] = matchingSubflow;
|
subflow_denylist[n.id] = matchingSubflow;
|
||||||
} else {
|
} else {
|
||||||
|
const oldId = n.id;
|
||||||
|
|
||||||
subflow_map[n.id] = n;
|
subflow_map[n.id] = n;
|
||||||
if (createNewIds || options.importMap[n.id] === "copy") {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
nid = getID();
|
nid = getID();
|
||||||
@ -2023,7 +2085,7 @@ RED.nodes = (function() {
|
|||||||
n.status.id = getID();
|
n.status.id = getID();
|
||||||
}
|
}
|
||||||
new_subflows.push(n);
|
new_subflows.push(n);
|
||||||
addSubflow(n,createNewIds || options.importMap[n.id] === "copy");
|
addSubflow(n,createNewIds || options.importMap[oldId] === "copy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2037,6 +2099,8 @@ RED.nodes = (function() {
|
|||||||
activeWorkspace = RED.workspaces.active();
|
activeWorkspace = RED.workspaces.active();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pendingConfigNodes = []
|
||||||
|
const pendingConfigNodeIds = new Set()
|
||||||
// Find all config nodes and add them
|
// Find all config nodes and add them
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
@ -2096,7 +2160,8 @@ RED.nodes = (function() {
|
|||||||
type:n.type,
|
type:n.type,
|
||||||
info: n.info,
|
info: n.info,
|
||||||
users:[],
|
users:[],
|
||||||
_config:{}
|
_config:{},
|
||||||
|
_configNodeReferences: new Set()
|
||||||
};
|
};
|
||||||
if (!n.z) {
|
if (!n.z) {
|
||||||
delete configNode.z;
|
delete configNode.z;
|
||||||
@ -2111,6 +2176,9 @@ RED.nodes = (function() {
|
|||||||
if (def.defaults.hasOwnProperty(d)) {
|
if (def.defaults.hasOwnProperty(d)) {
|
||||||
configNode[d] = n[d];
|
configNode[d] = n[d];
|
||||||
configNode._config[d] = JSON.stringify(n[d]);
|
configNode._config[d] = JSON.stringify(n[d]);
|
||||||
|
if (def.defaults[d].type) {
|
||||||
|
configNode._configNodeReferences.add(n[d])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||||
@ -2127,11 +2195,55 @@ RED.nodes = (function() {
|
|||||||
configNode.id = getID();
|
configNode.id = getID();
|
||||||
}
|
}
|
||||||
node_map[n.id] = configNode;
|
node_map[n.id] = configNode;
|
||||||
new_nodes.push(configNode);
|
pendingConfigNodes.push(configNode);
|
||||||
|
pendingConfigNodeIds.add(configNode.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to sort new_nodes (which only contains config nodes at this point)
|
||||||
|
// to ensure they get added in the right order. If NodeA depends on NodeB, then
|
||||||
|
// NodeB must be added first.
|
||||||
|
|
||||||
|
// Limit us to 5 full iterations of the list - this should be more than
|
||||||
|
// enough to process the list as config->config node relationships are
|
||||||
|
// not very common
|
||||||
|
let iterationLimit = pendingConfigNodes.length * 5
|
||||||
|
const handledConfigNodes = new Set()
|
||||||
|
while (pendingConfigNodes.length > 0 && iterationLimit > 0) {
|
||||||
|
const node = pendingConfigNodes.shift()
|
||||||
|
let hasPending = false
|
||||||
|
// Loop through the nodes referenced by this node to see if anything
|
||||||
|
// is pending
|
||||||
|
node._configNodeReferences.forEach(id => {
|
||||||
|
if (pendingConfigNodeIds.has(id) && !handledConfigNodes.has(id)) {
|
||||||
|
// This reference is for a node we know is in this import, but
|
||||||
|
// it isn't added yet - flag as pending
|
||||||
|
hasPending = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!hasPending) {
|
||||||
|
// This node has no pending config node references - safe to add
|
||||||
|
delete node._configNodeReferences
|
||||||
|
new_nodes.push(node)
|
||||||
|
handledConfigNodes.add(node.id)
|
||||||
|
} else {
|
||||||
|
// This node has pending config node references
|
||||||
|
// Put to the back of the queue
|
||||||
|
pendingConfigNodes.push(node)
|
||||||
|
}
|
||||||
|
iterationLimit--
|
||||||
|
}
|
||||||
|
if (pendingConfigNodes.length > 0) {
|
||||||
|
// We exceeded the iteration count. Could be due to reference loops
|
||||||
|
// between the config nodes. At this point, just add the remaining
|
||||||
|
// nodes as-is
|
||||||
|
pendingConfigNodes.forEach(node => {
|
||||||
|
delete node._configNodeReferences
|
||||||
|
new_nodes.push(node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Find regular flow nodes and subflow instances
|
// Find regular flow nodes and subflow instances
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
@ -2143,7 +2255,7 @@ RED.nodes = (function() {
|
|||||||
x:parseFloat(n.x || 0),
|
x:parseFloat(n.x || 0),
|
||||||
y:parseFloat(n.y || 0),
|
y:parseFloat(n.y || 0),
|
||||||
z:n.z,
|
z:n.z,
|
||||||
type:0,
|
type: n.type,
|
||||||
info: n.info,
|
info: n.info,
|
||||||
changed:false,
|
changed:false,
|
||||||
_config:{}
|
_config:{}
|
||||||
@ -2204,7 +2316,6 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.type = n.type;
|
|
||||||
node._def = def;
|
node._def = def;
|
||||||
if (node.type === "group") {
|
if (node.type === "group") {
|
||||||
node._def = RED.group.def;
|
node._def = RED.group.def;
|
||||||
@ -2234,6 +2345,15 @@ RED.nodes = (function() {
|
|||||||
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
|
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
|
||||||
set: registry.getNodeSet("node-red/unknown")
|
set: registry.getNodeSet("node-red/unknown")
|
||||||
}
|
}
|
||||||
|
var orig = {};
|
||||||
|
for (var p in n) {
|
||||||
|
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||||
|
orig[p] = n[p];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node._orig = orig;
|
||||||
|
node.name = n.type;
|
||||||
|
node.type = "unknown";
|
||||||
} else {
|
} else {
|
||||||
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
|
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
|
||||||
parentId = subflow.id;
|
parentId = subflow.id;
|
||||||
@ -2294,29 +2414,31 @@ RED.nodes = (function() {
|
|||||||
node.type = "unknown";
|
node.type = "unknown";
|
||||||
}
|
}
|
||||||
if (node._def.category != "config") {
|
if (node._def.category != "config") {
|
||||||
if (n.hasOwnProperty('inputs')) {
|
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
|
||||||
node.inputs = n.inputs;
|
node.inputs = parseInt(n.inputs, 10);
|
||||||
node._config.inputs = JSON.stringify(n.inputs);
|
node._config.inputs = JSON.stringify(n.inputs);
|
||||||
} else {
|
} else {
|
||||||
node.inputs = node._def.inputs;
|
node.inputs = node._def.inputs;
|
||||||
}
|
}
|
||||||
if (n.hasOwnProperty('outputs')) {
|
if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) {
|
||||||
node.outputs = n.outputs;
|
node.outputs = parseInt(n.outputs, 10);
|
||||||
node._config.outputs = JSON.stringify(n.outputs);
|
node._config.outputs = JSON.stringify(n.outputs);
|
||||||
} else {
|
} else {
|
||||||
node.outputs = node._def.outputs;
|
node.outputs = node._def.outputs;
|
||||||
}
|
}
|
||||||
if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) {
|
|
||||||
if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) {
|
// The node declares outputs in its defaults, but has not got a valid value
|
||||||
// If 'wires' is longer than outputs, clip wires
|
// Defer to the length of the wires array
|
||||||
console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs);
|
if (node.hasOwnProperty('wires')) {
|
||||||
node.wires = node.wires.slice(0,node.outputs);
|
if (isNaN(node.outputs)) {
|
||||||
} else {
|
|
||||||
// The node declares outputs in its defaults, but has not got a valid value
|
|
||||||
// Defer to the length of the wires array
|
|
||||||
node.outputs = node.wires.length;
|
node.outputs = node.wires.length;
|
||||||
|
} else if (node.wires.length > node.outputs) {
|
||||||
|
// If 'wires' is longer than outputs, clip wires
|
||||||
|
console.log("Warning: node.wires longer than node.outputs - trimming wires:", node.id, " wires:", node.wires.length, " outputs:", node.outputs);
|
||||||
|
node.wires = node.wires.slice(0, node.outputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
||||||
node[d] = n[d];
|
node[d] = n[d];
|
||||||
@ -2379,11 +2501,28 @@ RED.nodes = (function() {
|
|||||||
} else {
|
} else {
|
||||||
delete n.g
|
delete n.g
|
||||||
}
|
}
|
||||||
// If importing into a subflow, ensure an outbound-link doesn't get added
|
// If importing a link node, ensure both ends of each link are either:
|
||||||
if (activeSubflow && /^link /.test(n.type) && n.links) {
|
// - not in a subflow
|
||||||
|
// - both in the same subflow (not for link call node)
|
||||||
|
if (/^link /.test(n.type) && n.links) {
|
||||||
n.links = n.links.filter(function(id) {
|
n.links = n.links.filter(function(id) {
|
||||||
const otherNode = node_map[id] || RED.nodes.node(id);
|
const otherNode = node_map[id] || RED.nodes.node(id);
|
||||||
return (otherNode && otherNode.z === activeWorkspace);
|
if (!otherNode) {
|
||||||
|
// Cannot find other end - remove the link
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (otherNode.z === n.z) {
|
||||||
|
// Both ends in the same flow/subflow
|
||||||
|
return true
|
||||||
|
} else if (n.type === "link call" && !getSubflow(otherNode.z)) {
|
||||||
|
// Link call node can call out of a subflow as long as otherNode is
|
||||||
|
// not in a subflow
|
||||||
|
return true
|
||||||
|
} else if (!!getSubflow(n.z) || !!getSubflow(otherNode.z)) {
|
||||||
|
// One end is in a subflow - remove the link
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (var d3 in n._def.defaults) {
|
for (var d3 in n._def.defaults) {
|
||||||
@ -2396,11 +2535,6 @@ RED.nodes = (function() {
|
|||||||
nodeList = nodeList.map(function(id) {
|
nodeList = nodeList.map(function(id) {
|
||||||
var node = node_map[id];
|
var node = node_map[id];
|
||||||
if (node) {
|
if (node) {
|
||||||
if (node._def.category === 'config') {
|
|
||||||
if (node.users.indexOf(n) === -1) {
|
|
||||||
node.users.push(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node.id;
|
return node.id;
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
@ -2414,9 +2548,11 @@ RED.nodes = (function() {
|
|||||||
n = new_subflows[i];
|
n = new_subflows[i];
|
||||||
n.in.forEach(function(input) {
|
n.in.forEach(function(input) {
|
||||||
input.wires.forEach(function(wire) {
|
input.wires.forEach(function(wire) {
|
||||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
if (node_map.hasOwnProperty(wire.id)) {
|
||||||
addLink(link);
|
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||||
new_links.push(link);
|
addLink(link);
|
||||||
|
new_links.push(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
delete input.wires;
|
delete input.wires;
|
||||||
});
|
});
|
||||||
@ -2425,11 +2561,13 @@ RED.nodes = (function() {
|
|||||||
var link;
|
var link;
|
||||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
||||||
} else {
|
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
|
||||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
||||||
}
|
}
|
||||||
addLink(link);
|
if (link) {
|
||||||
new_links.push(link);
|
addLink(link);
|
||||||
|
new_links.push(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
delete output.wires;
|
delete output.wires;
|
||||||
});
|
});
|
||||||
@ -2438,11 +2576,13 @@ RED.nodes = (function() {
|
|||||||
var link;
|
var link;
|
||||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
|
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
|
||||||
} else {
|
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
|
||||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
|
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
|
||||||
}
|
}
|
||||||
addLink(link);
|
if (link) {
|
||||||
new_links.push(link);
|
addLink(link);
|
||||||
|
new_links.push(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
delete n.status.wires;
|
delete n.status.wires;
|
||||||
}
|
}
|
||||||
@ -2621,25 +2761,79 @@ RED.nodes = (function() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
|
/**
|
||||||
function updateConfigNodeUsers(n) {
|
* Update any config nodes referenced by the provided node to ensure
|
||||||
for (var d in n._def.defaults) {
|
* their 'users' list is correct.
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
*
|
||||||
var property = n._def.defaults[d];
|
* @param {object} node The node in which to check if it contains references
|
||||||
|
* @param {object} options Options to apply.
|
||||||
|
* @param {"add" | "remove"} [options.action] Add or remove the node from
|
||||||
|
* the Config Node users list. Default `add`.
|
||||||
|
* @param {boolean} [options.emitEvent] Emit the `nodes:changes` event.
|
||||||
|
* Default true.
|
||||||
|
*/
|
||||||
|
function updateConfigNodeUsers(node, options) {
|
||||||
|
const defaultOptions = { action: "add", emitEvent: true };
|
||||||
|
options = Object.assign({}, defaultOptions, options);
|
||||||
|
|
||||||
|
for (var d in node._def.defaults) {
|
||||||
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
|
var property = node._def.defaults[d];
|
||||||
if (property.type) {
|
if (property.type) {
|
||||||
var type = registry.getNodeType(property.type);
|
var type = registry.getNodeType(property.type);
|
||||||
|
// Need to ensure the type is a config node to not treat links nodes
|
||||||
if (type && type.category == "config") {
|
if (type && type.category == "config") {
|
||||||
var configNode = configNodes[n[d]];
|
var configNode = configNodes[node[d]];
|
||||||
if (configNode) {
|
if (configNode) {
|
||||||
if (configNode.users.indexOf(n) === -1) {
|
if (options.action === "add") {
|
||||||
configNode.users.push(n);
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
RED.events.emit('nodes:change',configNode)
|
configNode.users.push(node);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.action === "remove") {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
const users = configNode.users;
|
||||||
|
users.splice(users.indexOf(node), 1);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subflows can have config node env
|
||||||
|
if (node.type.indexOf("subflow:") === 0) {
|
||||||
|
node.env?.forEach((prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
// Add the node to the config node users
|
||||||
|
const configNode = getNode(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (options.action === "add") {
|
||||||
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
|
configNode.users.push(node);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.action === "remove") {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
const users = configNode.users;
|
||||||
|
users.splice(users.indexOf(node), 1);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function flowVersion(version) {
|
function flowVersion(version) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
RED.plugins = (function() {
|
RED.plugins = (function() {
|
||||||
var plugins = {};
|
var plugins = {};
|
||||||
var pluginsByType = {};
|
var pluginsByType = {};
|
||||||
|
var moduleList = {};
|
||||||
|
|
||||||
function registerPlugin(id,definition) {
|
function registerPlugin(id,definition) {
|
||||||
plugins[id] = definition;
|
plugins[id] = definition;
|
||||||
@ -38,9 +39,43 @@ RED.plugins = (function() {
|
|||||||
function getPluginsByType(type) {
|
function getPluginsByType(type) {
|
||||||
return pluginsByType[type] || [];
|
return pluginsByType[type] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setPluginList(list) {
|
||||||
|
for(let i=0;i<list.length;i++) {
|
||||||
|
let p = list[i];
|
||||||
|
addPlugin(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPlugin(p) {
|
||||||
|
|
||||||
|
moduleList[p.module] = moduleList[p.module] || {
|
||||||
|
name:p.module,
|
||||||
|
version:p.version,
|
||||||
|
local:p.local,
|
||||||
|
sets:{},
|
||||||
|
plugin: true,
|
||||||
|
id: p.id
|
||||||
|
};
|
||||||
|
if (p.pending_version) {
|
||||||
|
moduleList[p.module].pending_version = p.pending_version;
|
||||||
|
}
|
||||||
|
moduleList[p.module].sets[p.name] = p;
|
||||||
|
|
||||||
|
RED.events.emit("registry:plugin-module-added",p.module);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModule(module) {
|
||||||
|
return moduleList[module];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
registerPlugin: registerPlugin,
|
registerPlugin: registerPlugin,
|
||||||
getPlugin: getPlugin,
|
getPlugin: getPlugin,
|
||||||
getPluginsByType: getPluginsByType
|
getPluginsByType: getPluginsByType,
|
||||||
|
|
||||||
|
setPluginList: setPluginList,
|
||||||
|
addPlugin: addPlugin,
|
||||||
|
getModule: getModule
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -25,6 +25,7 @@ var RED = (function() {
|
|||||||
cache: false,
|
cache: false,
|
||||||
url: 'plugins',
|
url: 'plugins',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
RED.plugins.setPluginList(data);
|
||||||
loader.reportProgress(RED._("event.loadPlugins"), 13)
|
loader.reportProgress(RED._("event.loadPlugins"), 13)
|
||||||
RED.i18n.loadPluginCatalogs(function() {
|
RED.i18n.loadPluginCatalogs(function() {
|
||||||
loadPlugins(function() {
|
loadPlugins(function() {
|
||||||
@ -297,6 +298,7 @@ var RED = (function() {
|
|||||||
RED.workspaces.show(workspaces[0]);
|
RED.workspaces.show(workspaces[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RED.events.emit('flows:loaded')
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
RED.notify(
|
RED.notify(
|
||||||
@ -534,6 +536,41 @@ var RED = (function() {
|
|||||||
RED.view.redrawStatus(node);
|
RED.view.redrawStatus(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
RED.comms.subscribe("notification/plugin/#",function(topic,msg) {
|
||||||
|
if (topic == "notification/plugin/added") {
|
||||||
|
RED.settings.refreshSettings(function(err, data) {
|
||||||
|
let addedPlugins = [];
|
||||||
|
msg.forEach(function(m) {
|
||||||
|
let id = m.id;
|
||||||
|
RED.plugins.addPlugin(m);
|
||||||
|
|
||||||
|
m.plugins.forEach((p) => {
|
||||||
|
addedPlugins.push(p.id);
|
||||||
|
})
|
||||||
|
|
||||||
|
RED.i18n.loadNodeCatalog(id, function() {
|
||||||
|
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"text/html",
|
||||||
|
"Accept-Language": lang
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'plugins/'+id,
|
||||||
|
success: function(data) {
|
||||||
|
appendPluginConfig(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (addedPlugins.length) {
|
||||||
|
let pluginList = "<ul><li>"+addedPlugins.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||||
|
// ToDo: Adapt notification (node -> plugin)
|
||||||
|
RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let pendingNodeRemovedNotifications = []
|
let pendingNodeRemovedNotifications = []
|
||||||
let pendingNodeRemovedTimeout
|
let pendingNodeRemovedTimeout
|
||||||
@ -803,6 +840,10 @@ var RED = (function() {
|
|||||||
|
|
||||||
RED.nodes.init();
|
RED.nodes.init();
|
||||||
RED.runtime.init()
|
RED.runtime.init()
|
||||||
|
|
||||||
|
if (RED.settings.theme("multiplayer.enabled",false)) {
|
||||||
|
RED.multiplayer.init()
|
||||||
|
}
|
||||||
RED.comms.connect();
|
RED.comms.connect();
|
||||||
|
|
||||||
$("#red-ui-main-container").show();
|
$("#red-ui-main-container").show();
|
||||||
|
@ -205,7 +205,9 @@ RED.actionList = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
RED.actions.add("core:show-action-list",show);
|
if (RED.settings.theme("menu.menu-item-action-list", true)) {
|
||||||
|
RED.actions.add("core:show-action-list",show);
|
||||||
|
}
|
||||||
|
|
||||||
RED.events.on("editor:open",function() { disabled = true; });
|
RED.events.on("editor:open",function() { disabled = true; });
|
||||||
RED.events.on("editor:close",function() { disabled = false; });
|
RED.events.on("editor:close",function() { disabled = false; });
|
||||||
|
@ -26,6 +26,7 @@ RED.clipboard = (function() {
|
|||||||
var currentPopoverError;
|
var currentPopoverError;
|
||||||
var activeTab;
|
var activeTab;
|
||||||
var libraryBrowser;
|
var libraryBrowser;
|
||||||
|
var clipboardTabs;
|
||||||
|
|
||||||
var activeLibraries = {};
|
var activeLibraries = {};
|
||||||
|
|
||||||
@ -215,6 +216,13 @@ RED.clipboard = (function() {
|
|||||||
open: function( event, ui ) {
|
open: function( event, ui ) {
|
||||||
RED.keyboard.disable();
|
RED.keyboard.disable();
|
||||||
},
|
},
|
||||||
|
beforeClose: function(e) {
|
||||||
|
if (clipboardTabs && activeTab === "red-ui-clipboard-dialog-export-tab-clipboard") {
|
||||||
|
const jsonTabIndex = clipboardTabs.getTabIndex('red-ui-clipboard-dialog-export-tab-clipboard-json')
|
||||||
|
const activeTabIndex = clipboardTabs.activeIndex()
|
||||||
|
RED.settings.set("editor.dialog.export.json-view", activeTabIndex === jsonTabIndex )
|
||||||
|
}
|
||||||
|
},
|
||||||
close: function(e) {
|
close: function(e) {
|
||||||
RED.keyboard.enable();
|
RED.keyboard.enable();
|
||||||
if (popover) {
|
if (popover) {
|
||||||
@ -228,12 +236,23 @@ RED.clipboard = (function() {
|
|||||||
|
|
||||||
exportNodesDialog =
|
exportNodesDialog =
|
||||||
'<div class="form-row">'+
|
'<div class="form-row">'+
|
||||||
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
|
'<div style="display: flex; justify-content: space-between;">'+
|
||||||
'<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+
|
'<div class="form-row">'+
|
||||||
'<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
|
||||||
'<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
'<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+
|
||||||
'<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
'<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
||||||
'</span>'+
|
'<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
||||||
|
'<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.format"></label>'+
|
||||||
|
'<span id="red-ui-clipboard-dialog-export-fmt-group" class="button-group">'+
|
||||||
|
'<a id="red-ui-clipboard-dialog-export-fmt-mini" class="red-ui-button red-ui-button toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
||||||
|
'<a id="red-ui-clipboard-dialog-export-fmt-full" class="red-ui-button red-ui-button toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="red-ui-clipboard-dialog-box">'+
|
'<div class="red-ui-clipboard-dialog-box">'+
|
||||||
'<div class="red-ui-clipboard-dialog-tabs">'+
|
'<div class="red-ui-clipboard-dialog-tabs">'+
|
||||||
@ -248,15 +267,9 @@ RED.clipboard = (function() {
|
|||||||
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+
|
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+
|
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+
|
||||||
'<div class="form-row" style="height:calc(100% - 40px)">'+
|
'<div class="form-row" style="height:calc(100% - 10px)">'+
|
||||||
'<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+
|
'<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row" style="text-align: right;">'+
|
|
||||||
'<span id="red-ui-clipboard-dialog-export-fmt-group" class="button-group">'+
|
|
||||||
'<a id="red-ui-clipboard-dialog-export-fmt-mini" class="red-ui-button red-ui-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
|
||||||
'<a id="red-ui-clipboard-dialog-export-fmt-full" class="red-ui-button red-ui-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
|
||||||
'</span>'+
|
|
||||||
'</div>'+
|
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+
|
'<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+
|
||||||
@ -321,6 +334,30 @@ RED.clipboard = (function() {
|
|||||||
},100);
|
},100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if the provided string looks like valid flow json
|
||||||
|
* @param {string} flowString the string to validate
|
||||||
|
* @returns If valid, returns the node array
|
||||||
|
*/
|
||||||
|
function validateFlowString(flowString) {
|
||||||
|
const res = JSON.parse(flowString)
|
||||||
|
if (!Array.isArray(res)) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.notArray"));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
if (typeof res[i] !== "object") {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
||||||
|
}
|
||||||
|
if (!Object.hasOwn(res[i], 'id')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
||||||
|
}
|
||||||
|
if (!Object.hasOwn(res[i], 'type')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
var validateImportTimeout;
|
var validateImportTimeout;
|
||||||
function validateImport() {
|
function validateImport() {
|
||||||
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
|
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
|
||||||
@ -338,21 +375,7 @@ RED.clipboard = (function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!/^\[[\s\S]*\]$/m.test(v)) {
|
validateFlowString(v)
|
||||||
throw new Error(RED._("clipboard.import.errors.notArray"));
|
|
||||||
}
|
|
||||||
var res = JSON.parse(v);
|
|
||||||
for (var i=0;i<res.length;i++) {
|
|
||||||
if (typeof res[i] !== "object") {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('id')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('type')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentPopoverError = null;
|
currentPopoverError = null;
|
||||||
popover.close(true);
|
popover.close(true);
|
||||||
importInput.removeClass("input-error");
|
importInput.removeClass("input-error");
|
||||||
@ -569,7 +592,7 @@ RED.clipboard = (function() {
|
|||||||
|
|
||||||
dialogContainer.empty();
|
dialogContainer.empty();
|
||||||
dialogContainer.append($(exportNodesDialog));
|
dialogContainer.append($(exportNodesDialog));
|
||||||
|
clipboardTabs = null
|
||||||
var tabs = RED.tabs.create({
|
var tabs = RED.tabs.create({
|
||||||
id: "red-ui-clipboard-dialog-export-tabs",
|
id: "red-ui-clipboard-dialog-export-tabs",
|
||||||
vertical: true,
|
vertical: true,
|
||||||
@ -630,7 +653,7 @@ RED.clipboard = (function() {
|
|||||||
$("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
|
$("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
|
||||||
$("#red-ui-clipboard-dialog-export").button("enable");
|
$("#red-ui-clipboard-dialog-export").button("enable");
|
||||||
|
|
||||||
var clipboardTabs = RED.tabs.create({
|
clipboardTabs = RED.tabs.create({
|
||||||
id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs",
|
id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs",
|
||||||
onchange: function(tab) {
|
onchange: function(tab) {
|
||||||
$(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide();
|
$(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide();
|
||||||
@ -647,6 +670,9 @@ RED.clipboard = (function() {
|
|||||||
id: "red-ui-clipboard-dialog-export-tab-clipboard-json",
|
id: "red-ui-clipboard-dialog-export-tab-clipboard-json",
|
||||||
label: RED._("editor.types.json")
|
label: RED._("editor.types.json")
|
||||||
});
|
});
|
||||||
|
if (RED.settings.get("editor.dialog.export.json-view") === true) {
|
||||||
|
clipboardTabs.activateTab("red-ui-clipboard-dialog-export-tab-clipboard-json");
|
||||||
|
}
|
||||||
|
|
||||||
var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
|
var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
|
||||||
data: []
|
data: []
|
||||||
@ -982,16 +1008,16 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function importNodes(nodesStr,addFlow) {
|
function importNodes(nodesStr,addFlow) {
|
||||||
var newNodes = nodesStr;
|
let newNodes = nodesStr;
|
||||||
if (typeof nodesStr === 'string') {
|
if (typeof nodesStr === 'string') {
|
||||||
try {
|
try {
|
||||||
nodesStr = nodesStr.trim();
|
nodesStr = nodesStr.trim();
|
||||||
if (nodesStr.length === 0) {
|
if (nodesStr.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newNodes = JSON.parse(nodesStr);
|
newNodes = validateFlowString(nodesStr)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
const e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
||||||
e.code = "NODE_RED";
|
e.code = "NODE_RED";
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -1326,6 +1352,7 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
console.warn('Import failed: ', err)
|
||||||
// Ensure any errors throw above doesn't stop the drop target from
|
// Ensure any errors throw above doesn't stop the drop target from
|
||||||
// being hidden.
|
// being hidden.
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
this.menu = RED.popover.menu({
|
this.menu = RED.popover.menu({
|
||||||
tabSelect: true,
|
tabSelect: true,
|
||||||
width: 300,
|
width: Math.max(300, this.element.width()),
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
class: "red-ui-autoComplete-container",
|
class: "red-ui-autoComplete-container",
|
||||||
options: completions,
|
options: completions,
|
||||||
|
@ -174,12 +174,24 @@
|
|||||||
this.uiContainer.width(m[1]);
|
this.uiContainer.width(m[1]);
|
||||||
}
|
}
|
||||||
if (this.options.sortable) {
|
if (this.options.sortable) {
|
||||||
|
var isCanceled = false; // Flag to track if an item has been canceled from being dropped into a different list
|
||||||
|
var noDrop = false; // Flag to track if an item is being dragged into a different list
|
||||||
var handle = (typeof this.options.sortable === 'string')?
|
var handle = (typeof this.options.sortable === 'string')?
|
||||||
this.options.sortable :
|
this.options.sortable :
|
||||||
".red-ui-editableList-item-handle";
|
".red-ui-editableList-item-handle";
|
||||||
var sortOptions = {
|
var sortOptions = {
|
||||||
axis: "y",
|
axis: "y",
|
||||||
update: function( event, ui ) {
|
update: function( event, ui ) {
|
||||||
|
// dont trigger update if the item is being canceled
|
||||||
|
const targetList = $(event.target);
|
||||||
|
const draggedItem = ui.item;
|
||||||
|
const draggedItemParent = draggedItem.parent();
|
||||||
|
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
|
||||||
|
noDrop = true;
|
||||||
|
}
|
||||||
|
if (isCanceled || noDrop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (that.options.sortItems) {
|
if (that.options.sortItems) {
|
||||||
that.options.sortItems(that.items());
|
that.options.sortItems(that.items());
|
||||||
}
|
}
|
||||||
@ -189,8 +201,32 @@
|
|||||||
tolerance: "pointer",
|
tolerance: "pointer",
|
||||||
forcePlaceholderSize:true,
|
forcePlaceholderSize:true,
|
||||||
placeholder: "red-ui-editabelList-item-placeholder",
|
placeholder: "red-ui-editabelList-item-placeholder",
|
||||||
start: function(e, ui){
|
start: function (event, ui) {
|
||||||
ui.placeholder.height(ui.item.height()-4);
|
isCanceled = false;
|
||||||
|
ui.placeholder.height(ui.item.height() - 4);
|
||||||
|
ui.item.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
|
||||||
|
},
|
||||||
|
stop: function (event, ui) {
|
||||||
|
ui.item.css('cursor', 'auto');
|
||||||
|
},
|
||||||
|
receive: function (event, ui) {
|
||||||
|
if (ui.item.hasClass("red-ui-editableList-item-constrained")) {
|
||||||
|
isCanceled = true;
|
||||||
|
$(ui.sender).sortable('cancel');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
over: function (event, ui) {
|
||||||
|
// if the dragged item is constrained, prevent it from being dropped into a different list
|
||||||
|
const targetList = $(event.target);
|
||||||
|
const draggedItem = ui.item;
|
||||||
|
const draggedItemParent = draggedItem.parent();
|
||||||
|
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
|
||||||
|
noDrop = true;
|
||||||
|
draggedItem.css('cursor', 'no-drop'); // TODO: this doesn't seem to work, use a class instead?
|
||||||
|
} else {
|
||||||
|
noDrop = false;
|
||||||
|
draggedItem.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (this.options.connectWith) {
|
if (this.options.connectWith) {
|
||||||
|
@ -211,7 +211,7 @@ RED.popover = (function() {
|
|||||||
closePopup(true);
|
closePopup(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (trigger === 'hover' && options.interactive) {
|
if (/*trigger === 'hover' && */options.interactive) {
|
||||||
div.on('mouseenter', function(e) {
|
div.on('mouseenter', function(e) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
active = true;
|
active = true;
|
||||||
@ -445,9 +445,12 @@ RED.popover = (function() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
create: createPopover,
|
create: createPopover,
|
||||||
tooltip: function(target,content, action) {
|
tooltip: function(target,content, action, interactive) {
|
||||||
var label = function() {
|
var label = function() {
|
||||||
var label = content;
|
var label = content;
|
||||||
|
if (typeof content === 'function') {
|
||||||
|
label = content()
|
||||||
|
}
|
||||||
if (action) {
|
if (action) {
|
||||||
var shortcut = RED.keyboard.getShortcut(action);
|
var shortcut = RED.keyboard.getShortcut(action);
|
||||||
if (shortcut && shortcut.key) {
|
if (shortcut && shortcut.key) {
|
||||||
@ -463,6 +466,7 @@ RED.popover = (function() {
|
|||||||
size: "small",
|
size: "small",
|
||||||
direction: "bottom",
|
direction: "bottom",
|
||||||
content: label,
|
content: label,
|
||||||
|
interactive,
|
||||||
delay: { show: 750, hide: 50 }
|
delay: { show: 750, hide: 50 }
|
||||||
});
|
});
|
||||||
popover.setContent = function(newContent) {
|
popover.setContent = function(newContent) {
|
||||||
|
@ -365,7 +365,10 @@ RED.tabs = (function() {
|
|||||||
|
|
||||||
var thisTabA = thisTab.find("a");
|
var thisTabA = thisTab.find("a");
|
||||||
if (options.onclick) {
|
if (options.onclick) {
|
||||||
options.onclick(tabs[thisTabA.attr('href').slice(1)]);
|
options.onclick(tabs[thisTabA.attr('href').slice(1)], evt);
|
||||||
|
if (evt.isDefaultPrevented() && evt.isPropagationStopped()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
activateTab(thisTabA);
|
activateTab(thisTabA);
|
||||||
if (fireSelectionChanged) {
|
if (fireSelectionChanged) {
|
||||||
@ -548,6 +551,8 @@ RED.tabs = (function() {
|
|||||||
ul.find("li.red-ui-tab a")
|
ul.find("li.red-ui-tab a")
|
||||||
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
||||||
.on("mouseup",onTabClick)
|
.on("mouseup",onTabClick)
|
||||||
|
// prevent browser-default middle-click behaviour
|
||||||
|
.on("auxclick", function(evt) { evt.preventDefault() })
|
||||||
.on("click", function(evt) {evt.preventDefault(); })
|
.on("click", function(evt) {evt.preventDefault(); })
|
||||||
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
|
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
|
||||||
|
|
||||||
@ -816,6 +821,8 @@ RED.tabs = (function() {
|
|||||||
}
|
}
|
||||||
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
||||||
link.on("mouseup",onTabClick);
|
link.on("mouseup",onTabClick);
|
||||||
|
// prevent browser-default middle-click behaviour
|
||||||
|
link.on("auxclick", function(evt) { evt.preventDefault() })
|
||||||
link.on("click", function(evt) { evt.preventDefault(); })
|
link.on("click", function(evt) { evt.preventDefault(); })
|
||||||
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
|
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
|
||||||
|
|
||||||
|
@ -54,25 +54,27 @@
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
var autoComplete = function(options) {
|
function getMatch(value, searchValue) {
|
||||||
function getMatch(value, searchValue) {
|
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
const len = idx > -1 ? searchValue.length : 0;
|
||||||
const len = idx > -1 ? searchValue.length : 0;
|
return {
|
||||||
return {
|
index: idx,
|
||||||
index: idx,
|
found: idx > -1,
|
||||||
found: idx > -1,
|
pre: value.substring(0,idx),
|
||||||
pre: value.substring(0,idx),
|
match: value.substring(idx,idx+len),
|
||||||
match: value.substring(idx,idx+len),
|
post: value.substring(idx+len),
|
||||||
post: value.substring(idx+len),
|
exact: idx === 0 && value.length === searchValue.length
|
||||||
}
|
|
||||||
}
|
|
||||||
function generateSpans(match) {
|
|
||||||
const els = [];
|
|
||||||
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
|
||||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
|
||||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
|
||||||
return els;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
function generateSpans(match) {
|
||||||
|
const els = [];
|
||||||
|
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
||||||
|
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
||||||
|
if(match.post) { els.push($('<span/>').text(match.post)); }
|
||||||
|
return els;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgAutoComplete = function(options) {
|
||||||
return function(val) {
|
return function(val) {
|
||||||
var matches = [];
|
var matches = [];
|
||||||
options.forEach(opt => {
|
options.forEach(opt => {
|
||||||
@ -82,7 +84,7 @@
|
|||||||
const srcMatch = getMatch(optSrc, val);
|
const srcMatch = getMatch(optSrc, val);
|
||||||
if (valMatch.found || srcMatch.found) {
|
if (valMatch.found || srcMatch.found) {
|
||||||
const element = $('<div>',{style: "display: flex"});
|
const element = $('<div>',{style: "display: flex"});
|
||||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
valEl.append(generateSpans(valMatch));
|
valEl.append(generateSpans(valMatch));
|
||||||
valEl.appendTo(element);
|
valEl.appendTo(element);
|
||||||
if (optSrc) {
|
if (optSrc) {
|
||||||
@ -102,6 +104,210 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEnvVars (obj, envVars = {}) {
|
||||||
|
contextKnownKeys.env = contextKnownKeys.env || {}
|
||||||
|
if (contextKnownKeys.env[obj.id]) {
|
||||||
|
return contextKnownKeys.env[obj.id]
|
||||||
|
}
|
||||||
|
let parent
|
||||||
|
if (obj.type === 'tab' || obj.type === 'subflow') {
|
||||||
|
RED.nodes.eachConfig(function (conf) {
|
||||||
|
if (conf.type === "global-config") {
|
||||||
|
parent = conf;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (obj.g) {
|
||||||
|
parent = RED.nodes.group(obj.g)
|
||||||
|
} else if (obj.z) {
|
||||||
|
parent = RED.nodes.workspace(obj.z) || RED.nodes.subflow(obj.z)
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
getEnvVars(parent, envVars)
|
||||||
|
}
|
||||||
|
if (obj.env) {
|
||||||
|
obj.env.forEach(env => {
|
||||||
|
envVars[env.name] = obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
contextKnownKeys.env[obj.id] = envVars
|
||||||
|
return envVars
|
||||||
|
}
|
||||||
|
|
||||||
|
const envAutoComplete = function (val) {
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
if (editStack.length === 0) {
|
||||||
|
done([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const editingNode = editStack.pop()
|
||||||
|
if (!editingNode) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const envVarsMap = getEnvVars(editingNode)
|
||||||
|
const envVars = Object.keys(envVarsMap)
|
||||||
|
const matches = []
|
||||||
|
const i = val.lastIndexOf('${')
|
||||||
|
let searchKey = val
|
||||||
|
let isSubkey = false
|
||||||
|
if (i > -1) {
|
||||||
|
if (val.lastIndexOf('}') < i) {
|
||||||
|
searchKey = val.substring(i+2)
|
||||||
|
isSubkey = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
envVars.forEach(v => {
|
||||||
|
let valMatch = getMatch(v, searchKey);
|
||||||
|
if (valMatch.found) {
|
||||||
|
const optSrc = envVarsMap[v]
|
||||||
|
const element = $('<div>',{style: "display: flex"});
|
||||||
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
|
valEl.append(generateSpans(valMatch))
|
||||||
|
valEl.appendTo(element)
|
||||||
|
|
||||||
|
if (optSrc) {
|
||||||
|
const optEl = $('<div>').css({ "font-size": "0.8em" });
|
||||||
|
let label
|
||||||
|
if (optSrc.type === 'global-config') {
|
||||||
|
label = RED._('sidebar.context.global')
|
||||||
|
} else if (optSrc.type === 'group') {
|
||||||
|
label = RED.utils.getNodeLabel(optSrc) || (RED._('sidebar.info.group') + ': '+optSrc.id)
|
||||||
|
} else {
|
||||||
|
label = RED.utils.getNodeLabel(optSrc) || optSrc.id
|
||||||
|
}
|
||||||
|
|
||||||
|
optEl.append(generateSpans({ match: label }));
|
||||||
|
optEl.appendTo(element);
|
||||||
|
}
|
||||||
|
matches.push({
|
||||||
|
value: isSubkey ? val + v + '}' : v,
|
||||||
|
label: element,
|
||||||
|
i: valMatch.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
matches.sort(function(A,B){return A.i-B.i})
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextKnownKeys = {}
|
||||||
|
let contextCache = {}
|
||||||
|
if (RED.events) {
|
||||||
|
RED.events.on("editor:close", function () {
|
||||||
|
contextCache = {}
|
||||||
|
contextKnownKeys = {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextAutoComplete = function() {
|
||||||
|
const that = this
|
||||||
|
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
|
||||||
|
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
|
||||||
|
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map()
|
||||||
|
if (searchKey.length > 0) {
|
||||||
|
try {
|
||||||
|
RED.utils.normalisePropertyExpression(searchKey)
|
||||||
|
} catch (err) {
|
||||||
|
// Not a valid context key, so don't try looking up
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
|
||||||
|
if (contextCache[url]) {
|
||||||
|
// console.log('CACHED', url)
|
||||||
|
done()
|
||||||
|
} else {
|
||||||
|
// console.log('GET', url)
|
||||||
|
$.getJSON(url, function(data) {
|
||||||
|
// console.log(data)
|
||||||
|
contextCache[url] = true
|
||||||
|
const result = data[store] || {}
|
||||||
|
const keys = result.keys || []
|
||||||
|
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
|
||||||
|
keys.forEach(keyInfo => {
|
||||||
|
const key = keyInfo.key
|
||||||
|
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
|
||||||
|
contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo)
|
||||||
|
} else {
|
||||||
|
contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getContextKeys = function(key, done) {
|
||||||
|
const keyParts = key.split('.')
|
||||||
|
const partialKey = keyParts.pop()
|
||||||
|
let scope = that.propertyType
|
||||||
|
if (scope === 'flow') {
|
||||||
|
// Get the flow id of the node we're editing
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
if (editStack.length === 0) {
|
||||||
|
done(new Map())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const editingNode = editStack.pop()
|
||||||
|
if (editingNode.z) {
|
||||||
|
scope = `${scope}/${editingNode.z}`
|
||||||
|
} else {
|
||||||
|
done(new Map())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const store = (contextStoreOptions.length === 1) ? contextStoreOptions[0].value : that.optionValue
|
||||||
|
const searchKey = keyParts.join('.')
|
||||||
|
|
||||||
|
getContextKeysFromRuntime(scope, store, searchKey, function() {
|
||||||
|
if (contextKnownKeys[scope][store].has(key) || key.endsWith(']')) {
|
||||||
|
getContextKeysFromRuntime(scope, store, key, function() {
|
||||||
|
done(contextKnownKeys[scope][store])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
done(contextKnownKeys[scope][store])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(val, done) {
|
||||||
|
getContextKeys(val, function (keys) {
|
||||||
|
const matches = []
|
||||||
|
keys.forEach((keyInfo, v) => {
|
||||||
|
let optVal = v
|
||||||
|
let valMatch = getMatch(optVal, val);
|
||||||
|
if (!valMatch.found && val.length > 0) {
|
||||||
|
if (val.endsWith('.')) {
|
||||||
|
// Search key ends in '.' - but doesn't match. Check again
|
||||||
|
// with [" at the end instead so we match bracket notation
|
||||||
|
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
|
||||||
|
// } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) {
|
||||||
|
// console.log('this case')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valMatch.found) {
|
||||||
|
const element = $('<div>',{style: "display: flex"});
|
||||||
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
|
// if (keyInfo.format) {
|
||||||
|
// valMatch.post += ' ' + keyInfo.format
|
||||||
|
// }
|
||||||
|
if (valMatch.exact && /^array/.test(keyInfo.format)) {
|
||||||
|
valMatch.post += `[0-${keyInfo.length}]`
|
||||||
|
optVal += '['
|
||||||
|
|
||||||
|
}
|
||||||
|
valEl.append(generateSpans(valMatch))
|
||||||
|
valEl.appendTo(element)
|
||||||
|
matches.push({
|
||||||
|
value: optVal,
|
||||||
|
label: element,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
matches.sort(function(a, b) { return a.value.localeCompare(b.value) });
|
||||||
|
done(matches);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
||||||
var msgCompletions = [
|
var msgCompletions = [
|
||||||
{ value: "payload" },
|
{ value: "payload" },
|
||||||
@ -166,68 +372,93 @@
|
|||||||
{ value: "_session", source: ["websocket out","tcp out"] },
|
{ value: "_session", source: ["websocket out","tcp out"] },
|
||||||
]
|
]
|
||||||
var allOptions = {
|
var allOptions = {
|
||||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
msg: { value: "msg", label: "msg.", validate: RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions) },
|
||||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
flow: { value: "flow", label: "flow.", hasValue: true,
|
||||||
options:[],
|
options: [],
|
||||||
validate:RED.utils.validatePropertyExpression,
|
validate: RED.utils.validatePropertyExpression,
|
||||||
parse: contextParse,
|
parse: contextParse,
|
||||||
export: contextExport,
|
export: contextExport,
|
||||||
valueLabel: contextLabel
|
valueLabel: contextLabel,
|
||||||
|
autoComplete: contextAutoComplete
|
||||||
},
|
},
|
||||||
global: {value:"global",label:"global.",hasValue:true,
|
global: {
|
||||||
options:[],
|
value: "global", label: "global.", hasValue: true,
|
||||||
validate:RED.utils.validatePropertyExpression,
|
options: [],
|
||||||
|
validate: RED.utils.validatePropertyExpression,
|
||||||
parse: contextParse,
|
parse: contextParse,
|
||||||
export: contextExport,
|
export: contextExport,
|
||||||
valueLabel: contextLabel
|
valueLabel: contextLabel,
|
||||||
|
autoComplete: contextAutoComplete
|
||||||
},
|
},
|
||||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
|
str: { value: "str", label: "string", icon: "red/images/typedInput/az.svg" },
|
||||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
|
num: { value: "num", label: "number", icon: "red/images/typedInput/09.svg", validate: function (v, o) {
|
||||||
return (true === RED.utils.validateTypedProperty(v, "num"));
|
return RED.utils.validateTypedProperty(v, "num", o);
|
||||||
} },
|
} },
|
||||||
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
|
bool: { value: "bool", label: "boolean", icon: "red/images/typedInput/bool.svg", options: ["true", "false"] },
|
||||||
json: {
|
json: {
|
||||||
value:"json",
|
value: "json",
|
||||||
label:"JSON",
|
label: "JSON",
|
||||||
icon:"red/images/typedInput/json.svg",
|
icon: "red/images/typedInput/json.svg",
|
||||||
validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}},
|
validate: function (v, o) {
|
||||||
expand: function() {
|
return RED.utils.validateTypedProperty(v, "json", o);
|
||||||
|
},
|
||||||
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
var value = this.value();
|
var value = this.value();
|
||||||
try {
|
try {
|
||||||
value = JSON.stringify(JSON.parse(value),null,4);
|
value = JSON.stringify(JSON.parse(value), null, 4);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
}
|
}
|
||||||
RED.editor.editJSON({
|
RED.editor.editJSON({
|
||||||
value: value,
|
value: value,
|
||||||
stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
|
stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
|
||||||
focus: true,
|
focus: true,
|
||||||
complete: function(v) {
|
complete: function (v) {
|
||||||
var value = v;
|
var value = v;
|
||||||
try {
|
try {
|
||||||
value = JSON.stringify(JSON.parse(v));
|
value = JSON.stringify(JSON.parse(v));
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
}
|
}
|
||||||
that.value(value);
|
that.value(value);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
|
re: { value: "re", label: "regular expression", icon: "red/images/typedInput/re.svg" },
|
||||||
date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false},
|
date: {
|
||||||
|
value: "date",
|
||||||
|
label: "timestamp",
|
||||||
|
icon: "fa fa-clock-o",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'milliseconds since epoch',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'YYYY-MM-DDTHH:mm:ss.sssZ',
|
||||||
|
value: 'iso'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'JavaScript Date Object',
|
||||||
|
value: 'object'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
jsonata: {
|
jsonata: {
|
||||||
value: "jsonata",
|
value: "jsonata",
|
||||||
label: "expression",
|
label: "expression",
|
||||||
icon: "red/images/typedInput/expr.svg",
|
icon: "red/images/typedInput/expr.svg",
|
||||||
validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
|
validate: function (v, o) {
|
||||||
expand:function() {
|
return RED.utils.validateTypedProperty(v, "jsonata", o);
|
||||||
|
},
|
||||||
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
RED.editor.editExpression({
|
RED.editor.editExpression({
|
||||||
value: this.value().replace(/\t/g,"\n"),
|
value: this.value().replace(/\t/g, "\n"),
|
||||||
stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
|
stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
|
||||||
focus: true,
|
focus: true,
|
||||||
complete: function(v) {
|
complete: function (v) {
|
||||||
that.value(v.replace(/\n/g,"\t"));
|
that.value(v.replace(/\n/g, "\t"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -236,13 +467,13 @@
|
|||||||
value: "bin",
|
value: "bin",
|
||||||
label: "buffer",
|
label: "buffer",
|
||||||
icon: "red/images/typedInput/bin.svg",
|
icon: "red/images/typedInput/bin.svg",
|
||||||
expand: function() {
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
RED.editor.editBuffer({
|
RED.editor.editBuffer({
|
||||||
value: this.value(),
|
value: this.value(),
|
||||||
stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
|
stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
|
||||||
focus: true,
|
focus: true,
|
||||||
complete: function(v) {
|
complete: function (v) {
|
||||||
that.value(v);
|
that.value(v);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -251,15 +482,16 @@
|
|||||||
env: {
|
env: {
|
||||||
value: "env",
|
value: "env",
|
||||||
label: "env variable",
|
label: "env variable",
|
||||||
icon: "red/images/typedInput/env.svg"
|
icon: "red/images/typedInput/env.svg",
|
||||||
|
autoComplete: envAutoComplete
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
value: "node",
|
value: "node",
|
||||||
label: "node",
|
label: "node",
|
||||||
icon: "red/images/typedInput/target.svg",
|
icon: "red/images/typedInput/target.svg",
|
||||||
valueLabel: function(container,value) {
|
valueLabel: function (container, value) {
|
||||||
var node = RED.nodes.node(value);
|
var node = RED.nodes.node(value);
|
||||||
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).css({
|
var nodeDiv = $('<div>', { class: "red-ui-search-result-node" }).css({
|
||||||
"margin-top": "2px",
|
"margin-top": "2px",
|
||||||
"margin-left": "3px"
|
"margin-left": "3px"
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
@ -268,133 +500,190 @@
|
|||||||
"margin-left": "6px"
|
"margin-left": "6px"
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
if (node) {
|
if (node) {
|
||||||
var colour = RED.utils.getNodeColor(node.type,node._def);
|
var colour = RED.utils.getNodeColor(node.type, node._def);
|
||||||
var icon_url = RED.utils.getNodeIcon(node._def,node);
|
var icon_url = RED.utils.getNodeIcon(node._def, node);
|
||||||
if (node.type === 'tab') {
|
if (node.type === 'tab') {
|
||||||
colour = "#C0DEED";
|
colour = "#C0DEED";
|
||||||
}
|
}
|
||||||
nodeDiv.css('backgroundColor',colour);
|
nodeDiv.css('backgroundColor', colour);
|
||||||
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
var iconContainer = $('<div/>', { class: "red-ui-palette-icon-container" }).appendTo(nodeDiv);
|
||||||
RED.utils.createIconElement(icon_url, iconContainer, true);
|
RED.utils.createIconElement(icon_url, iconContainer, true);
|
||||||
var l = RED.utils.getNodeLabel(node,node.id);
|
var l = RED.utils.getNodeLabel(node, node.id);
|
||||||
nodeLabel.text(l);
|
nodeLabel.text(l);
|
||||||
} else {
|
} else {
|
||||||
nodeDiv.css({
|
nodeDiv.css({
|
||||||
'backgroundColor': '#eee',
|
'backgroundColor': '#eee',
|
||||||
'border-style' : 'dashed'
|
'border-style': 'dashed'
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expand: function() {
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
RED.tray.hide();
|
RED.tray.hide();
|
||||||
RED.view.selectNodes({
|
RED.view.selectNodes({
|
||||||
single: true,
|
single: true,
|
||||||
selected: [that.value()],
|
selected: [that.value()],
|
||||||
onselect: function(selection) {
|
onselect: function (selection) {
|
||||||
that.value(selection.id);
|
that.value(selection.id);
|
||||||
RED.tray.show();
|
RED.tray.show();
|
||||||
},
|
},
|
||||||
oncancel: function() {
|
oncancel: function () {
|
||||||
RED.tray.show();
|
RED.tray.show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cred:{
|
cred: {
|
||||||
value:"cred",
|
value: "cred",
|
||||||
label:"credential",
|
label: "credential",
|
||||||
icon:"fa fa-lock",
|
icon: "fa fa-lock",
|
||||||
inputType: "password",
|
inputType: "password",
|
||||||
valueLabel: function(container,value) {
|
valueLabel: function (container, value) {
|
||||||
var that = this;
|
var that = this;
|
||||||
container.css("pointer-events","none");
|
container.css("pointer-events", "none");
|
||||||
container.css("flex-grow",0);
|
container.css("flex-grow", 0);
|
||||||
this.elementDiv.hide();
|
this.elementDiv.hide();
|
||||||
var buttons = $('<div>').css({
|
var buttons = $('<div>').css({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right:"6px",
|
right: "6px",
|
||||||
top: "6px",
|
top: "6px",
|
||||||
"pointer-events":"all"
|
"pointer-events": "all"
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
|
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
|
||||||
width:"20px"
|
width: "20px"
|
||||||
}).appendTo(buttons).on("click", function(evt) {
|
}).appendTo(buttons).on("click", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
var cursorPosition = that.input[0].selectionStart;
|
var cursorPosition = that.input[0].selectionStart;
|
||||||
var currentType = that.input.attr("type");
|
var currentType = that.input.attr("type");
|
||||||
if (currentType === "text") {
|
if (currentType === "text") {
|
||||||
that.input.attr("type","password");
|
that.input.attr("type", "password");
|
||||||
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
that.input.focus();
|
that.input.focus();
|
||||||
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
||||||
},50);
|
}, 50);
|
||||||
} else {
|
} else {
|
||||||
that.input.attr("type","text");
|
that.input.attr("type", "text");
|
||||||
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
|
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
that.input.focus();
|
that.input.focus();
|
||||||
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
||||||
},50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}).hide();
|
}).hide();
|
||||||
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-2px").appendTo(eyeButton);
|
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left", "-2px").appendTo(eyeButton);
|
||||||
|
|
||||||
if (value === "__PWRD__") {
|
if (value === "__PWRD__") {
|
||||||
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
|
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
|
||||||
padding:"6px 6px",
|
padding: "6px 6px",
|
||||||
borderRadius:"4px"
|
borderRadius: "4px"
|
||||||
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
|
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
|
||||||
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function(evt) {
|
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
innerContainer.hide();
|
innerContainer.hide();
|
||||||
container.css("background","none");
|
container.css("background", "none");
|
||||||
container.css("pointer-events","none");
|
container.css("pointer-events", "none");
|
||||||
that.input.val("");
|
that.input.val("");
|
||||||
that.element.val("");
|
that.element.val("");
|
||||||
that.elementDiv.show();
|
that.elementDiv.show();
|
||||||
editButton.hide();
|
editButton.hide();
|
||||||
cancelButton.show();
|
cancelButton.show();
|
||||||
eyeButton.show();
|
eyeButton.show();
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
that.input.focus();
|
that.input.focus();
|
||||||
},50);
|
}, 50);
|
||||||
});
|
});
|
||||||
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left","3px").appendTo(buttons).on("click", function(evt) {
|
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left", "3px").appendTo(buttons).on("click", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
innerContainer.show();
|
innerContainer.show();
|
||||||
container.css("background","");
|
container.css("background", "");
|
||||||
that.input.val("__PWRD__");
|
that.input.val("__PWRD__");
|
||||||
that.element.val("__PWRD__");
|
that.element.val("__PWRD__");
|
||||||
that.elementDiv.hide();
|
that.elementDiv.hide();
|
||||||
editButton.show();
|
editButton.show();
|
||||||
cancelButton.hide();
|
cancelButton.hide();
|
||||||
eyeButton.hide();
|
eyeButton.hide();
|
||||||
that.input.attr("type","password");
|
that.input.attr("type", "password");
|
||||||
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
||||||
|
|
||||||
}).hide();
|
}).hide();
|
||||||
} else {
|
} else {
|
||||||
container.css("background","none");
|
container.css("background", "none");
|
||||||
container.css("pointer-events","none");
|
container.css("pointer-events", "none");
|
||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
eyeButton.show();
|
eyeButton.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'conf-types': {
|
||||||
|
value: "conf-types",
|
||||||
|
label: "config",
|
||||||
|
icon: "fa fa-cog",
|
||||||
|
// hasValue: false,
|
||||||
|
valueLabel: function (container, value) {
|
||||||
|
// get the selected option (for access to the "name" and "module" properties)
|
||||||
|
const _options = this._optionsCache || this.typeList.find(opt => opt.value === value)?.options || []
|
||||||
|
const selectedOption = _options.find(opt => opt.value === value) || {
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
module: ''
|
||||||
|
}
|
||||||
|
container.attr("title", selectedOption.title) // set tooltip to the full path/id of the module/node
|
||||||
|
container.text(selectedOption.name) // apply the "name" of the selected option
|
||||||
|
// set "line-height" such as to make the "name" appear further up, giving room for the "module" to be displayed below the value
|
||||||
|
container.css("line-height", "1.4em")
|
||||||
|
// add the module name in smaller, lighter font below the value
|
||||||
|
$('<div></div>').text(selectedOption.module).css({
|
||||||
|
// "font-family": "var(--red-ui-monospace-font)",
|
||||||
|
color: "var(--red-ui-tertiary-text-color)",
|
||||||
|
"font-size": "0.8em",
|
||||||
|
"line-height": "1em",
|
||||||
|
opacity: 0.8
|
||||||
|
}).appendTo(container);
|
||||||
|
},
|
||||||
|
// hasValue: false,
|
||||||
|
options: function () {
|
||||||
|
if (this._optionsCache) {
|
||||||
|
return this._optionsCache
|
||||||
|
}
|
||||||
|
const configNodes = RED.nodes.registry.getNodeDefinitions({configOnly: true, filter: (def) => def.type !== "global-config"}).map((def) => {
|
||||||
|
// create a container with with 2 rows (row 1 for the name, row 2 for the module name in smaller, lighter font)
|
||||||
|
const container = $('<div style="display: flex; flex-direction: column; justify-content: space-between; row-gap: 1px;">')
|
||||||
|
const row1Name = $('<div>').text(def.type)
|
||||||
|
const row2Module = $('<div style="font-size: 0.8em; color: var(--red-ui-tertiary-text-color);">').text(def.set.module)
|
||||||
|
container.append(row1Name, row2Module)
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: def.type,
|
||||||
|
name: def.type,
|
||||||
|
enabled: def.set.enabled ?? true,
|
||||||
|
local: def.set.local,
|
||||||
|
title: def.set.id, // tooltip e.g. "node-red-contrib-foo/bar"
|
||||||
|
module: def.set.module,
|
||||||
|
icon: container[0].outerHTML.trim(), // the typeInput will interpret this as html text and render it in the anchor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this._optionsCache = configNodes
|
||||||
|
return configNodes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// For a type with options, check value is a valid selection
|
// For a type with options, check value is a valid selection
|
||||||
// If !opt.multiple, returns the valid option object
|
// If !opt.multiple, returns the valid option object
|
||||||
// if opt.multiple, returns an array of valid option objects
|
// if opt.multiple, returns an array of valid option objects
|
||||||
// If not valid, returns null;
|
// If not valid, returns null;
|
||||||
|
|
||||||
function isOptionValueValid(opt, currentVal) {
|
function isOptionValueValid(opt, currentVal) {
|
||||||
|
let _options = opt.options
|
||||||
|
if (typeof _options === "function") {
|
||||||
|
_options = _options.call(this)
|
||||||
|
}
|
||||||
if (!opt.multiple) {
|
if (!opt.multiple) {
|
||||||
for (var i=0;i<opt.options.length;i++) {
|
for (var i=0;i<_options.length;i++) {
|
||||||
op = opt.options[i];
|
op = _options[i];
|
||||||
if (typeof op === "string" && op === currentVal) {
|
if (typeof op === "string" && op === currentVal) {
|
||||||
return {value:currentVal}
|
return {value:currentVal}
|
||||||
} else if (op.value === currentVal) {
|
} else if (op.value === currentVal) {
|
||||||
@ -411,8 +700,8 @@
|
|||||||
currentValues[v] = true;
|
currentValues[v] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (var i=0;i<opt.options.length;i++) {
|
for (var i=0;i<_options.length;i++) {
|
||||||
op = opt.options[i];
|
op = _options[i];
|
||||||
var val = typeof op === "string" ? op : op.value;
|
var val = typeof op === "string" ? op : op.value;
|
||||||
if (currentValues.hasOwnProperty(val)) {
|
if (currentValues.hasOwnProperty(val)) {
|
||||||
delete currentValues[val];
|
delete currentValues[val];
|
||||||
@ -427,6 +716,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var nlsd = false;
|
var nlsd = false;
|
||||||
|
let contextStoreOptions;
|
||||||
|
|
||||||
$.widget( "nodered.typedInput", {
|
$.widget( "nodered.typedInput", {
|
||||||
_create: function() {
|
_create: function() {
|
||||||
@ -438,7 +728,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var contextStores = RED.settings.context.stores;
|
var contextStores = RED.settings.context.stores;
|
||||||
var contextOptions = contextStores.map(function(store) {
|
contextStoreOptions = contextStores.map(function(store) {
|
||||||
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
|
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
|
||||||
}).sort(function(A,B) {
|
}).sort(function(A,B) {
|
||||||
if (A.value === RED.settings.context.default) {
|
if (A.value === RED.settings.context.default) {
|
||||||
@ -449,13 +739,17 @@
|
|||||||
return A.value.localeCompare(B.value);
|
return A.value.localeCompare(B.value);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (contextOptions.length < 2) {
|
if (contextStoreOptions.length < 2) {
|
||||||
allOptions.flow.options = [];
|
allOptions.flow.options = [];
|
||||||
allOptions.global.options = [];
|
allOptions.global.options = [];
|
||||||
} else {
|
} else {
|
||||||
allOptions.flow.options = contextOptions;
|
allOptions.flow.options = contextStoreOptions;
|
||||||
allOptions.global.options = contextOptions;
|
allOptions.global.options = contextStoreOptions;
|
||||||
}
|
}
|
||||||
|
// Translate timestamp options
|
||||||
|
allOptions.date.options.forEach(opt => {
|
||||||
|
opt.label = RED._("typedInput.date.format." + (opt.value || 'timestamp'), {defaultValue: opt.label})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
nlsd = true;
|
nlsd = true;
|
||||||
var that = this;
|
var that = this;
|
||||||
@ -544,7 +838,7 @@
|
|||||||
that.element.trigger('paste',evt);
|
that.element.trigger('paste',evt);
|
||||||
});
|
});
|
||||||
this.input.on('keydown', function(evt) {
|
this.input.on('keydown', function(evt) {
|
||||||
if (that.typeMap[that.propertyType].autoComplete) {
|
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
||||||
@ -838,7 +1132,9 @@
|
|||||||
if (this.optionMenu) {
|
if (this.optionMenu) {
|
||||||
this.optionMenu.remove();
|
this.optionMenu.remove();
|
||||||
}
|
}
|
||||||
this.menu.remove();
|
if (this.menu) {
|
||||||
|
this.menu.remove();
|
||||||
|
}
|
||||||
this.uiSelect.remove();
|
this.uiSelect.remove();
|
||||||
},
|
},
|
||||||
types: function(types) {
|
types: function(types) {
|
||||||
@ -871,7 +1167,7 @@
|
|||||||
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
|
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
|
||||||
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
|
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
|
||||||
if (!firstCall) {
|
if (!firstCall) {
|
||||||
this.type(this.typeList[0].value);
|
this.type(this.typeList[0]?.value || ""); // permit empty typeList
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.propertyType = null;
|
this.propertyType = null;
|
||||||
@ -908,6 +1204,11 @@
|
|||||||
var selectedOption = [];
|
var selectedOption = [];
|
||||||
var valueToCheck = value;
|
var valueToCheck = value;
|
||||||
if (opt.options) {
|
if (opt.options) {
|
||||||
|
let _options = opt.options
|
||||||
|
if (typeof opt.options === "function") {
|
||||||
|
_options = opt.options.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
if (opt.hasValue && opt.parse) {
|
if (opt.hasValue && opt.parse) {
|
||||||
var parts = opt.parse(value);
|
var parts = opt.parse(value);
|
||||||
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
|
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
|
||||||
@ -921,8 +1222,8 @@
|
|||||||
checkValues = valueToCheck.split(",");
|
checkValues = valueToCheck.split(",");
|
||||||
}
|
}
|
||||||
checkValues.forEach(function(valueToCheck) {
|
checkValues.forEach(function(valueToCheck) {
|
||||||
for (var i=0;i<opt.options.length;i++) {
|
for (var i=0;i<_options.length;i++) {
|
||||||
var op = opt.options[i];
|
var op = _options[i];
|
||||||
if (typeof op === "string") {
|
if (typeof op === "string") {
|
||||||
if (op === valueToCheck || op === ""+valueToCheck) {
|
if (op === valueToCheck || op === ""+valueToCheck) {
|
||||||
selectedOption.push(that.activeOptions[op]);
|
selectedOption.push(that.activeOptions[op]);
|
||||||
@ -957,7 +1258,7 @@
|
|||||||
},
|
},
|
||||||
type: function(type) {
|
type: function(type) {
|
||||||
if (!arguments.length) {
|
if (!arguments.length) {
|
||||||
return this.propertyType;
|
return this.propertyType || this.options?.default || '';
|
||||||
} else {
|
} else {
|
||||||
var that = this;
|
var that = this;
|
||||||
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
|
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
|
||||||
@ -967,6 +1268,9 @@
|
|||||||
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
||||||
var previousType = this.typeMap[this.propertyType];
|
var previousType = this.typeMap[this.propertyType];
|
||||||
previousValue = this.input.val();
|
previousValue = this.input.val();
|
||||||
|
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||||
|
this.input.autoComplete("destroy");
|
||||||
|
}
|
||||||
|
|
||||||
if (previousType && this.typeChanged) {
|
if (previousType && this.typeChanged) {
|
||||||
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
||||||
@ -1013,7 +1317,9 @@
|
|||||||
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
|
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
|
||||||
}
|
}
|
||||||
if (previousType.autoComplete) {
|
if (previousType.autoComplete) {
|
||||||
this.input.autoComplete("destroy");
|
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||||
|
this.input.autoComplete("destroy");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.propertyType = type;
|
this.propertyType = type;
|
||||||
@ -1053,6 +1359,10 @@
|
|||||||
this.optionMenu = null;
|
this.optionMenu = null;
|
||||||
}
|
}
|
||||||
if (opt.options) {
|
if (opt.options) {
|
||||||
|
let _options = opt.options
|
||||||
|
if (typeof _options === "function") {
|
||||||
|
_options = opt.options.call(this);
|
||||||
|
}
|
||||||
if (this.optionExpandButton) {
|
if (this.optionExpandButton) {
|
||||||
this.optionExpandButton.hide();
|
this.optionExpandButton.hide();
|
||||||
this.optionExpandButton.shown = false;
|
this.optionExpandButton.shown = false;
|
||||||
@ -1069,7 +1379,7 @@
|
|||||||
this.valueLabelContainer.hide();
|
this.valueLabelContainer.hide();
|
||||||
}
|
}
|
||||||
this.activeOptions = {};
|
this.activeOptions = {};
|
||||||
opt.options.forEach(function(o) {
|
_options.forEach(function(o) {
|
||||||
if (typeof o === 'string') {
|
if (typeof o === 'string') {
|
||||||
that.activeOptions[o] = {label:o,value:o};
|
that.activeOptions[o] = {label:o,value:o};
|
||||||
} else {
|
} else {
|
||||||
@ -1089,7 +1399,7 @@
|
|||||||
if (validValues) {
|
if (validValues) {
|
||||||
that._updateOptionSelectLabel(validValues)
|
that._updateOptionSelectLabel(validValues)
|
||||||
} else {
|
} else {
|
||||||
op = opt.options[0];
|
op = _options[0] || {value:""}; // permit zero options
|
||||||
if (typeof op === "string") {
|
if (typeof op === "string") {
|
||||||
this.value(op);
|
this.value(op);
|
||||||
that._updateOptionSelectLabel({value:op});
|
that._updateOptionSelectLabel({value:op});
|
||||||
@ -1108,7 +1418,7 @@
|
|||||||
that._updateOptionSelectLabel(validValues);
|
that._updateOptionSelectLabel(validValues);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var selectedOption = this.optionValue||opt.options[0];
|
var selectedOption = this.optionValue||_options[0];
|
||||||
if (opt.parse) {
|
if (opt.parse) {
|
||||||
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
|
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
|
||||||
var parts = opt.parse(this.input.val(),selectedOptionObj);
|
var parts = opt.parse(this.input.val(),selectedOptionObj);
|
||||||
@ -1141,8 +1451,18 @@
|
|||||||
} else {
|
} else {
|
||||||
this.optionSelectTrigger.hide();
|
this.optionSelectTrigger.hide();
|
||||||
}
|
}
|
||||||
|
if (opt.autoComplete) {
|
||||||
|
let searchFunction = opt.autoComplete
|
||||||
|
if (searchFunction.length === 0) {
|
||||||
|
searchFunction = opt.autoComplete.call(this)
|
||||||
|
}
|
||||||
|
this.input.autoComplete({
|
||||||
|
search: searchFunction,
|
||||||
|
minLength: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.optionMenu = this._createMenu(opt.options,opt,function(v){
|
this.optionMenu = this._createMenu(_options,opt,function(v){
|
||||||
if (!opt.multiple) {
|
if (!opt.multiple) {
|
||||||
that._updateOptionSelectLabel(that.activeOptions[v]);
|
that._updateOptionSelectLabel(that.activeOptions[v]);
|
||||||
if (!opt.hasValue) {
|
if (!opt.hasValue) {
|
||||||
@ -1183,8 +1503,12 @@
|
|||||||
this.valueLabelContainer.hide();
|
this.valueLabelContainer.hide();
|
||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
if (opt.autoComplete) {
|
if (opt.autoComplete) {
|
||||||
|
let searchFunction = opt.autoComplete
|
||||||
|
if (searchFunction.length === 0) {
|
||||||
|
searchFunction = opt.autoComplete.call(this)
|
||||||
|
}
|
||||||
this.input.autoComplete({
|
this.input.autoComplete({
|
||||||
search: opt.autoComplete,
|
search: searchFunction,
|
||||||
minLength: 0
|
minLength: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1233,26 +1557,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validate: function() {
|
validate: function(options) {
|
||||||
var result;
|
let valid = true;
|
||||||
var value = this.value();
|
const value = this.value();
|
||||||
var type = this.type();
|
const type = this.type();
|
||||||
if (this.typeMap[type] && this.typeMap[type].validate) {
|
if (this.typeMap[type] && this.typeMap[type].validate) {
|
||||||
var val = this.typeMap[type].validate;
|
const validate = this.typeMap[type].validate;
|
||||||
if (typeof val === 'function') {
|
if (typeof validate === 'function') {
|
||||||
result = val(value);
|
valid = validate(value, {});
|
||||||
} else {
|
} else {
|
||||||
result = val.test(value);
|
// Regex
|
||||||
|
valid = validate.test(value);
|
||||||
|
if (!valid) {
|
||||||
|
valid = RED._("validator.errors.invalid-regexp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((typeof valid === "string") || !valid) {
|
||||||
|
this.element.addClass("input-error");
|
||||||
|
this.uiSelect.addClass("input-error");
|
||||||
|
if (typeof valid === "string") {
|
||||||
|
let tooltip = this.element.data("tooltip");
|
||||||
|
if (tooltip) {
|
||||||
|
tooltip.setContent(valid);
|
||||||
|
} else {
|
||||||
|
const target = this.typeMap[type]?.options ? this.optionSelectLabel : this.elementDiv;
|
||||||
|
tooltip = RED.popover.tooltip(target, valid);
|
||||||
|
this.element.data("tooltip", tooltip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = true;
|
this.element.removeClass("input-error");
|
||||||
|
this.uiSelect.removeClass("input-error");
|
||||||
|
const tooltip = this.element.data("tooltip");
|
||||||
|
if (tooltip) {
|
||||||
|
this.element.data("tooltip", null);
|
||||||
|
tooltip.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result) {
|
if (options?.returnErrorMessage === true) {
|
||||||
this.uiSelect.removeClass('input-error');
|
return valid;
|
||||||
} else {
|
|
||||||
this.uiSelect.addClass('input-error');
|
|
||||||
}
|
}
|
||||||
return result;
|
// Must return a boolean for no 3.x validator
|
||||||
|
return (typeof valid === "string") ? false : valid;
|
||||||
},
|
},
|
||||||
show: function() {
|
show: function() {
|
||||||
this.uiSelect.show();
|
this.uiSelect.show();
|
||||||
|
@ -32,39 +32,44 @@ RED.contextMenu = (function () {
|
|||||||
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
|
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
|
||||||
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
|
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
|
||||||
if (hasSelection) {
|
if (hasSelection) {
|
||||||
selection.nodes.forEach(n => {
|
const nodes = selection.nodes.slice();
|
||||||
|
while (nodes.length) {
|
||||||
|
const n = nodes.shift();
|
||||||
if (n.type === 'group') {
|
if (n.type === 'group') {
|
||||||
hasGroup = true;
|
hasGroup = true;
|
||||||
|
nodes.push(...n.nodes);
|
||||||
} else {
|
} else {
|
||||||
isAllGroups = false;
|
isAllGroups = false;
|
||||||
}
|
if (n.d) {
|
||||||
if (n.d) {
|
hasDisabledNode = true;
|
||||||
hasDisabledNode = true;
|
} else {
|
||||||
} else {
|
hasEnabledNode = true;
|
||||||
hasEnabledNode = true;
|
}
|
||||||
}
|
}
|
||||||
if (n.l === undefined || n.l) {
|
if (n.l === undefined || n.l) {
|
||||||
hasLabeledNode = true;
|
hasLabeledNode = true;
|
||||||
} else {
|
} else {
|
||||||
hasUnlabeledNode = true;
|
hasUnlabeledNode = true;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
const offset = $("#red-ui-workspace-chart").offset()
|
|
||||||
|
|
||||||
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
|
const scale = RED.view.scale()
|
||||||
let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
|
const offset = $("#red-ui-workspace-chart").offset()
|
||||||
|
let addX = (options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / scale
|
||||||
|
let addY = (options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()) / scale
|
||||||
|
|
||||||
if (RED.view.snapGrid) {
|
if (RED.view.snapGrid) {
|
||||||
const gridSize = RED.view.gridSize()
|
const gridSize = RED.view.gridSize()
|
||||||
addX = gridSize * Math.floor(addX / gridSize)
|
addX = gridSize * Math.round(addX / gridSize)
|
||||||
addY = gridSize * Math.floor(addY / gridSize)
|
addY = gridSize * Math.round(addY / gridSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems.push(
|
if (RED.settings.theme("menu.menu-item-action-list", true)) {
|
||||||
{ onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
|
menuItems.push(
|
||||||
)
|
{ onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
|
||||||
|
)
|
||||||
|
}
|
||||||
const insertOptions = []
|
const insertOptions = []
|
||||||
menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
|
menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
|
||||||
insertOptions.push(
|
insertOptions.push(
|
||||||
@ -82,7 +87,9 @@ RED.contextMenu = (function () {
|
|||||||
},
|
},
|
||||||
(hasLinks) ? { // has least 1 wire selected
|
(hasLinks) ? { // has least 1 wire selected
|
||||||
label: RED._("contextMenu.junction"),
|
label: RED._("contextMenu.junction"),
|
||||||
onselect: 'core:split-wires-with-junctions',
|
onselect: function () {
|
||||||
|
RED.actions.invoke('core:split-wires-with-junctions', { x: addX, y: addY })
|
||||||
|
},
|
||||||
disabled: !canEdit || !hasLinks
|
disabled: !canEdit || !hasLinks
|
||||||
} : {
|
} : {
|
||||||
label: RED._("contextMenu.junction"),
|
label: RED._("contextMenu.junction"),
|
||||||
|
@ -34,6 +34,8 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
var currentDiff = null;
|
var currentDiff = null;
|
||||||
|
|
||||||
|
var activeBackgroundDeployNotification;
|
||||||
|
|
||||||
function changeDeploymentType(type) {
|
function changeDeploymentType(type) {
|
||||||
deploymentType = type;
|
deploymentType = type;
|
||||||
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
|
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
|
||||||
@ -42,6 +44,7 @@ RED.deploy = (function() {
|
|||||||
/**
|
/**
|
||||||
* options:
|
* options:
|
||||||
* type: "default" - Button with drop-down options - no further customisation available
|
* type: "default" - Button with drop-down options - no further customisation available
|
||||||
|
* label: the text to display - default: "Deploy"
|
||||||
* type: "simple" - Button without dropdown. Customisations:
|
* type: "simple" - Button without dropdown. Customisations:
|
||||||
* label: the text to display - default: "Deploy"
|
* label: the text to display - default: "Deploy"
|
||||||
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
|
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
|
||||||
@ -49,19 +52,20 @@ RED.deploy = (function() {
|
|||||||
function init(options) {
|
function init(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var type = options.type || "default";
|
var type = options.type || "default";
|
||||||
|
var label = options.label || RED._("deploy.deploy");
|
||||||
|
|
||||||
if (type == "default") {
|
if (type == "default") {
|
||||||
$('<li><span class="red-ui-deploy-button-group button-group">'+
|
$('<li><span class="red-ui-deploy-button-group button-group">'+
|
||||||
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
|
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
|
||||||
'<span class="red-ui-deploy-button-content">'+
|
'<span class="red-ui-deploy-button-content">'+
|
||||||
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
|
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
|
||||||
'<span>'+RED._("deploy.deploy")+'</span>'+
|
'<span>'+label+'</span>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'<span class="red-ui-deploy-button-spinner hide">'+
|
'<span class="red-ui-deploy-button-spinner hide">'+
|
||||||
'<img src="red/images/spin.svg"/>'+
|
'<img src="red/images/spin.svg"/>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'</a>'+
|
'</a>'+
|
||||||
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
|
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i><i class="fa fa-lock"></i></a>'+
|
||||||
'</span></li>').prependTo(".red-ui-header-toolbar");
|
'</span></li>').prependTo(".red-ui-header-toolbar");
|
||||||
const mainMenuItems = [
|
const mainMenuItems = [
|
||||||
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||||
@ -76,7 +80,6 @@ RED.deploy = (function() {
|
|||||||
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
||||||
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
||||||
} else if (type == "simple") {
|
} else if (type == "simple") {
|
||||||
var label = options.label || RED._("deploy.deploy");
|
|
||||||
var icon = 'red/images/deploy-full-o.svg';
|
var icon = 'red/images/deploy-full-o.svg';
|
||||||
if (options.hasOwnProperty('icon')) {
|
if (options.hasOwnProperty('icon')) {
|
||||||
icon = options.icon;
|
icon = options.icon;
|
||||||
@ -112,53 +115,80 @@ RED.deploy = (function() {
|
|||||||
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function (event) {
|
||||||
|
if (RED.nodes.dirty()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
event.returnValue = RED._("deploy.confirm.undeployedChanges");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
RED.events.on('workspace:dirty',function(state) {
|
RED.events.on('workspace:dirty',function(state) {
|
||||||
|
if (RED.settings.user?.permissions === 'read') {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (state.dirty) {
|
if (state.dirty) {
|
||||||
window.onbeforeunload = function() {
|
// window.onbeforeunload = function() {
|
||||||
return RED._("deploy.confirm.undeployedChanges");
|
// return
|
||||||
}
|
// }
|
||||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
} else {
|
} else {
|
||||||
window.onbeforeunload = null;
|
// window.onbeforeunload = null;
|
||||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var activeNotifyMessage;
|
|
||||||
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
|
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
|
||||||
if (!activeNotifyMessage) {
|
var currentRev = RED.nodes.version();
|
||||||
var currentRev = RED.nodes.version();
|
if (currentRev === null || deployInflight || currentRev === msg.revision) {
|
||||||
if (currentRev === null || deployInflight || currentRev === msg.revision) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
|
||||||
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
|
activeBackgroundDeployNotification.showNotification()
|
||||||
activeNotifyMessage = RED.notify(message,{
|
return
|
||||||
modal: true,
|
}
|
||||||
fixed: true,
|
const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
|
||||||
buttons: [
|
const options = {
|
||||||
{
|
id: 'background-update',
|
||||||
text: RED._('deploy.confirm.button.ignore'),
|
type: 'compact',
|
||||||
click: function() {
|
modal: false,
|
||||||
activeNotifyMessage.close();
|
fixed: true,
|
||||||
activeNotifyMessage = null;
|
timeout: 10000,
|
||||||
}
|
buttons: [
|
||||||
},
|
{
|
||||||
{
|
text: RED._('deploy.confirm.button.review'),
|
||||||
text: RED._('deploy.confirm.button.review'),
|
class: "primary",
|
||||||
class: "primary",
|
click: function() {
|
||||||
click: function() {
|
activeBackgroundDeployNotification.hideNotification();
|
||||||
activeNotifyMessage.close();
|
var nns = RED.nodes.createCompleteNodeSet();
|
||||||
var nns = RED.nodes.createCompleteNodeSet();
|
resolveConflict(nns,false);
|
||||||
resolveConflict(nns,false);
|
|
||||||
activeNotifyMessage = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
});
|
]
|
||||||
|
}
|
||||||
|
if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
|
||||||
|
activeBackgroundDeployNotification = RED.notify(message, options)
|
||||||
|
} else {
|
||||||
|
activeBackgroundDeployNotification.update(message, options)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
updateLockedState()
|
||||||
|
RED.events.on('login', updateLockedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLockedState() {
|
||||||
|
if (RED.settings.user?.permissions === 'read') {
|
||||||
|
$(".red-ui-deploy-button-group").addClass("readOnly");
|
||||||
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||||
|
} else {
|
||||||
|
$(".red-ui-deploy-button-group").removeClass("readOnly");
|
||||||
|
if (RED.nodes.dirty()) {
|
||||||
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeInfo(node) {
|
function getNodeInfo(node) {
|
||||||
@ -213,7 +243,11 @@ RED.deploy = (function() {
|
|||||||
class: "primary disabled",
|
class: "primary disabled",
|
||||||
click: function() {
|
click: function() {
|
||||||
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
|
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
|
||||||
RED.diff.showRemoteDiff();
|
RED.diff.showRemoteDiff(null, {
|
||||||
|
onmerge: function () {
|
||||||
|
activeBackgroundDeployNotification.close()
|
||||||
|
}
|
||||||
|
});
|
||||||
conflictNotification.close();
|
conflictNotification.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,6 +260,7 @@ RED.deploy = (function() {
|
|||||||
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
|
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
|
||||||
RED.diff.mergeDiff(currentDiff);
|
RED.diff.mergeDiff(currentDiff);
|
||||||
conflictNotification.close();
|
conflictNotification.close();
|
||||||
|
activeBackgroundDeployNotification.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,6 +273,7 @@ RED.deploy = (function() {
|
|||||||
click: function() {
|
click: function() {
|
||||||
save(true,activeDeploy);
|
save(true,activeDeploy);
|
||||||
conflictNotification.close();
|
conflictNotification.close();
|
||||||
|
activeBackgroundDeployNotification.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -248,21 +284,17 @@ RED.deploy = (function() {
|
|||||||
buttons: buttons
|
buttons: buttons
|
||||||
});
|
});
|
||||||
|
|
||||||
var now = Date.now();
|
|
||||||
RED.diff.getRemoteDiff(function(diff) {
|
RED.diff.getRemoteDiff(function(diff) {
|
||||||
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
|
|
||||||
currentDiff = diff;
|
currentDiff = diff;
|
||||||
setTimeout(function() {
|
conflictCheck.hide();
|
||||||
conflictCheck.hide();
|
var d = Object.keys(diff.conflicts);
|
||||||
var d = Object.keys(diff.conflicts);
|
if (d.length === 0) {
|
||||||
if (d.length === 0) {
|
conflictAutoMerge.show();
|
||||||
conflictAutoMerge.show();
|
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
|
||||||
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
|
} else {
|
||||||
} else {
|
conflictManualMerge.show();
|
||||||
conflictManualMerge.show();
|
}
|
||||||
}
|
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
|
||||||
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
|
|
||||||
},ellapsed);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function cropList(list) {
|
function cropList(list) {
|
||||||
@ -393,11 +425,15 @@ RED.deploy = (function() {
|
|||||||
const unknownNodes = [];
|
const unknownNodes = [];
|
||||||
const invalidNodes = [];
|
const invalidNodes = [];
|
||||||
|
|
||||||
|
const isDisabled = function (node) {
|
||||||
|
return (node.d || RED.nodes.workspace(node.z)?.disabled);
|
||||||
|
};
|
||||||
|
|
||||||
RED.nodes.eachConfig(function (node) {
|
RED.nodes.eachConfig(function (node) {
|
||||||
if (node.valid === undefined) {
|
if (node.valid === undefined) {
|
||||||
RED.editor.validateNode(node);
|
RED.editor.validateNode(node);
|
||||||
}
|
}
|
||||||
if (!node.valid && !node.d) {
|
if (!node.valid && !isDisabled(node)) {
|
||||||
invalidNodes.push(getNodeInfo(node));
|
invalidNodes.push(getNodeInfo(node));
|
||||||
}
|
}
|
||||||
if (node.type === "unknown") {
|
if (node.type === "unknown") {
|
||||||
@ -407,7 +443,7 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.nodes.eachNode(function (node) {
|
RED.nodes.eachNode(function (node) {
|
||||||
if (!node.valid && !node.d) {
|
if (!node.valid && !isDisabled(node)) {
|
||||||
invalidNodes.push(getNodeInfo(node));
|
invalidNodes.push(getNodeInfo(node));
|
||||||
}
|
}
|
||||||
if (node.type === "unknown") {
|
if (node.type === "unknown") {
|
||||||
@ -421,7 +457,7 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
const unusedConfigNodes = [];
|
const unusedConfigNodes = [];
|
||||||
RED.nodes.eachConfig(function (node) {
|
RED.nodes.eachConfig(function (node) {
|
||||||
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
|
if ((node._def.hasUsers !== false) && (node.users.length === 0) && !isDisabled(node)) {
|
||||||
unusedConfigNodes.push(getNodeInfo(node));
|
unusedConfigNodes.push(getNodeInfo(node));
|
||||||
hasUnusedConfig = true;
|
hasUnusedConfig = true;
|
||||||
}
|
}
|
||||||
@ -558,7 +594,9 @@ RED.deploy = (function() {
|
|||||||
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
||||||
}
|
}
|
||||||
const flowsToLock = new Set()
|
const flowsToLock = new Set()
|
||||||
|
// Node's properties cannot be modified if its workspace is locked.
|
||||||
function ensureUnlocked(id) {
|
function ensureUnlocked(id) {
|
||||||
|
// TODO: `RED.nodes.subflow` is useless
|
||||||
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
const isLocked = flow ? flow.locked : false;
|
const isLocked = flow ? flow.locked : false;
|
||||||
if (flow && isLocked) {
|
if (flow && isLocked) {
|
||||||
@ -611,6 +649,7 @@ RED.deploy = (function() {
|
|||||||
delete confNode.credentials;
|
delete confNode.credentials;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Subflow cannot be locked
|
||||||
RED.nodes.eachSubflow(function (subflow) {
|
RED.nodes.eachSubflow(function (subflow) {
|
||||||
if (subflow.changed) {
|
if (subflow.changed) {
|
||||||
subflow.changed = false;
|
subflow.changed = false;
|
||||||
@ -619,12 +658,18 @@ RED.deploy = (function() {
|
|||||||
});
|
});
|
||||||
RED.nodes.eachWorkspace(function (ws) {
|
RED.nodes.eachWorkspace(function (ws) {
|
||||||
if (ws.changed || ws.added) {
|
if (ws.changed || ws.added) {
|
||||||
ensureUnlocked(ws.z)
|
// Ensure the Workspace is unlocked to modify its properties.
|
||||||
|
ensureUnlocked(ws.id);
|
||||||
ws.changed = false;
|
ws.changed = false;
|
||||||
delete ws.added
|
delete ws.added
|
||||||
|
if (flowsToLock.has(ws)) {
|
||||||
|
ws.locked = true;
|
||||||
|
flowsToLock.delete(ws);
|
||||||
|
}
|
||||||
RED.events.emit("flows:change", ws)
|
RED.events.emit("flows:change", ws)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Ensures all workspaces to be locked have been locked.
|
||||||
flowsToLock.forEach(flow => {
|
flowsToLock.forEach(flow => {
|
||||||
flow.locked = true
|
flow.locked = true
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
RED.diff = (function() {
|
RED.diff = (function() {
|
||||||
|
|
||||||
var currentDiff = {};
|
var currentDiff = {};
|
||||||
var diffVisible = false;
|
var diffVisible = false;
|
||||||
var diffList;
|
var diffList;
|
||||||
@ -62,12 +61,14 @@ RED.diff = (function() {
|
|||||||
addedCount:0,
|
addedCount:0,
|
||||||
deletedCount:0,
|
deletedCount:0,
|
||||||
changedCount:0,
|
changedCount:0,
|
||||||
|
movedCount:0,
|
||||||
unchangedCount: 0
|
unchangedCount: 0
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
addedCount:0,
|
addedCount:0,
|
||||||
deletedCount:0,
|
deletedCount:0,
|
||||||
changedCount:0,
|
changedCount:0,
|
||||||
|
movedCount:0,
|
||||||
unchangedCount: 0
|
unchangedCount: 0
|
||||||
},
|
},
|
||||||
conflicts: 0
|
conflicts: 0
|
||||||
@ -138,7 +139,7 @@ RED.diff = (function() {
|
|||||||
$(this).parent().toggleClass('collapsed');
|
$(this).parent().toggleClass('collapsed');
|
||||||
});
|
});
|
||||||
|
|
||||||
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
|
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
|
||||||
selectState = "";
|
selectState = "";
|
||||||
if (conflicts[tab.id]) {
|
if (conflicts[tab.id]) {
|
||||||
flowStats.conflicts++;
|
flowStats.conflicts++;
|
||||||
@ -208,19 +209,26 @@ RED.diff = (function() {
|
|||||||
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
|
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
|
||||||
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
|
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
|
||||||
|
|
||||||
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
|
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
|
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
|
||||||
if (flowStats.conflicts > 0) {
|
if (flowStats.conflicts > 0) {
|
||||||
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
|
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
|
||||||
}
|
}
|
||||||
if (flowStats.local.addedCount > 0) {
|
if (flowStats.local.addedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
|
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.added'))
|
||||||
}
|
}
|
||||||
if (flowStats.local.changedCount > 0) {
|
if (flowStats.local.changedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
|
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.changed'))
|
||||||
|
}
|
||||||
|
if (flowStats.local.movedCount > 0) {
|
||||||
|
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.movedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.moved'))
|
||||||
}
|
}
|
||||||
if (flowStats.local.deletedCount > 0) {
|
if (flowStats.local.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
|
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
|
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
|
||||||
}
|
}
|
||||||
@ -246,19 +254,26 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
|
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
|
||||||
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
|
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
|
||||||
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
|
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
|
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
|
||||||
if (flowStats.conflicts > 0) {
|
if (flowStats.conflicts > 0) {
|
||||||
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
|
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
|
||||||
}
|
}
|
||||||
if (flowStats.remote.addedCount > 0) {
|
if (flowStats.remote.addedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
|
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.added'))
|
||||||
}
|
}
|
||||||
if (flowStats.remote.changedCount > 0) {
|
if (flowStats.remote.changedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
|
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.changed'))
|
||||||
|
}
|
||||||
|
if (flowStats.remote.movedCount > 0) {
|
||||||
|
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.movedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.moved'))
|
||||||
}
|
}
|
||||||
if (flowStats.remote.deletedCount > 0) {
|
if (flowStats.remote.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
|
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
|
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
|
||||||
}
|
}
|
||||||
@ -293,7 +308,7 @@ RED.diff = (function() {
|
|||||||
if (options.mode === "merge") {
|
if (options.mode === "merge") {
|
||||||
diffPanel.addClass("red-ui-diff-panel-merge");
|
diffPanel.addClass("red-ui-diff-panel-merge");
|
||||||
}
|
}
|
||||||
var diffList = createDiffTable(diffPanel, diff);
|
var diffList = createDiffTable(diffPanel, diff, options);
|
||||||
|
|
||||||
var localDiff = diff.localDiff;
|
var localDiff = diff.localDiff;
|
||||||
var remoteDiff = diff.remoteDiff;
|
var remoteDiff = diff.remoteDiff;
|
||||||
@ -482,7 +497,7 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (c === 0) {
|
if (c === 0) {
|
||||||
result.text("none");
|
result.text(RED._("diff.type.none"));
|
||||||
} else {
|
} else {
|
||||||
list.appendTo(result);
|
list.appendTo(result);
|
||||||
}
|
}
|
||||||
@ -516,7 +531,6 @@ RED.diff = (function() {
|
|||||||
|
|
||||||
var hasChanges = false; // exists in original and local/remote but with changes
|
var hasChanges = false; // exists in original and local/remote but with changes
|
||||||
var unChanged = true; // existing in original,local,remote unchanged
|
var unChanged = true; // existing in original,local,remote unchanged
|
||||||
var localChanged = false;
|
|
||||||
|
|
||||||
if (localDiff.added[node.id]) {
|
if (localDiff.added[node.id]) {
|
||||||
stats.local.addedCount++;
|
stats.local.addedCount++;
|
||||||
@ -535,12 +549,20 @@ RED.diff = (function() {
|
|||||||
unChanged = false;
|
unChanged = false;
|
||||||
}
|
}
|
||||||
if (localDiff.changed[node.id]) {
|
if (localDiff.changed[node.id]) {
|
||||||
stats.local.changedCount++;
|
if (localDiff.positionChanged[node.id]) {
|
||||||
|
stats.local.movedCount++
|
||||||
|
} else {
|
||||||
|
stats.local.changedCount++;
|
||||||
|
}
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
unChanged = false;
|
unChanged = false;
|
||||||
}
|
}
|
||||||
if (remoteDiff && remoteDiff.changed[node.id]) {
|
if (remoteDiff && remoteDiff.changed[node.id]) {
|
||||||
stats.remote.changedCount++;
|
if (remoteDiff.positionChanged[node.id]) {
|
||||||
|
stats.remote.movedCount++
|
||||||
|
} else {
|
||||||
|
stats.remote.changedCount++;
|
||||||
|
}
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
unChanged = false;
|
unChanged = false;
|
||||||
}
|
}
|
||||||
@ -605,27 +627,32 @@ RED.diff = (function() {
|
|||||||
localNodeDiv.addClass("red-ui-diff-status-moved");
|
localNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
var localMovedMessage = "";
|
var localMovedMessage = "";
|
||||||
if (node.z === localN.z) {
|
if (node.z === localN.z) {
|
||||||
localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
|
const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z]
|
||||||
|
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
|
||||||
|
localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel});
|
||||||
} else {
|
} else {
|
||||||
localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
|
const movedToNodeTab = localDiff.newConfig.all[localN.z]
|
||||||
|
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
|
||||||
|
localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
|
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
|
||||||
}
|
}
|
||||||
localChanged = true;
|
|
||||||
} else if (localDiff.deleted[node.z]) {
|
} else if (localDiff.deleted[node.z]) {
|
||||||
localNodeDiv.addClass("red-ui-diff-empty");
|
localNodeDiv.addClass("red-ui-diff-empty");
|
||||||
localChanged = true;
|
|
||||||
} else if (localDiff.deleted[node.id]) {
|
} else if (localDiff.deleted[node.id]) {
|
||||||
localNodeDiv.addClass("red-ui-diff-status-deleted");
|
localNodeDiv.addClass("red-ui-diff-status-deleted");
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
|
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
|
||||||
localChanged = true;
|
|
||||||
} else if (localDiff.changed[node.id]) {
|
} else if (localDiff.changed[node.id]) {
|
||||||
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
localNodeDiv.addClass("red-ui-diff-empty");
|
localNodeDiv.addClass("red-ui-diff-empty");
|
||||||
} else {
|
} else {
|
||||||
localNodeDiv.addClass("red-ui-diff-status-changed");
|
if (localDiff.positionChanged[node.id]) {
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
|
localNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
localChanged = true;
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(localNodeDiv);
|
||||||
|
} else {
|
||||||
|
localNodeDiv.addClass("red-ui-diff-status-changed");
|
||||||
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
@ -646,9 +673,13 @@ RED.diff = (function() {
|
|||||||
remoteNodeDiv.addClass("red-ui-diff-status-moved");
|
remoteNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
var remoteMovedMessage = "";
|
var remoteMovedMessage = "";
|
||||||
if (node.z === remoteN.z) {
|
if (node.z === remoteN.z) {
|
||||||
remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
|
const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z]
|
||||||
|
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
|
||||||
|
remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel});
|
||||||
} else {
|
} else {
|
||||||
remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
|
const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z]
|
||||||
|
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
|
||||||
|
remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
|
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
|
||||||
}
|
}
|
||||||
@ -661,8 +692,13 @@ RED.diff = (function() {
|
|||||||
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
remoteNodeDiv.addClass("red-ui-diff-empty");
|
remoteNodeDiv.addClass("red-ui-diff-empty");
|
||||||
} else {
|
} else {
|
||||||
remoteNodeDiv.addClass("red-ui-diff-status-changed");
|
if (remoteDiff.positionChanged[node.id]) {
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
|
remoteNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(remoteNodeDiv);
|
||||||
|
} else {
|
||||||
|
remoteNodeDiv.addClass("red-ui-diff-status-changed");
|
||||||
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
@ -785,10 +821,10 @@ RED.diff = (function() {
|
|||||||
conflict = true;
|
conflict = true;
|
||||||
}
|
}
|
||||||
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
||||||
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
|
$("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.position")).appendTo(row);
|
||||||
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
||||||
if (localNode) {
|
if (localNode) {
|
||||||
localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
|
localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
|
||||||
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
||||||
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
|
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
|
||||||
var localPosition = {x:localNode.x,y:localNode.y};
|
var localPosition = {x:localNode.x,y:localNode.y};
|
||||||
@ -813,7 +849,7 @@ RED.diff = (function() {
|
|||||||
|
|
||||||
if (remoteNode !== undefined) {
|
if (remoteNode !== undefined) {
|
||||||
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
|
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
|
||||||
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged"));
|
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged"));
|
||||||
if (remoteNode) {
|
if (remoteNode) {
|
||||||
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
||||||
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
|
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
|
||||||
@ -863,7 +899,7 @@ RED.diff = (function() {
|
|||||||
conflict = true;
|
conflict = true;
|
||||||
}
|
}
|
||||||
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
||||||
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("wires").appendTo(row);
|
$("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.wires")).appendTo(row);
|
||||||
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
||||||
if (localNode) {
|
if (localNode) {
|
||||||
if (!conflict) {
|
if (!conflict) {
|
||||||
@ -1099,11 +1135,11 @@ RED.diff = (function() {
|
|||||||
// var diff = generateDiff(originalFlow,nns);
|
// var diff = generateDiff(originalFlow,nns);
|
||||||
// showDiff(diff);
|
// showDiff(diff);
|
||||||
// }
|
// }
|
||||||
function showRemoteDiff(diff) {
|
function showRemoteDiff(diff, options = {}) {
|
||||||
if (diff === undefined) {
|
if (!diff) {
|
||||||
getRemoteDiff(showRemoteDiff);
|
getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
|
||||||
} else {
|
} else {
|
||||||
showDiff(diff,{mode:'merge'});
|
showDiff(diff,{...options, mode:'merge'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function parseNodes(nodeList) {
|
function parseNodes(nodeList) {
|
||||||
@ -1144,23 +1180,53 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function generateDiff(currentNodes,newNodes) {
|
function generateDiff(currentNodes,newNodes) {
|
||||||
var currentConfig = parseNodes(currentNodes);
|
const currentConfig = parseNodes(currentNodes);
|
||||||
var newConfig = parseNodes(newNodes);
|
const newConfig = parseNodes(newNodes);
|
||||||
var added = {};
|
const added = {};
|
||||||
var deleted = {};
|
const deleted = {};
|
||||||
var changed = {};
|
const changed = {};
|
||||||
var moved = {};
|
const positionChanged = {};
|
||||||
|
const moved = {};
|
||||||
|
|
||||||
Object.keys(currentConfig.all).forEach(function(id) {
|
Object.keys(currentConfig.all).forEach(function(id) {
|
||||||
var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
|
const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
|
||||||
if (!newConfig.all.hasOwnProperty(id)) {
|
if (!newConfig.all.hasOwnProperty(id)) {
|
||||||
deleted[id] = true;
|
deleted[id] = true;
|
||||||
} else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
|
return
|
||||||
changed[id] = true;
|
}
|
||||||
|
const currentConfigJSON = JSON.stringify(currentConfig.all[id])
|
||||||
|
const newConfigJSON = JSON.stringify(newConfig.all[id])
|
||||||
|
|
||||||
|
if (currentConfigJSON !== newConfigJSON) {
|
||||||
|
changed[id] = true;
|
||||||
if (currentConfig.all[id].z !== newConfig.all[id].z) {
|
if (currentConfig.all[id].z !== newConfig.all[id].z) {
|
||||||
moved[id] = true;
|
moved[id] = true;
|
||||||
|
} else if (
|
||||||
|
currentConfig.all[id].x !== newConfig.all[id].x ||
|
||||||
|
currentConfig.all[id].y !== newConfig.all[id].y ||
|
||||||
|
currentConfig.all[id].w !== newConfig.all[id].w ||
|
||||||
|
currentConfig.all[id].h !== newConfig.all[id].h
|
||||||
|
) {
|
||||||
|
// This node's position on its parent has changed. We want to
|
||||||
|
// check if this is the *only* change for this given node
|
||||||
|
const currentNodeClone = JSON.parse(currentConfigJSON)
|
||||||
|
const newNodeClone = JSON.parse(newConfigJSON)
|
||||||
|
|
||||||
|
delete currentNodeClone.x
|
||||||
|
delete currentNodeClone.y
|
||||||
|
delete currentNodeClone.w
|
||||||
|
delete currentNodeClone.h
|
||||||
|
delete newNodeClone.x
|
||||||
|
delete newNodeClone.y
|
||||||
|
delete newNodeClone.w
|
||||||
|
delete newNodeClone.h
|
||||||
|
|
||||||
|
if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) {
|
||||||
|
// Only the position has changed - everything else is the same
|
||||||
|
positionChanged[id] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Object.keys(newConfig.all).forEach(function(id) {
|
Object.keys(newConfig.all).forEach(function(id) {
|
||||||
@ -1169,13 +1235,14 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var diff = {
|
const diff = {
|
||||||
currentConfig: currentConfig,
|
currentConfig,
|
||||||
newConfig: newConfig,
|
newConfig,
|
||||||
added: added,
|
added,
|
||||||
deleted: deleted,
|
deleted,
|
||||||
changed: changed,
|
changed,
|
||||||
moved: moved
|
positionChanged,
|
||||||
|
moved
|
||||||
};
|
};
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
@ -1240,13 +1307,15 @@ RED.diff = (function() {
|
|||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDiff(diff,options) {
|
function showDiff(diff, options) {
|
||||||
if (diffVisible) {
|
if (diffVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var mode = options.mode || 'merge';
|
var mode = options.mode || 'merge';
|
||||||
|
|
||||||
|
options.hidePositionChanges = true
|
||||||
|
|
||||||
var localDiff = diff.localDiff;
|
var localDiff = diff.localDiff;
|
||||||
var remoteDiff = diff.remoteDiff;
|
var remoteDiff = diff.remoteDiff;
|
||||||
var conflicts = diff.conflicts;
|
var conflicts = diff.conflicts;
|
||||||
@ -1315,6 +1384,9 @@ RED.diff = (function() {
|
|||||||
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
|
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
|
||||||
refreshConflictHeader(diff);
|
refreshConflictHeader(diff);
|
||||||
mergeDiff(diff);
|
mergeDiff(diff);
|
||||||
|
if (options.onmerge) {
|
||||||
|
options.onmerge()
|
||||||
|
}
|
||||||
RED.tray.close();
|
RED.tray.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1345,6 +1417,7 @@ RED.diff = (function() {
|
|||||||
var newConfig = [];
|
var newConfig = [];
|
||||||
var node;
|
var node;
|
||||||
var nodeChangedStates = {};
|
var nodeChangedStates = {};
|
||||||
|
var nodeMovedStates = {};
|
||||||
var localChangedStates = {};
|
var localChangedStates = {};
|
||||||
for (id in localDiff.newConfig.all) {
|
for (id in localDiff.newConfig.all) {
|
||||||
if (localDiff.newConfig.all.hasOwnProperty(id)) {
|
if (localDiff.newConfig.all.hasOwnProperty(id)) {
|
||||||
@ -1352,12 +1425,14 @@ RED.diff = (function() {
|
|||||||
if (resolutions[id] === 'local') {
|
if (resolutions[id] === 'local') {
|
||||||
if (node) {
|
if (node) {
|
||||||
nodeChangedStates[id] = node.changed;
|
nodeChangedStates[id] = node.changed;
|
||||||
|
nodeMovedStates[id] = node.moved;
|
||||||
}
|
}
|
||||||
newConfig.push(localDiff.newConfig.all[id]);
|
newConfig.push(localDiff.newConfig.all[id]);
|
||||||
} else if (resolutions[id] === 'remote') {
|
} else if (resolutions[id] === 'remote') {
|
||||||
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
||||||
if (node) {
|
if (node) {
|
||||||
nodeChangedStates[id] = node.changed;
|
nodeChangedStates[id] = node.changed;
|
||||||
|
nodeMovedStates[id] = node.moved;
|
||||||
}
|
}
|
||||||
localChangedStates[id] = 1;
|
localChangedStates[id] = 1;
|
||||||
newConfig.push(remoteDiff.newConfig.all[id]);
|
newConfig.push(remoteDiff.newConfig.all[id]);
|
||||||
@ -1381,8 +1456,9 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
config: newConfig,
|
config: newConfig,
|
||||||
nodeChangedStates: nodeChangedStates,
|
nodeChangedStates,
|
||||||
localChangedStates: localChangedStates
|
nodeMovedStates,
|
||||||
|
localChangedStates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1393,6 +1469,7 @@ RED.diff = (function() {
|
|||||||
|
|
||||||
var newConfig = appliedDiff.config;
|
var newConfig = appliedDiff.config;
|
||||||
var nodeChangedStates = appliedDiff.nodeChangedStates;
|
var nodeChangedStates = appliedDiff.nodeChangedStates;
|
||||||
|
var nodeMovedStates = appliedDiff.nodeMovedStates;
|
||||||
var localChangedStates = appliedDiff.localChangedStates;
|
var localChangedStates = appliedDiff.localChangedStates;
|
||||||
|
|
||||||
var isDirty = RED.nodes.dirty();
|
var isDirty = RED.nodes.dirty();
|
||||||
@ -1401,33 +1478,56 @@ RED.diff = (function() {
|
|||||||
t:"replace",
|
t:"replace",
|
||||||
config: RED.nodes.createCompleteNodeSet(),
|
config: RED.nodes.createCompleteNodeSet(),
|
||||||
changed: nodeChangedStates,
|
changed: nodeChangedStates,
|
||||||
|
moved: nodeMovedStates,
|
||||||
|
complete: true,
|
||||||
dirty: isDirty,
|
dirty: isDirty,
|
||||||
rev: RED.nodes.version()
|
rev: RED.nodes.version()
|
||||||
}
|
}
|
||||||
|
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
|
|
||||||
var originalFlow = RED.nodes.originalFlow();
|
// var originalFlow = RED.nodes.originalFlow();
|
||||||
// originalFlow is what the editor things it loaded
|
// // originalFlow is what the editor thinks it loaded
|
||||||
// - add any newly added nodes from remote diff as they are now part of the record
|
// // - add any newly added nodes from remote diff as they are now part of the record
|
||||||
for (var id in diff.remoteDiff.added) {
|
// for (var id in diff.remoteDiff.added) {
|
||||||
if (diff.remoteDiff.added.hasOwnProperty(id)) {
|
// if (diff.remoteDiff.added.hasOwnProperty(id)) {
|
||||||
if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
// if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
||||||
originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
|
// originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
RED.nodes.clear();
|
RED.nodes.clear();
|
||||||
var imported = RED.nodes.import(newConfig);
|
var imported = RED.nodes.import(newConfig);
|
||||||
|
|
||||||
// Restore the original flow so subsequent merge resolutions can properly
|
// // Restore the original flow so subsequent merge resolutions can properly
|
||||||
// identify new-vs-old
|
// // identify new-vs-old
|
||||||
RED.nodes.originalFlow(originalFlow);
|
// RED.nodes.originalFlow(originalFlow);
|
||||||
|
|
||||||
|
// Clear all change flags from the import
|
||||||
|
RED.nodes.dirty(false);
|
||||||
|
|
||||||
|
const flowsToLock = new Set()
|
||||||
|
function ensureUnlocked(id) {
|
||||||
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
|
const isLocked = flow ? flow.locked : false;
|
||||||
|
if (flow && isLocked) {
|
||||||
|
flow.locked = false;
|
||||||
|
flowsToLock.add(flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
imported.nodes.forEach(function(n) {
|
imported.nodes.forEach(function(n) {
|
||||||
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
|
if (nodeChangedStates[n.id]) {
|
||||||
|
ensureUnlocked(n.z)
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
}
|
}
|
||||||
|
if (nodeMovedStates[n.id]) {
|
||||||
|
ensureUnlocked(n.z)
|
||||||
|
n.moved = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
flowsToLock.forEach(flow => {
|
||||||
|
flow.locked = true
|
||||||
})
|
})
|
||||||
|
|
||||||
RED.nodes.version(diff.remoteDiff.rev);
|
RED.nodes.version(diff.remoteDiff.rev);
|
||||||
@ -1929,15 +2029,14 @@ RED.diff = (function() {
|
|||||||
if (!isSeparator) {
|
if (!isSeparator) {
|
||||||
var isOurs = /^..<<<<<<</.test(lineText);
|
var isOurs = /^..<<<<<<</.test(lineText);
|
||||||
if (isOurs) {
|
if (isOurs) {
|
||||||
$('<span>').text("<<<<<<< Local Changes").appendTo(line);
|
$('<span>').text("<<<<<<< " + RED._("diff.localChanges")).appendTo(line);
|
||||||
hunk.localChangeStart = actualLineNumber;
|
hunk.localChangeStart = actualLineNumber;
|
||||||
} else {
|
} else {
|
||||||
hunk.remoteChangeEnd = actualLineNumber;
|
hunk.remoteChangeEnd = actualLineNumber;
|
||||||
$('<span>').text(">>>>>>> Remote Changes").appendTo(line);
|
$('<span>').text(">>>>>>> " + RED._("diff.remoteChanges")).appendTo(line);
|
||||||
|
|
||||||
}
|
}
|
||||||
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
|
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
|
||||||
$('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
|
$('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> '+RED._(isOurs?"diff.useLocalChanges":"diff.useRemoteChanges")+'</button>')
|
||||||
.appendTo(line)
|
.appendTo(line)
|
||||||
.on("click", function(evt) {
|
.on("click", function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@ -2019,7 +2118,7 @@ RED.diff = (function() {
|
|||||||
$("<h3>").text(commit.title).appendTo(content);
|
$("<h3>").text(commit.title).appendTo(content);
|
||||||
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
|
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
|
||||||
var summary = $('<div class="commit-summary"></div>').appendTo(content);
|
var summary = $('<div class="commit-summary"></div>').appendTo(content);
|
||||||
$('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
|
$('<div style="float: right">').text(RED._('diff.commit')+" "+commit.sha).appendTo(summary);
|
||||||
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
|
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
|
||||||
|
|
||||||
if (commit.files) {
|
if (commit.files) {
|
||||||
|
@ -157,6 +157,12 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (valid && "validate" in definition[property]) {
|
if (valid && "validate" in definition[property]) {
|
||||||
|
if (definition[property].hasOwnProperty("required") &&
|
||||||
|
definition[property].required === false) {
|
||||||
|
if (value === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
var opt = {};
|
var opt = {};
|
||||||
if (label) {
|
if (label) {
|
||||||
@ -183,6 +189,11 @@ RED.editor = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (valid) {
|
} else if (valid) {
|
||||||
|
if (definition[property].hasOwnProperty("required") && definition[property].required === false) {
|
||||||
|
if (value === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
// If the validator is not provided in node property => Check if the input has a validator
|
// If the validator is not provided in node property => Check if the input has a validator
|
||||||
if ("category" in node._def) {
|
if ("category" in node._def) {
|
||||||
const isConfig = node._def.category === "config";
|
const isConfig = node._def.category === "config";
|
||||||
@ -190,7 +201,10 @@ RED.editor = (function() {
|
|||||||
const input = $("#"+prefix+"-"+property);
|
const input = $("#"+prefix+"-"+property);
|
||||||
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
|
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
|
||||||
if (isTypedInput) {
|
if (isTypedInput) {
|
||||||
valid = input.typedInput("validate");
|
valid = input.typedInput("validate", { returnErrorMessage: true });
|
||||||
|
if (typeof valid === "string") {
|
||||||
|
return label ? label + ": " + valid : valid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,49 +342,101 @@ RED.editor = (function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a config-node select box for this property
|
* Create a config-node select box for this property
|
||||||
* @param node - the node being edited
|
* @param {Object} node - the node being edited
|
||||||
* @param property - the name of the field
|
* @param {String} property - the name of the node property
|
||||||
* @param type - the type of the config-node
|
* @param {String} type - the type of the config-node
|
||||||
|
* @param {"node-config-input"|"node-input"|"node-input-subflow-env"} prefix - the prefix to use in the input element ids
|
||||||
|
* @param {Function} [filter] - a function to filter the list of config nodes
|
||||||
|
* @param {Object} [env] - the environment variable object (only used for subflow env vars)
|
||||||
*/
|
*/
|
||||||
function prepareConfigNodeSelect(node,property,type,prefix,filter) {
|
function prepareConfigNodeSelect(node, property, type, prefix, filter, env) {
|
||||||
var input = $("#"+prefix+"-"+property);
|
let nodeValue
|
||||||
if (input.length === 0 ) {
|
if (prefix === 'node-input-subflow-env') {
|
||||||
|
nodeValue = env?.value
|
||||||
|
} else {
|
||||||
|
nodeValue = node[property]
|
||||||
|
}
|
||||||
|
|
||||||
|
const addBtnId = `${prefix}-btn-${property}-add`;
|
||||||
|
const editBtnId = `${prefix}-btn-${property}-edit`;
|
||||||
|
const selectId = prefix + '-' + property;
|
||||||
|
const input = $(`#${selectId}`);
|
||||||
|
if (input.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var newWidth = input.width();
|
const attrStyle = input.attr('style');
|
||||||
var attrStyle = input.attr('style');
|
let newWidth;
|
||||||
var m;
|
let m;
|
||||||
if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) {
|
if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) {
|
||||||
newWidth = m[2].trim();
|
newWidth = m[2].trim();
|
||||||
} else {
|
} else {
|
||||||
newWidth = "70%";
|
newWidth = "70%";
|
||||||
}
|
}
|
||||||
var outerWrap = $("<div></div>").css({
|
const outerWrap = $("<div></div>").css({
|
||||||
width: newWidth,
|
width: newWidth,
|
||||||
display:'inline-flex'
|
display: 'inline-flex'
|
||||||
});
|
});
|
||||||
var select = $('<select id="'+prefix+'-'+property+'"></select>').appendTo(outerWrap);
|
const select = $('<select id="' + selectId + '"></select>').appendTo(outerWrap);
|
||||||
input.replaceWith(outerWrap);
|
input.replaceWith(outerWrap);
|
||||||
// set the style attr directly - using width() on FF causes a value of 114%...
|
// set the style attr directly - using width() on FF causes a value of 114%...
|
||||||
select.css({
|
select.css({
|
||||||
'flex-grow': 1
|
'flex-grow': 1
|
||||||
});
|
});
|
||||||
updateConfigNodeSelect(property,type,node[property],prefix,filter);
|
|
||||||
$('<a id="'+prefix+'-lookup-'+property+'" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
|
updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
|
||||||
.css({"margin-left":"10px"})
|
|
||||||
|
// create the edit button
|
||||||
|
const editButton = $('<a id="' + editBtnId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
|
||||||
|
.css({ "margin-left": "10px" })
|
||||||
.appendTo(outerWrap);
|
.appendTo(outerWrap);
|
||||||
$('#'+prefix+'-lookup-'+property).on("click", function(e) {
|
|
||||||
showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix,node);
|
RED.popover.tooltip(editButton, RED._('editor.editConfig', { type }));
|
||||||
|
|
||||||
|
// create the add button
|
||||||
|
const addButton = $('<a id="' + addBtnId + '" class="red-ui-button"><i class="fa fa-plus"></i></a>')
|
||||||
|
.css({ "margin-left": "10px" })
|
||||||
|
.appendTo(outerWrap);
|
||||||
|
RED.popover.tooltip(addButton, RED._('editor.addNewConfig', { type }));
|
||||||
|
|
||||||
|
const disableButton = function(button, disabled) {
|
||||||
|
$(button).prop("disabled", !!disabled)
|
||||||
|
$(button).toggleClass("disabled", !!disabled)
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the click handler
|
||||||
|
addButton.on("click", function (e) {
|
||||||
|
if (addButton.prop("disabled")) { return }
|
||||||
|
showEditConfigNodeDialog(property, type, "_ADD_", prefix, node);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
editButton.on("click", function (e) {
|
||||||
|
const selectedOpt = select.find(":selected")
|
||||||
|
if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog)
|
||||||
|
if (editButton.prop("disabled")) { return }
|
||||||
|
showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
var label = "";
|
|
||||||
var configNode = RED.nodes.node(node[property]);
|
|
||||||
var node_def = RED.nodes.getType(type);
|
|
||||||
|
|
||||||
if (configNode) {
|
// dont permit the user to click the button if the selected option is an env var
|
||||||
label = RED.utils.getNodeLabel(configNode,configNode.id);
|
select.on("change", function () {
|
||||||
}
|
const selectedOpt = select.find(":selected");
|
||||||
input.val(label);
|
const optionsLength = select.find("option").length;
|
||||||
|
if (selectedOpt?.data('env')) {
|
||||||
|
disableButton(addButton, true);
|
||||||
|
disableButton(editButton, true);
|
||||||
|
// disable the edit button if no options available or 'none' selected
|
||||||
|
} else if (optionsLength === 1 || selectedOpt.val() === "_ADD_") {
|
||||||
|
disableButton(addButton, false);
|
||||||
|
disableButton(editButton, true);
|
||||||
|
} else {
|
||||||
|
disableButton(addButton, false);
|
||||||
|
disableButton(editButton, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the value is "", 'add new...' option if no config node available or 'none' option
|
||||||
|
// Otherwise, it's a config node
|
||||||
|
select.val(nodeValue || '_ADD_');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -742,6 +808,20 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldCreds = {};
|
||||||
|
if (editing_node._def.credentials) {
|
||||||
|
for (const prop in editing_node._def.credentials) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
|
||||||
|
if (editing_node._def.credentials[prop].type === 'password') {
|
||||||
|
oldCreds['has_' + prop] = editing_node.credentials['has_' + prop];
|
||||||
|
}
|
||||||
|
if (prop in editing_node.credentials) {
|
||||||
|
oldCreds[prop] = editing_node.credentials[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rc = editing_node._def.oneditsave.call(editing_node);
|
const rc = editing_node._def.oneditsave.call(editing_node);
|
||||||
if (rc === true) {
|
if (rc === true) {
|
||||||
@ -773,16 +853,32 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editing_node._def.credentials) {
|
||||||
|
for (const prop in editing_node._def.credentials) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
|
||||||
|
if (oldCreds[prop] !== editing_node.credentials[prop]) {
|
||||||
|
if (editing_node.credentials[prop] === '__PWRD__') {
|
||||||
|
// The password may not exist in oldCreds
|
||||||
|
// The value '__PWRD__' means the password exists,
|
||||||
|
// so ignore this change
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editState.changes.credentials = editState.changes.credentials || {};
|
||||||
|
editState.changes.credentials['has_' + prop] = oldCreds['has_' + prop];
|
||||||
|
editState.changes.credentials[prop] = oldCreds[prop];
|
||||||
|
editState.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultConfigNodeSort(A,B) {
|
function defaultConfigNodeSort(A,B) {
|
||||||
if (A.__label__ < B.__label__) {
|
// sort case insensitive so that `[env] node-name` items are at the top and
|
||||||
return -1;
|
// not mixed inbetween the the lower and upper case items
|
||||||
} else if (A.__label__ > B.__label__) {
|
return (A.__label__ || '').localeCompare((B.__label__ || ''), undefined, {sensitivity: 'base'})
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConfigNodeSelect(name,type,value,prefix,filter) {
|
function updateConfigNodeSelect(name,type,value,prefix,filter) {
|
||||||
@ -797,7 +893,7 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
$("#"+prefix+"-"+name).val(value);
|
$("#"+prefix+"-"+name).val(value);
|
||||||
} else {
|
} else {
|
||||||
|
let inclSubflowEnvvars = false
|
||||||
var select = $("#"+prefix+"-"+name);
|
var select = $("#"+prefix+"-"+name);
|
||||||
var node_def = RED.nodes.getType(type);
|
var node_def = RED.nodes.getType(type);
|
||||||
select.children().remove();
|
select.children().remove();
|
||||||
@ -805,6 +901,7 @@ RED.editor = (function() {
|
|||||||
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
||||||
if (!activeWorkspace) {
|
if (!activeWorkspace) {
|
||||||
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
||||||
|
inclSubflowEnvvars = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var configNodes = [];
|
var configNodes = [];
|
||||||
@ -820,6 +917,31 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// as includeSubflowEnvvars is true, this is a subflow.
|
||||||
|
// include any 'conf-types' env vars as a list of avaiable configs
|
||||||
|
// in the config dropdown as `[env] node-name`
|
||||||
|
if (inclSubflowEnvvars && activeWorkspace.env) {
|
||||||
|
const parentEnv = activeWorkspace.env.filter(env => env.ui?.type === 'conf-types' && env.type === type)
|
||||||
|
if (parentEnv && parentEnv.length > 0) {
|
||||||
|
const locale = RED.i18n.lang()
|
||||||
|
for (let i = 0; i < parentEnv.length; i++) {
|
||||||
|
const tenv = parentEnv[i]
|
||||||
|
const ui = tenv.ui || {}
|
||||||
|
const labels = ui.label || {}
|
||||||
|
const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale)
|
||||||
|
const config = {
|
||||||
|
env: tenv,
|
||||||
|
id: '${' + tenv.name + '}',
|
||||||
|
type: type,
|
||||||
|
label: labelText,
|
||||||
|
__label__: `[env] ${labelText}`
|
||||||
|
}
|
||||||
|
configNodes.push(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var configSortFn = defaultConfigNodeSort;
|
var configSortFn = defaultConfigNodeSort;
|
||||||
if (typeof node_def.sort == "function") {
|
if (typeof node_def.sort == "function") {
|
||||||
configSortFn = node_def.sort;
|
configSortFn = node_def.sort;
|
||||||
@ -831,7 +953,10 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configNodes.forEach(function(cn) {
|
configNodes.forEach(function(cn) {
|
||||||
$('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
|
const option = $('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
|
||||||
|
if (cn.env) {
|
||||||
|
option.data('env', cn.env) // set a data attribute to indicate this is an env var (to inhibit the edit button)
|
||||||
|
}
|
||||||
delete cn.__label__;
|
delete cn.__label__;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -844,7 +969,14 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
|
if (!configNodes.length) {
|
||||||
|
// Add 'add new...' option
|
||||||
|
select.append('<option value="_ADD_" selected>' + RED._("editor.addNewType", { type: label }) + '</option>');
|
||||||
|
} else {
|
||||||
|
// Add 'none' option
|
||||||
|
select.append('<option value="_ADD_">' + RED._("editor.inputs.none") + '</option>');
|
||||||
|
}
|
||||||
|
|
||||||
window.setTimeout(function() { select.trigger("change");},50);
|
window.setTimeout(function() { select.trigger("change");},50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1382,139 +1514,193 @@ RED.editor = (function() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "node-config-dialog-ok",
|
id: "node-config-dialog-ok",
|
||||||
text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"),
|
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
|
||||||
class: "primary",
|
class: "primary",
|
||||||
click: function() {
|
click: function() {
|
||||||
var editState = {
|
// TODO: Already defined
|
||||||
|
const configProperty = name;
|
||||||
|
const configType = type;
|
||||||
|
const configTypeDef = RED.nodes.getType(configType);
|
||||||
|
|
||||||
|
const wasChanged = editing_config_node.changed;
|
||||||
|
const editState = {
|
||||||
changes: {},
|
changes: {},
|
||||||
changed: false,
|
changed: false,
|
||||||
outputMap: null
|
outputMap: null
|
||||||
};
|
};
|
||||||
var configProperty = name;
|
|
||||||
var configId = editing_config_node.id;
|
|
||||||
var configType = type;
|
|
||||||
var configAdding = adding;
|
|
||||||
var configTypeDef = RED.nodes.getType(configType);
|
|
||||||
var d;
|
|
||||||
var input;
|
|
||||||
|
|
||||||
if (configTypeDef.oneditsave) {
|
// Call `oneditsave` and search for changes
|
||||||
try {
|
handleEditSave(editing_config_node, editState);
|
||||||
configTypeDef.oneditsave.call(editing_config_node);
|
|
||||||
} catch(err) {
|
|
||||||
console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (d in configTypeDef.defaults) {
|
// Search for changes in the edit box (panes)
|
||||||
if (configTypeDef.defaults.hasOwnProperty(d)) {
|
activeEditPanes.forEach(function (pane) {
|
||||||
var newValue;
|
|
||||||
input = $("#node-config-input-"+d);
|
|
||||||
if (input.attr('type') === "checkbox") {
|
|
||||||
newValue = input.prop('checked');
|
|
||||||
} else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") {
|
|
||||||
newValue = input.text();
|
|
||||||
} else {
|
|
||||||
newValue = input.val();
|
|
||||||
}
|
|
||||||
if (newValue != null && newValue !== editing_config_node[d]) {
|
|
||||||
if (editing_config_node._def.defaults[d].type) {
|
|
||||||
if (newValue == "_ADD_") {
|
|
||||||
newValue = "";
|
|
||||||
}
|
|
||||||
// Change to a related config node
|
|
||||||
var configNode = RED.nodes.node(editing_config_node[d]);
|
|
||||||
if (configNode) {
|
|
||||||
var users = configNode.users;
|
|
||||||
users.splice(users.indexOf(editing_config_node),1);
|
|
||||||
RED.events.emit("nodes:change",configNode);
|
|
||||||
}
|
|
||||||
configNode = RED.nodes.node(newValue);
|
|
||||||
if (configNode) {
|
|
||||||
configNode.users.push(editing_config_node);
|
|
||||||
RED.events.emit("nodes:change",configNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editing_config_node[d] = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activeEditPanes.forEach(function(pane) {
|
|
||||||
if (pane.apply) {
|
if (pane.apply) {
|
||||||
pane.apply.call(pane, editState);
|
pane.apply.call(pane, editState);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
editing_config_node.label = configTypeDef.label;
|
// TODO: Why?
|
||||||
|
editing_config_node.label = configTypeDef.label
|
||||||
var scope = $("#red-ui-editor-config-scope").val();
|
|
||||||
editing_config_node.z = scope;
|
|
||||||
|
|
||||||
|
// Check if disabled has changed
|
||||||
if ($("#node-config-input-node-disabled").prop('checked')) {
|
if ($("#node-config-input-node-disabled").prop('checked')) {
|
||||||
if (editing_config_node.d !== true) {
|
if (editing_config_node.d !== true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
editing_config_node.d = true;
|
editing_config_node.d = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (editing_config_node.d === true) {
|
if (editing_config_node.d === true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
delete editing_config_node.d;
|
delete editing_config_node.d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: must be undefined if no scope used
|
||||||
|
const scope = $("#red-ui-editor-config-scope").val() || undefined;
|
||||||
|
|
||||||
|
// Check if the scope has changed
|
||||||
|
if (editing_config_node.z !== scope) {
|
||||||
|
editState.changes.z = editing_config_node.z;
|
||||||
|
editState.changed = true;
|
||||||
|
editing_config_node.z = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for nodes that use this config node that are no longer
|
||||||
|
// in scope, so must be removed
|
||||||
|
const historyEvents = [];
|
||||||
if (scope) {
|
if (scope) {
|
||||||
// Search for nodes that use this one that are no longer
|
const newUsers = editing_config_node.users.filter(function (node) {
|
||||||
// in scope, so must be removed
|
let keepNode = false;
|
||||||
editing_config_node.users = editing_config_node.users.filter(function(n) {
|
let nodeModified = null;
|
||||||
var keep = true;
|
|
||||||
for (var d in n._def.defaults) {
|
for (const d in node._def.defaults) {
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
if (n._def.defaults[d].type === editing_config_node.type &&
|
if (node._def.defaults[d].type === editing_config_node.type) {
|
||||||
n[d] === editing_config_node.id &&
|
if (node[d] === editing_config_node.id) {
|
||||||
n.z !== scope) {
|
if (node.z === editing_config_node.z) {
|
||||||
keep = false;
|
// The node is kept only if at least one property uses
|
||||||
// Remove the reference to this node
|
// this config node in the correct scope.
|
||||||
// and revalidate
|
keepNode = true;
|
||||||
n[d] = null;
|
} else {
|
||||||
n.dirty = true;
|
if (!nodeModified) {
|
||||||
n.changed = true;
|
nodeModified = {
|
||||||
validateNode(n);
|
t: "edit",
|
||||||
|
node: node,
|
||||||
|
changes: { [d]: node[d] },
|
||||||
|
changed: node.changed,
|
||||||
|
dirty: node.dirty
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
nodeModified.changes[d] = node[d];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reference to the config node
|
||||||
|
node[d] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keep;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configAdding) {
|
// Add the node modified to the history
|
||||||
RED.nodes.add(editing_config_node);
|
if (nodeModified) {
|
||||||
}
|
historyEvents.push(nodeModified);
|
||||||
|
|
||||||
validateNode(editing_config_node);
|
|
||||||
var validatedNodes = {};
|
|
||||||
validatedNodes[editing_config_node.id] = true;
|
|
||||||
|
|
||||||
var userStack = editing_config_node.users.slice();
|
|
||||||
while(userStack.length > 0) {
|
|
||||||
var user = userStack.pop();
|
|
||||||
if (!validatedNodes[user.id]) {
|
|
||||||
validatedNodes[user.id] = true;
|
|
||||||
if (user.users) {
|
|
||||||
userStack = userStack.concat(user.users);
|
|
||||||
}
|
}
|
||||||
validateNode(user);
|
|
||||||
|
// Mark as changed and revalidate this node
|
||||||
|
if (!keepNode) {
|
||||||
|
node.changed = true;
|
||||||
|
node.dirty = true;
|
||||||
|
validateNode(node);
|
||||||
|
RED.events.emit("nodes:change", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keepNode;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if users are changed
|
||||||
|
if (editing_config_node.users.length !== newUsers.length) {
|
||||||
|
editState.changes.users = editing_config_node.users;
|
||||||
|
editState.changed = true;
|
||||||
|
editing_config_node.users = newUsers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RED.nodes.dirty(true);
|
|
||||||
RED.view.redraw(true);
|
if (editState.changed) {
|
||||||
if (!configAdding) {
|
// Set the congig node as changed
|
||||||
RED.events.emit("editor:save",editing_config_node);
|
editing_config_node.changed = true;
|
||||||
RED.events.emit("nodes:change",editing_config_node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now, validate the config node
|
||||||
|
validateNode(editing_config_node);
|
||||||
|
|
||||||
|
// And validate nodes using this config node too
|
||||||
|
const validatedNodes = new Set();
|
||||||
|
const userStack = editing_config_node.users.slice();
|
||||||
|
|
||||||
|
validatedNodes.add(editing_config_node.id);
|
||||||
|
while (userStack.length) {
|
||||||
|
const node = userStack.pop();
|
||||||
|
if (!validatedNodes.has(node.id)) {
|
||||||
|
validatedNodes.add(node.id);
|
||||||
|
if (node.users) {
|
||||||
|
userStack.push(...node.users);
|
||||||
|
}
|
||||||
|
validateNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let historyEvent = {
|
||||||
|
t: "edit",
|
||||||
|
node: editing_config_node,
|
||||||
|
changes: editState.changes,
|
||||||
|
changed: wasChanged,
|
||||||
|
dirty: RED.nodes.dirty()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (historyEvents.length) {
|
||||||
|
// Need a multi events
|
||||||
|
historyEvent = {
|
||||||
|
t: "multi",
|
||||||
|
events: [historyEvent].concat(historyEvents),
|
||||||
|
dirty: historyEvent.dirty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adding) {
|
||||||
|
// This event is triggered when the edit box is saved,
|
||||||
|
// regardless of whether there are any modifications.
|
||||||
|
RED.events.emit("editor:save", editing_config_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editState.changed) {
|
||||||
|
if (adding) {
|
||||||
|
RED.history.push({ t: "add", nodes: [editing_config_node.id], dirty: RED.nodes.dirty() });
|
||||||
|
// Add the new config node and trigger the `nodes:add` event
|
||||||
|
RED.nodes.add(editing_config_node);
|
||||||
|
} else {
|
||||||
|
RED.history.push(historyEvent);
|
||||||
|
RED.events.emit("nodes:change", editing_config_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
RED.nodes.dirty(true);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
RED.tray.close(function() {
|
RED.tray.close(function() {
|
||||||
var filter = null;
|
var filter = null;
|
||||||
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
|
// when editing a config via subflow edit panel, the `configProperty` will not
|
||||||
filter = function(n) {
|
// necessarily be a property of the editContext._def.defaults object
|
||||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
// Also, when editing via dashboard sidebar, editContext can be null
|
||||||
|
// so we need to guard both scenarios
|
||||||
|
if (editContext?._def) {
|
||||||
|
const isSubflow = (editContext._def.type === 'subflow' || /subflow:.*/.test(editContext._def.type))
|
||||||
|
if (editContext && !isSubflow && typeof editContext._def.defaults?.[configProperty]?.filter === 'function') {
|
||||||
|
filter = function(n) {
|
||||||
|
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter);
|
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter);
|
||||||
@ -1575,7 +1761,7 @@ RED.editor = (function() {
|
|||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
RED.tray.close(function() {
|
RED.tray.close(function() {
|
||||||
var filter = null;
|
var filter = null;
|
||||||
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
|
if (editContext && typeof editContext._def.defaults[configProperty]?.filter === 'function') {
|
||||||
filter = function(n) {
|
filter = function(n) {
|
||||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||||
}
|
}
|
||||||
@ -1650,8 +1836,18 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let envToRemove = new Set()
|
||||||
if (!isSameObj(old_env, new_env)) {
|
if (!isSameObj(old_env, new_env)) {
|
||||||
|
// Get a list of env properties that have been removed
|
||||||
|
// by comparing old_env and new_env
|
||||||
|
if (old_env) {
|
||||||
|
old_env.forEach(env => { envToRemove.add(env.name) })
|
||||||
|
}
|
||||||
|
if (new_env) {
|
||||||
|
new_env.forEach(env => {
|
||||||
|
envToRemove.delete(env.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
editState.changes.env = editing_node.env;
|
editState.changes.env = editing_node.env;
|
||||||
editing_node.env = new_env;
|
editing_node.env = new_env;
|
||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
@ -1660,10 +1856,11 @@ RED.editor = (function() {
|
|||||||
|
|
||||||
|
|
||||||
if (editState.changed) {
|
if (editState.changed) {
|
||||||
var wasChanged = editing_node.changed;
|
let wasChanged = editing_node.changed;
|
||||||
editing_node.changed = true;
|
editing_node.changed = true;
|
||||||
validateNode(editing_node);
|
validateNode(editing_node);
|
||||||
var subflowInstances = [];
|
let subflowInstances = [];
|
||||||
|
let instanceHistoryEvents = []
|
||||||
RED.nodes.eachNode(function(n) {
|
RED.nodes.eachNode(function(n) {
|
||||||
if (n.type == "subflow:"+editing_node.id) {
|
if (n.type == "subflow:"+editing_node.id) {
|
||||||
subflowInstances.push({
|
subflowInstances.push({
|
||||||
@ -1673,13 +1870,35 @@ RED.editor = (function() {
|
|||||||
n._def.color = editing_node.color;
|
n._def.color = editing_node.color;
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
|
if (n.env) {
|
||||||
|
const oldEnv = n.env
|
||||||
|
const newEnv = []
|
||||||
|
let envChanged = false
|
||||||
|
n.env.forEach((env, index) => {
|
||||||
|
if (envToRemove.has(env.name)) {
|
||||||
|
envChanged = true
|
||||||
|
} else {
|
||||||
|
newEnv.push(env)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (envChanged) {
|
||||||
|
instanceHistoryEvents.push({
|
||||||
|
t: 'edit',
|
||||||
|
node: n,
|
||||||
|
changes: { env: oldEnv },
|
||||||
|
dirty: n.dirty,
|
||||||
|
changed: n.changed
|
||||||
|
})
|
||||||
|
n.env = newEnv
|
||||||
|
}
|
||||||
|
}
|
||||||
updateNodeProperties(n);
|
updateNodeProperties(n);
|
||||||
validateNode(n);
|
validateNode(n);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.events.emit("subflows:change",editing_node);
|
RED.events.emit("subflows:change",editing_node);
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
var historyEvent = {
|
let historyEvent = {
|
||||||
t:'edit',
|
t:'edit',
|
||||||
node:editing_node,
|
node:editing_node,
|
||||||
changes:editState.changes,
|
changes:editState.changes,
|
||||||
@ -1689,7 +1908,13 @@ RED.editor = (function() {
|
|||||||
instances:subflowInstances
|
instances:subflowInstances
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (instanceHistoryEvents.length > 0) {
|
||||||
|
historyEvent = {
|
||||||
|
t: 'multi',
|
||||||
|
events: [ historyEvent, ...instanceHistoryEvents ],
|
||||||
|
dirty: wasDirty
|
||||||
|
}
|
||||||
|
}
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
}
|
}
|
||||||
editing_node.dirty = true;
|
editing_node.dirty = true;
|
||||||
@ -2116,6 +2341,7 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
||||||
|
getEditStack: function () { return [...editStack] },
|
||||||
buildEditForm: buildEditForm,
|
buildEditForm: buildEditForm,
|
||||||
validateNode: validateNode,
|
validateNode: validateNode,
|
||||||
updateNodeProperties: updateNodeProperties,
|
updateNodeProperties: updateNodeProperties,
|
||||||
@ -2160,6 +2386,7 @@ RED.editor = (function() {
|
|||||||
filteredEditPanes[type] = filter
|
filteredEditPanes[type] = filter
|
||||||
}
|
}
|
||||||
editPanes[type] = definition;
|
editPanes[type] = definition;
|
||||||
}
|
},
|
||||||
|
prepareConfigNodeSelect: prepareConfigNodeSelect,
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -165,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
//Handles orphaned models
|
//Handles orphaned models
|
||||||
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
|
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
|
||||||
RED.events.on("editor:close",function() {
|
RED.events.on("editor:close",function() {
|
||||||
let models = window.monaco ? monaco.editor.getModels() : null;
|
if (!window.monaco) { return; }
|
||||||
|
const editors = window.monaco.editor.getEditors()
|
||||||
|
const orphanEditors = editors.filter(editor => editor && !document.body.contains(editor.getDomNode()))
|
||||||
|
orphanEditors.forEach(editor => {
|
||||||
|
editor.dispose();
|
||||||
|
});
|
||||||
|
let models = monaco.editor.getModels()
|
||||||
if(models && models.length) {
|
if(models && models.length) {
|
||||||
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
|
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
|
||||||
for (let index = 0; index < models.length; index++) {
|
for (let index = 0; index < models.length; index++) {
|
||||||
@ -585,7 +591,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
|
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
|
||||||
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
|
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
|
||||||
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
|
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
|
||||||
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
|
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME,NR_SUBFLOW_NAME,NR_SUBFLOW_ID,NR_SUBFLOW_PATH|}");','Get env variable value',range),
|
||||||
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
|
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
|
||||||
["```typescript",
|
["```typescript",
|
||||||
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
|
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
|
||||||
@ -685,6 +691,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
2322, //Type 'unknown' is not assignable to type 'string'
|
2322, //Type 'unknown' is not assignable to type 'string'
|
||||||
2339, //property does not exist on
|
2339, //property does not exist on
|
||||||
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
|
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
|
||||||
|
2538, //Ignore symbols as index property error.
|
||||||
7043, //i forget what this one is,
|
7043, //i forget what this one is,
|
||||||
80001, //Convert to ES6 module
|
80001, //Convert to ES6 module
|
||||||
80004, //JSDoc types may be moved to TypeScript types.
|
80004, //JSDoc types may be moved to TypeScript types.
|
||||||
@ -1124,6 +1131,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
|
|
||||||
$(el).remove();
|
$(el).remove();
|
||||||
$(toolbarRow).remove();
|
$(toolbarRow).remove();
|
||||||
|
ed.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
ed.resize = function resize() {
|
ed.resize = function resize() {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
RED.editor.envVarList = (function() {
|
RED.editor.envVarList = (function() {
|
||||||
|
|
||||||
var currentLocale = 'en-US';
|
var currentLocale = 'en-US';
|
||||||
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
const DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
||||||
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
|
const DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES = ['str','num','bool','json','bin','env','conf-types'];
|
||||||
|
const DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create env var edit interface
|
* Create env var edit interface
|
||||||
@ -10,8 +11,8 @@ RED.editor.envVarList = (function() {
|
|||||||
* @param node - subflow node
|
* @param node - subflow node
|
||||||
*/
|
*/
|
||||||
function buildPropertiesList(envContainer, node) {
|
function buildPropertiesList(envContainer, node) {
|
||||||
|
if(RED.editor.envVarList.debug) { console.log('envVarList: buildPropertiesList', envContainer, node) }
|
||||||
var isTemplateNode = (node.type === "subflow");
|
const isTemplateNode = (node.type === "subflow");
|
||||||
|
|
||||||
envContainer
|
envContainer
|
||||||
.css({
|
.css({
|
||||||
@ -83,7 +84,14 @@ RED.editor.envVarList = (function() {
|
|||||||
// if `opt.ui` does not exist, then apply defaults. If these
|
// if `opt.ui` does not exist, then apply defaults. If these
|
||||||
// defaults do not change then they will get stripped off
|
// defaults do not change then they will get stripped off
|
||||||
// before saving.
|
// before saving.
|
||||||
if (opt.type === 'cred') {
|
if (opt.type === 'conf-types') {
|
||||||
|
opt.ui = opt.ui || {
|
||||||
|
icon: "fa fa-cog",
|
||||||
|
type: "conf-types",
|
||||||
|
opts: {opts:[]}
|
||||||
|
}
|
||||||
|
opt.ui.type = "conf-types";
|
||||||
|
} else if (opt.type === 'cred') {
|
||||||
opt.ui = opt.ui || {
|
opt.ui = opt.ui || {
|
||||||
icon: "",
|
icon: "",
|
||||||
type: "cred"
|
type: "cred"
|
||||||
@ -119,11 +127,11 @@ RED.editor.envVarList = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
|
buildEnvEditRow(uiRow, opt, nameField, valueField);
|
||||||
nameField.trigger('change');
|
nameField.trigger('change');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sortable: ".red-ui-editableList-item-handle",
|
sortable: true,
|
||||||
removable: false
|
removable: false
|
||||||
});
|
});
|
||||||
var parentEnv = {};
|
var parentEnv = {};
|
||||||
@ -181,21 +189,23 @@ RED.editor.envVarList = (function() {
|
|||||||
* @param nameField - name field of env var
|
* @param nameField - name field of env var
|
||||||
* @param valueField - value field of env var
|
* @param valueField - value field of env var
|
||||||
*/
|
*/
|
||||||
function buildEnvEditRow(container, ui, nameField, valueField) {
|
function buildEnvEditRow(container, opt, nameField, valueField) {
|
||||||
|
const ui = opt.ui
|
||||||
|
if(RED.editor.envVarList.debug) { console.log('envVarList: buildEnvEditRow', container, ui, nameField, valueField) }
|
||||||
container.addClass("red-ui-editor-subflow-env-ui-row")
|
container.addClass("red-ui-editor-subflow-env-ui-row")
|
||||||
var topRow = $('<div></div>').appendTo(container);
|
var topRow = $('<div></div>').appendTo(container);
|
||||||
$('<div></div>').appendTo(topRow);
|
$('<div></div>').appendTo(topRow);
|
||||||
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
||||||
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
||||||
$('<div>').text(RED._("editor.inputType")).appendTo(topRow);
|
$('<div class="red-env-ui-input-type-col">').text(RED._("editor.inputType")).appendTo(topRow);
|
||||||
|
|
||||||
var row = $('<div></div>').appendTo(container);
|
var row = $('<div></div>').appendTo(container);
|
||||||
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
||||||
var typeOptions = {
|
var typeOptions = {
|
||||||
'input': {types:DEFAULT_ENV_TYPE_LIST},
|
'input': {types:DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES},
|
||||||
'select': {opts:[]},
|
'select': {opts:[]},
|
||||||
'spinner': {},
|
'spinner': {},
|
||||||
'cred': {}
|
'cred': {}
|
||||||
};
|
};
|
||||||
if (ui.opts) {
|
if (ui.opts) {
|
||||||
typeOptions[ui.type] = ui.opts;
|
typeOptions[ui.type] = ui.opts;
|
||||||
@ -260,15 +270,16 @@ RED.editor.envVarList = (function() {
|
|||||||
labelInput.attr("placeholder",$(this).val())
|
labelInput.attr("placeholder",$(this).val())
|
||||||
});
|
});
|
||||||
|
|
||||||
var inputCell = $('<div></div>').appendTo(row);
|
var inputCell = $('<div class="red-env-ui-input-type-col"></div>').appendTo(row);
|
||||||
var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
var uiInputTypeInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
||||||
if (ui.type === "input") {
|
if (ui.type === "input") {
|
||||||
inputCellInput.val(ui.opts.types.join(","));
|
uiInputTypeInput.val(ui.opts.types.join(","));
|
||||||
}
|
}
|
||||||
var checkbox;
|
var checkbox;
|
||||||
var selectBox;
|
var selectBox;
|
||||||
|
|
||||||
inputCellInput.typedInput({
|
// the options presented in the UI section for an "input" type selection
|
||||||
|
uiInputTypeInput.typedInput({
|
||||||
types: [
|
types: [
|
||||||
{
|
{
|
||||||
value:"input",
|
value:"input",
|
||||||
@ -429,7 +440,7 @@ RED.editor.envVarList = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.opts.opts = vals;
|
ui.opts.opts = vals;
|
||||||
inputCellInput.typedInput('value',Date.now())
|
uiInputTypeInput.typedInput('value',Date.now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -496,12 +507,13 @@ RED.editor.envVarList = (function() {
|
|||||||
} else {
|
} else {
|
||||||
delete ui.opts.max;
|
delete ui.opts.max;
|
||||||
}
|
}
|
||||||
inputCellInput.typedInput('value',Date.now())
|
uiInputTypeInput.typedInput('value',Date.now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'conf-types',
|
||||||
{
|
{
|
||||||
value:"none",
|
value:"none",
|
||||||
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
||||||
@ -519,14 +531,20 @@ RED.editor.envVarList = (function() {
|
|||||||
// In the case of 'input' type, the typedInput uses the multiple-option
|
// In the case of 'input' type, the typedInput uses the multiple-option
|
||||||
// mode. Its value needs to be set to a comma-separately list of the
|
// mode. Its value needs to be set to a comma-separately list of the
|
||||||
// selected options.
|
// selected options.
|
||||||
inputCellInput.typedInput('value',ui.opts.types.join(","))
|
uiInputTypeInput.typedInput('value',ui.opts.types.join(","))
|
||||||
|
} else if (ui.type === 'conf-types') {
|
||||||
|
// In the case of 'conf-types' type, the typedInput will be populated
|
||||||
|
// with a list of all config nodes types installed.
|
||||||
|
// Restore the value to the last selected type
|
||||||
|
uiInputTypeInput.typedInput('value', opt.type)
|
||||||
} else {
|
} else {
|
||||||
// No other type cares about `value`, but doing this will
|
// No other type cares about `value`, but doing this will
|
||||||
// force a refresh of the label now that `ui.opts` has
|
// force a refresh of the label now that `ui.opts` has
|
||||||
// been updated.
|
// been updated.
|
||||||
inputCellInput.typedInput('value',Date.now())
|
uiInputTypeInput.typedInput('value',Date.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:typedinputtypechange. ui.type = ' + ui.type) }
|
||||||
switch (ui.type) {
|
switch (ui.type) {
|
||||||
case 'input':
|
case 'input':
|
||||||
valueField.typedInput('types',ui.opts.types);
|
valueField.typedInput('types',ui.opts.types);
|
||||||
@ -544,7 +562,7 @@ RED.editor.envVarList = (function() {
|
|||||||
valueField.typedInput('types',['cred']);
|
valueField.typedInput('types',['cred']);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
|
valueField.typedInput('types', DEFAULT_ENV_TYPE_LIST);
|
||||||
}
|
}
|
||||||
if (ui.type === 'checkbox') {
|
if (ui.type === 'checkbox') {
|
||||||
valueField.typedInput('type','bool');
|
valueField.typedInput('type','bool');
|
||||||
@ -556,8 +574,46 @@ RED.editor.envVarList = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}).on("change", function(evt,type) {
|
}).on("change", function(evt,type) {
|
||||||
if (ui.type === 'input') {
|
const selectedType = $(this).typedInput('type') // the UI typedInput type
|
||||||
var types = inputCellInput.typedInput('value');
|
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:change. selectedType = ' + selectedType) }
|
||||||
|
if (selectedType === 'conf-types') {
|
||||||
|
const selectedConfigType = $(this).typedInput('value') || opt.type
|
||||||
|
let activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
||||||
|
if (!activeWorkspace) {
|
||||||
|
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a list of all config nodes matching the selectedValue
|
||||||
|
const configNodes = [];
|
||||||
|
RED.nodes.eachConfig(function(config) {
|
||||||
|
if (config.type == selectedConfigType && (!config.z || config.z === activeWorkspace.id)) {
|
||||||
|
const modulePath = config._def?.set?.id || ''
|
||||||
|
let label = RED.utils.getNodeLabel(config, config.id) || config.id;
|
||||||
|
label += config.d ? ' ['+RED._('workspace.disabled')+']' : '';
|
||||||
|
const _config = {
|
||||||
|
_type: selectedConfigType,
|
||||||
|
value: config.id,
|
||||||
|
label: label,
|
||||||
|
title: modulePath ? modulePath + ' - ' + label : label,
|
||||||
|
enabled: config.d !== true,
|
||||||
|
disabled: config.d === true,
|
||||||
|
}
|
||||||
|
configNodes.push(_config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const tiTypes = {
|
||||||
|
value: selectedConfigType,
|
||||||
|
label: "config",
|
||||||
|
icon: "fa fa-cog",
|
||||||
|
options: configNodes,
|
||||||
|
}
|
||||||
|
valueField.typedInput('types', [tiTypes]);
|
||||||
|
valueField.typedInput('type', selectedConfigType);
|
||||||
|
valueField.typedInput('value', opt.value);
|
||||||
|
|
||||||
|
|
||||||
|
} else if (ui.type === 'input') {
|
||||||
|
var types = uiInputTypeInput.typedInput('value');
|
||||||
ui.opts.types = (types === "") ? ["str"] : types.split(",");
|
ui.opts.types = (types === "") ? ["str"] : types.split(",");
|
||||||
valueField.typedInput('types',ui.opts.types);
|
valueField.typedInput('types',ui.opts.types);
|
||||||
}
|
}
|
||||||
@ -569,7 +625,7 @@ RED.editor.envVarList = (function() {
|
|||||||
})
|
})
|
||||||
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
||||||
// event handler (just above ^^) to update the value if needed
|
// event handler (just above ^^) to update the value if needed
|
||||||
inputCellInput.typedInput('type',ui.type)
|
uiInputTypeInput.typedInput('type',ui.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLocale(l, list) {
|
function setLocale(l, list) {
|
||||||
|
@ -27,6 +27,12 @@
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function file2Text(file,cb) {
|
||||||
|
file.arrayBuffer().then(d => {
|
||||||
|
cb( new TextDecoder().decode(d) )
|
||||||
|
}).catch(ex => { cb(`error: ${ex}`) })
|
||||||
|
}
|
||||||
|
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var currentEditor = null;
|
var currentEditor = null;
|
||||||
/**
|
/**
|
||||||
@ -52,6 +58,7 @@
|
|||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
var file = files[0];
|
var file = files[0];
|
||||||
var name = file.name.toLowerCase();
|
var name = file.name.toLowerCase();
|
||||||
|
var fileType = file.type.toLowerCase();
|
||||||
|
|
||||||
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
|
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
|
||||||
file2base64Image(file, function (image) {
|
file2base64Image(file, function (image) {
|
||||||
@ -63,6 +70,29 @@
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( fileType.startsWith("text/") ) {
|
||||||
|
file2Text(file, function (txt) {
|
||||||
|
var session = currentEditor.getSession();
|
||||||
|
var pos = session.getCursorPosition();
|
||||||
|
session.insert(pos, txt);
|
||||||
|
$("#red-ui-image-drop-target").hide();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if ($.inArray("text/plain", ev.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
let item = Object.values(ev.originalEvent.dataTransfer.items).filter(d => d.type == "text/plain")[0]
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item.getAsString(txt => {
|
||||||
|
var session = currentEditor.getSession();
|
||||||
|
var pos = session.getCursorPosition();
|
||||||
|
session.insert(pos, txt);
|
||||||
|
$("#red-ui-image-drop-target").hide();
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("#red-ui-image-drop-target").hide();
|
$("#red-ui-image-drop-target").hide();
|
||||||
|
@ -11,9 +11,22 @@ RED.editor.mermaid = (function () {
|
|||||||
|
|
||||||
if (!initializing) {
|
if (!initializing) {
|
||||||
initializing = true
|
initializing = true
|
||||||
$.getScript(
|
// Find the cache-buster:
|
||||||
'vendor/mermaid/mermaid.min.js',
|
let cacheBuster
|
||||||
function (data, stat, jqxhr) {
|
$('script').each(function (i, el) {
|
||||||
|
if (!cacheBuster) {
|
||||||
|
const src = el.getAttribute('src')
|
||||||
|
const m = /\?v=(.+)$/.exec(src)
|
||||||
|
if (m) {
|
||||||
|
cacheBuster = m[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$.ajax({
|
||||||
|
url: `vendor/mermaid/mermaid.min.js?v=${cacheBuster}`,
|
||||||
|
dataType: "script",
|
||||||
|
cache: true,
|
||||||
|
success: function (data, stat, jqxhr) {
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
startOnLoad: false,
|
startOnLoad: false,
|
||||||
theme: RED.settings.get('mermaid', {}).theme
|
theme: RED.settings.get('mermaid', {}).theme
|
||||||
@ -24,7 +37,7 @@ RED.editor.mermaid = (function () {
|
|||||||
render(pending)
|
render(pending)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const nodes = document.querySelectorAll(selector)
|
const nodes = document.querySelectorAll(selector)
|
||||||
|
@ -20,10 +20,31 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var old_env = node.env;
|
var old_env = node.env;
|
||||||
var new_env = [];
|
var new_env = [];
|
||||||
|
|
||||||
if (/^subflow:/.test(node.type)) {
|
if (/^subflow:/.test(node.type)) {
|
||||||
|
// Get the list of environment variables from the node properties
|
||||||
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (old_env && old_env.length) {
|
||||||
|
old_env.forEach(function (prop) {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
const stillInUse = new_env?.some((p) => p.type === "conf-type" && p.name === prop.name && p.value === prop.value);
|
||||||
|
if (!stillInUse) {
|
||||||
|
// Remove the node from the config node users
|
||||||
|
// Only for empty value or modified
|
||||||
|
const configNode = RED.nodes.node(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
configNode.users.splice(configNode.users.indexOf(node), 1);
|
||||||
|
RED.events.emit('nodes:change', configNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get the values from the Properties table tab
|
// Get the values from the Properties table tab
|
||||||
var items = this.list.editableList('items');
|
var items = this.list.editableList('items');
|
||||||
items.each(function (i,el) {
|
items.each(function (i,el) {
|
||||||
@ -41,7 +62,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (new_env && new_env.length > 0) {
|
if (new_env && new_env.length > 0) {
|
||||||
new_env.forEach(function(prop) {
|
new_env.forEach(function(prop) {
|
||||||
if (prop.type === "cred") {
|
if (prop.type === "cred") {
|
||||||
@ -52,6 +72,15 @@
|
|||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
}
|
}
|
||||||
delete prop.value;
|
delete prop.value;
|
||||||
|
} else if (prop.type === "conf-type" && prop.value) {
|
||||||
|
const configNode = RED.nodes.node(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
|
// Add the node to the config node users
|
||||||
|
configNode.users.push(node);
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var newValue;
|
var newValue;
|
||||||
var d;
|
var d;
|
||||||
|
// If the node is a subflow, the node's properties (exepts name) are saved by `envProperties`
|
||||||
if (node._def.defaults) {
|
if (node._def.defaults) {
|
||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@ -131,9 +132,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node._def.credentials) {
|
if (node._def.credentials) {
|
||||||
var credDefinition = node._def.credentials;
|
const credDefinition = node._def.credentials;
|
||||||
var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
|
const credChanges = updateNodeCredentials(node, credDefinition, this.inputClass);
|
||||||
editState.changed = editState.changed || credsChanged;
|
|
||||||
|
if (Object.keys(credChanges).length) {
|
||||||
|
editState.changed = true;
|
||||||
|
editState.changes.credentials = {
|
||||||
|
...(editState.changes.credentials || {}),
|
||||||
|
...credChanges
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,10 +169,11 @@
|
|||||||
* @param node - the node containing the credentials
|
* @param node - the node containing the credentials
|
||||||
* @param credDefinition - definition of the credentials
|
* @param credDefinition - definition of the credentials
|
||||||
* @param prefix - prefix of the input fields
|
* @param prefix - prefix of the input fields
|
||||||
* @return {boolean} whether anything has changed
|
* @return {object} an object containing the modified properties
|
||||||
*/
|
*/
|
||||||
function updateNodeCredentials(node, credDefinition, prefix) {
|
function updateNodeCredentials(node, credDefinition, prefix) {
|
||||||
var changed = false;
|
const changes = {};
|
||||||
|
|
||||||
if (!node.credentials) {
|
if (!node.credentials) {
|
||||||
node.credentials = {_:{}};
|
node.credentials = {_:{}};
|
||||||
} else if (!node.credentials._) {
|
} else if (!node.credentials._) {
|
||||||
@ -177,22 +186,33 @@
|
|||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
var value = input.val();
|
var value = input.val();
|
||||||
if (credDefinition[cred].type == 'password') {
|
if (credDefinition[cred].type == 'password') {
|
||||||
node.credentials['has_' + cred] = (value !== "");
|
if (value === '__PWRD__') {
|
||||||
if (value == '__PWRD__') {
|
// A cred value exists - no changes
|
||||||
continue;
|
} else if (value === '' && node.credentials['has_' + cred] === false) {
|
||||||
|
// Empty cred value exists - no changes
|
||||||
|
} else if (value === node.credentials[cred]) {
|
||||||
|
// A cred value exists locally in the editor - no changes
|
||||||
|
// Like the user sets a value, saves the config,
|
||||||
|
// reopens the config and save the config again
|
||||||
|
} else {
|
||||||
|
changes['has_' + cred] = node.credentials['has_' + cred];
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
|
|
||||||
}
|
node.credentials['has_' + cred] = (value !== '');
|
||||||
node.credentials[cred] = value;
|
} else {
|
||||||
if (value != node.credentials._[cred]) {
|
// Since these creds are loaded by the editor,
|
||||||
changed = true;
|
// values can be directly compared
|
||||||
|
if (value !== node.credentials[cred]) {
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed;
|
|
||||||
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -153,10 +153,6 @@ RED.envVar = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init(done) {
|
function init(done) {
|
||||||
if (!RED.user.hasPermission("settings.write")) {
|
|
||||||
RED.notify(RED._("user.errors.settings"),"error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RED.userSettings.add({
|
RED.userSettings.add({
|
||||||
id:'envvar',
|
id:'envvar',
|
||||||
title: RED._("env-var.environment"),
|
title: RED._("env-var.environment"),
|
||||||
|
@ -245,10 +245,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@ -303,10 +308,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@ -839,10 +849,10 @@ RED.library = (function() {
|
|||||||
if (file && file.label && !file.children) {
|
if (file && file.label && !file.children) {
|
||||||
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
||||||
//TODO: nls + sanitize
|
//TODO: nls + sanitize
|
||||||
var propRow = $('<tr class="red-ui-help-info-row"><td>Type</td><td></td></tr>').appendTo(table);
|
var propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.type")+'</td><td></td></tr>').appendTo(table);
|
||||||
$(propRow.children()[1]).text(activeLibrary.type);
|
$(propRow.children()[1]).text(activeLibrary.type);
|
||||||
if (file.props.hasOwnProperty('name')) {
|
if (file.props.hasOwnProperty('name')) {
|
||||||
propRow = $('<tr class="red-ui-help-info-row"><td>Name</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.name")+'</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
||||||
$(propRow.children()[1]).text(file.props.name);
|
$(propRow.children()[1]).text(file.props.name);
|
||||||
}
|
}
|
||||||
for (var p in file.props) {
|
for (var p in file.props) {
|
||||||
|
@ -221,12 +221,12 @@ RED.notifications = (function() {
|
|||||||
if (newType) {
|
if (newType) {
|
||||||
n.className = "red-ui-notification red-ui-notification-"+newType;
|
n.className = "red-ui-notification red-ui-notification-"+newType;
|
||||||
}
|
}
|
||||||
|
newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
|
||||||
if (!fixed || newOptions.fixed === false) {
|
if (!fixed || newOptions.fixed === false) {
|
||||||
newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
|
newTimeout = newTimeout || 5000
|
||||||
}
|
}
|
||||||
if (newOptions.buttons) {
|
if (newOptions.buttons) {
|
||||||
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
|
var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn)
|
||||||
newOptions.buttons.forEach(function(buttonDef) {
|
newOptions.buttons.forEach(function(buttonDef) {
|
||||||
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
|
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
|
||||||
if (buttonDef.id) {
|
if (buttonDef.id) {
|
||||||
@ -272,6 +272,15 @@ RED.notifications = (function() {
|
|||||||
};
|
};
|
||||||
})());
|
})());
|
||||||
n.timeoutid = window.setTimeout(n.close,timeout||5000);
|
n.timeoutid = window.setTimeout(n.close,timeout||5000);
|
||||||
|
} else if (timeout) {
|
||||||
|
$(n).on("click.red-ui-notification-close", (function() {
|
||||||
|
var nn = n;
|
||||||
|
return function() {
|
||||||
|
nn.hideNotification();
|
||||||
|
window.clearTimeout(nn.timeoutid);
|
||||||
|
};
|
||||||
|
})());
|
||||||
|
n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000);
|
||||||
}
|
}
|
||||||
currentNotifications.push(n);
|
currentNotifications.push(n);
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
|
@ -133,7 +133,7 @@ RED.palette.editor = (function() {
|
|||||||
}).done(function(data,textStatus,xhr) {
|
}).done(function(data,textStatus,xhr) {
|
||||||
callback();
|
callback();
|
||||||
}).fail(function(xhr,textStatus,err) {
|
}).fail(function(xhr,textStatus,err) {
|
||||||
callback(xhr);
|
callback(xhr,textStatus,err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function removeNodeModule(id,callback) {
|
function removeNodeModule(id,callback) {
|
||||||
@ -248,86 +248,106 @@ RED.palette.editor = (function() {
|
|||||||
var moduleInfo = nodeEntries[module].info;
|
var moduleInfo = nodeEntries[module].info;
|
||||||
var nodeEntry = nodeEntries[module].elements;
|
var nodeEntry = nodeEntries[module].elements;
|
||||||
if (nodeEntry) {
|
if (nodeEntry) {
|
||||||
var activeTypeCount = 0;
|
if (moduleInfo.plugin) {
|
||||||
var typeCount = 0;
|
nodeEntry.enableButton.hide();
|
||||||
var errorCount = 0;
|
nodeEntry.removeButton.show();
|
||||||
nodeEntry.errorList.empty();
|
|
||||||
nodeEntries[module].totalUseCount = 0;
|
|
||||||
nodeEntries[module].setUseCount = {};
|
|
||||||
|
|
||||||
for (var setName in moduleInfo.sets) {
|
let pluginCount = 0;
|
||||||
if (moduleInfo.sets.hasOwnProperty(setName)) {
|
for (let setName in moduleInfo.sets) {
|
||||||
var inUseCount = 0;
|
if (moduleInfo.sets.hasOwnProperty(setName)) {
|
||||||
var set = moduleInfo.sets[setName];
|
let set = moduleInfo.sets[setName];
|
||||||
var setElements = nodeEntry.sets[setName];
|
if (set.plugins) {
|
||||||
if (set.err) {
|
pluginCount += set.plugins.length;
|
||||||
errorCount++;
|
|
||||||
var errMessage = set.err;
|
|
||||||
if (set.err.message) {
|
|
||||||
errMessage = set.err.message;
|
|
||||||
} else if (set.err.code) {
|
|
||||||
errMessage = set.err.code;
|
|
||||||
}
|
}
|
||||||
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
|
|
||||||
}
|
}
|
||||||
if (set.enabled) {
|
}
|
||||||
activeTypeCount += set.types.length;
|
|
||||||
}
|
nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount}));
|
||||||
typeCount += set.types.length;
|
|
||||||
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
|
} else {
|
||||||
var t = moduleInfo.sets[setName].types[i];
|
var activeTypeCount = 0;
|
||||||
inUseCount += (typesInUse[t]||0);
|
var typeCount = 0;
|
||||||
var swatch = setElements.swatches[t];
|
var errorCount = 0;
|
||||||
|
nodeEntry.errorList.empty();
|
||||||
|
nodeEntries[module].totalUseCount = 0;
|
||||||
|
nodeEntries[module].setUseCount = {};
|
||||||
|
|
||||||
|
for (var setName in moduleInfo.sets) {
|
||||||
|
if (moduleInfo.sets.hasOwnProperty(setName)) {
|
||||||
|
var inUseCount = 0;
|
||||||
|
const set = moduleInfo.sets[setName];
|
||||||
|
const setElements = nodeEntry.sets[setName]
|
||||||
|
|
||||||
|
if (set.err) {
|
||||||
|
errorCount++;
|
||||||
|
var errMessage = set.err;
|
||||||
|
if (set.err.message) {
|
||||||
|
errMessage = set.err.message;
|
||||||
|
} else if (set.err.code) {
|
||||||
|
errMessage = set.err.code;
|
||||||
|
}
|
||||||
|
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
|
||||||
|
}
|
||||||
if (set.enabled) {
|
if (set.enabled) {
|
||||||
var def = RED.nodes.getType(t);
|
activeTypeCount += set.types.length;
|
||||||
if (def && def.color) {
|
}
|
||||||
swatch.css({background:RED.utils.getNodeColor(t,def)});
|
typeCount += set.types.length;
|
||||||
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
|
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
|
||||||
|
var t = moduleInfo.sets[setName].types[i];
|
||||||
|
inUseCount += (typesInUse[t]||0);
|
||||||
|
if (setElements && set.enabled) {
|
||||||
|
var def = RED.nodes.getType(t);
|
||||||
|
if (def && def.color) {
|
||||||
|
setElements.swatches[t].css({background:RED.utils.getNodeColor(t,def)});
|
||||||
|
setElements.swatches[t].css({border: "1px solid "+getContrastingBorder(setElements.swatches[t].css('backgroundColor'))})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
nodeEntries[module].setUseCount[setName] = inUseCount;
|
||||||
nodeEntries[module].setUseCount[setName] = inUseCount;
|
nodeEntries[module].totalUseCount += inUseCount;
|
||||||
nodeEntries[module].totalUseCount += inUseCount;
|
|
||||||
|
|
||||||
if (inUseCount > 0) {
|
if (setElements) {
|
||||||
setElements.enableButton.text(RED._('palette.editor.inuse'));
|
if (inUseCount > 0) {
|
||||||
setElements.enableButton.addClass('disabled');
|
setElements.enableButton.text(RED._('palette.editor.inuse'));
|
||||||
} else {
|
setElements.enableButton.addClass('disabled');
|
||||||
setElements.enableButton.removeClass('disabled');
|
} else {
|
||||||
if (set.enabled) {
|
setElements.enableButton.removeClass('disabled');
|
||||||
setElements.enableButton.text(RED._('palette.editor.disable'));
|
if (set.enabled) {
|
||||||
} else {
|
setElements.enableButton.text(RED._('palette.editor.disable'));
|
||||||
setElements.enableButton.text(RED._('palette.editor.enable'));
|
} else {
|
||||||
|
setElements.enableButton.text(RED._('palette.editor.enable'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (errorCount === 0) {
|
if (errorCount === 0) {
|
||||||
nodeEntry.errorRow.hide()
|
nodeEntry.errorRow.hide()
|
||||||
} else {
|
|
||||||
nodeEntry.errorRow.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
|
|
||||||
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
|
|
||||||
|
|
||||||
if (nodeEntries[module].totalUseCount > 0) {
|
|
||||||
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
|
|
||||||
nodeEntry.enableButton.addClass('disabled');
|
|
||||||
nodeEntry.removeButton.hide();
|
|
||||||
} else {
|
|
||||||
nodeEntry.enableButton.removeClass('disabled');
|
|
||||||
if (moduleInfo.local) {
|
|
||||||
nodeEntry.removeButton.css('display', 'inline-block');
|
|
||||||
}
|
|
||||||
if (activeTypeCount === 0) {
|
|
||||||
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
|
|
||||||
} else {
|
} else {
|
||||||
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
|
nodeEntry.errorRow.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
|
||||||
|
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
|
||||||
|
|
||||||
|
if (nodeEntries[module].totalUseCount > 0) {
|
||||||
|
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
|
||||||
|
nodeEntry.enableButton.addClass('disabled');
|
||||||
|
nodeEntry.removeButton.hide();
|
||||||
|
} else {
|
||||||
|
nodeEntry.enableButton.removeClass('disabled');
|
||||||
|
if (moduleInfo.local) {
|
||||||
|
nodeEntry.removeButton.css('display', 'inline-block');
|
||||||
|
}
|
||||||
|
if (activeTypeCount === 0) {
|
||||||
|
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
|
||||||
|
} else {
|
||||||
|
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
|
||||||
|
}
|
||||||
|
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
|
||||||
}
|
}
|
||||||
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (moduleInfo.pending_version) {
|
if (moduleInfo.pending_version) {
|
||||||
@ -678,6 +698,33 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
RED.events.on("registry:plugin-module-added", function(module) {
|
||||||
|
|
||||||
|
if (!nodeEntries.hasOwnProperty(module)) {
|
||||||
|
nodeEntries[module] = {info:RED.plugins.getModule(module)};
|
||||||
|
var index = [module];
|
||||||
|
for (var s in nodeEntries[module].info.sets) {
|
||||||
|
if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
|
||||||
|
index.push(s);
|
||||||
|
index = index.concat(nodeEntries[module].info.sets[s].types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeEntries[module].index = index.join(",").toLowerCase();
|
||||||
|
nodeList.editableList('addItem', nodeEntries[module]);
|
||||||
|
} else {
|
||||||
|
_refreshNodeModule(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0;i<filteredList.length;i++) {
|
||||||
|
if (filteredList[i].info.id === module) {
|
||||||
|
var installButton = filteredList[i].elements.installButton;
|
||||||
|
installButton.addClass('disabled');
|
||||||
|
installButton.text(RED._('palette.editor.installed'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingsPane;
|
var settingsPane;
|
||||||
@ -804,6 +851,7 @@ RED.palette.editor = (function() {
|
|||||||
errorRow: errorRow,
|
errorRow: errorRow,
|
||||||
errorList: errorList,
|
errorList: errorList,
|
||||||
setCount: setCount,
|
setCount: setCount,
|
||||||
|
setButton: setButton,
|
||||||
container: container,
|
container: container,
|
||||||
shade: shade,
|
shade: shade,
|
||||||
versionSpan: versionSpan,
|
versionSpan: versionSpan,
|
||||||
@ -814,49 +862,88 @@ RED.palette.editor = (function() {
|
|||||||
if (container.hasClass('expanded')) {
|
if (container.hasClass('expanded')) {
|
||||||
container.removeClass('expanded');
|
container.removeClass('expanded');
|
||||||
contentRow.slideUp();
|
contentRow.slideUp();
|
||||||
|
setTimeout(() => {
|
||||||
|
contentRow.empty()
|
||||||
|
}, 200)
|
||||||
|
object.elements.sets = {}
|
||||||
} else {
|
} else {
|
||||||
container.addClass('expanded');
|
container.addClass('expanded');
|
||||||
|
populateSetList()
|
||||||
contentRow.slideDown();
|
contentRow.slideDown();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const populateSetList = function () {
|
||||||
var setList = Object.keys(entry.sets)
|
var setList = Object.keys(entry.sets)
|
||||||
setList.sort(function(A,B) {
|
setList.sort(function(A,B) {
|
||||||
return A.toLowerCase().localeCompare(B.toLowerCase());
|
return A.toLowerCase().localeCompare(B.toLowerCase());
|
||||||
});
|
});
|
||||||
setList.forEach(function(setName) {
|
setList.forEach(function(setName) {
|
||||||
var set = entry.sets[setName];
|
var set = entry.sets[setName];
|
||||||
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
|
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
|
||||||
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
|
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
|
||||||
var typeSwatches = {};
|
var typeSwatches = {};
|
||||||
set.types.forEach(function(t) {
|
let enableButton;
|
||||||
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
|
if (set.types) {
|
||||||
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
set.types.forEach(function(t) {
|
||||||
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
|
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
|
||||||
})
|
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
||||||
var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
|
if (set.enabled) {
|
||||||
enableButton.on("click", function(evt) {
|
var def = RED.nodes.getType(t);
|
||||||
evt.preventDefault();
|
if (def && def.color) {
|
||||||
if (object.setUseCount[setName] === 0) {
|
typeSwatches[t].css({background:RED.utils.getNodeColor(t,def)});
|
||||||
var currentSet = RED.nodes.registry.getNodeSet(set.id);
|
typeSwatches[t].css({border: "1px solid "+getContrastingBorder(typeSwatches[t].css('backgroundColor'))})
|
||||||
shade.show();
|
|
||||||
var newState = !currentSet.enabled
|
|
||||||
changeNodeState(set.id,newState,shade,function(xhr){
|
|
||||||
if (xhr) {
|
|
||||||
if (xhr.responseJSON) {
|
|
||||||
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
|
||||||
}
|
})
|
||||||
})
|
enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
|
||||||
|
enableButton.on("click", function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (object.setUseCount[setName] === 0) {
|
||||||
|
var currentSet = RED.nodes.registry.getNodeSet(set.id);
|
||||||
|
shade.show();
|
||||||
|
var newState = !currentSet.enabled
|
||||||
|
changeNodeState(set.id,newState,shade,function(xhr){
|
||||||
|
if (xhr) {
|
||||||
|
if (xhr.responseJSON) {
|
||||||
|
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
object.elements.sets[set.name] = {
|
if (object.setUseCount[setName] > 0) {
|
||||||
setRow: setRow,
|
enableButton.text(RED._('palette.editor.inuse'));
|
||||||
enableButton: enableButton,
|
enableButton.addClass('disabled');
|
||||||
swatches: typeSwatches
|
} else {
|
||||||
};
|
enableButton.removeClass('disabled');
|
||||||
});
|
if (set.enabled) {
|
||||||
|
enableButton.text(RED._('palette.editor.disable'));
|
||||||
|
} else {
|
||||||
|
enableButton.text(RED._('palette.editor.enable'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
if (set.plugins) {
|
||||||
|
set.plugins.forEach(function(p) {
|
||||||
|
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
|
||||||
|
// typeSwatches[p.id] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
||||||
|
$('<span><i class="fa fa-puzzle-piece" aria-hidden="true"></i> </span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
||||||
|
$('<span>',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
object.elements.sets[set.name] = {
|
||||||
|
setRow: setRow,
|
||||||
|
enableButton: enableButton,
|
||||||
|
swatches: typeSwatches
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
enableButton.on("click", function(evt) {
|
enableButton.on("click", function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
if (object.totalUseCount === 0) {
|
if (object.totalUseCount === 0) {
|
||||||
@ -1226,7 +1313,55 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}); }
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// dedicated list management for plugins
|
||||||
|
if (entry.plugin) {
|
||||||
|
|
||||||
|
let e = nodeEntries[entry.name];
|
||||||
|
if (e) {
|
||||||
|
nodeList.editableList('removeItem', e);
|
||||||
|
delete nodeEntries[entry.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We assume that a plugin that implements onremove
|
||||||
|
// cleans the editor accordingly of its left-overs.
|
||||||
|
let found_onremove = true;
|
||||||
|
|
||||||
|
let keys = Object.keys(entry.sets);
|
||||||
|
keys.forEach((key) => {
|
||||||
|
let set = entry.sets[key];
|
||||||
|
for (let i=0; i<set.plugins?.length; i++) {
|
||||||
|
let plgn = RED.plugins.getPlugin(set.plugins[i].id);
|
||||||
|
if (plgn && plgn.onremove && typeof plgn.onremove === 'function') {
|
||||||
|
plgn.onremove();
|
||||||
|
} else {
|
||||||
|
if (plgn && plgn.onadd && typeof plgn.onadd === 'function') {
|
||||||
|
// if there's no 'onadd', there shouldn't be any left-overs
|
||||||
|
found_onremove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found_onremove) {
|
||||||
|
let removeNotify = RED.notify(RED._("palette.editor.confirm.removePlugin.body",{module:entry.name}),{
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
type: 'warning',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("palette.editor.confirm.button.understood"),
|
||||||
|
class:"primary",
|
||||||
|
click: function(e) {
|
||||||
|
removeNotify.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
notification.close();
|
notification.close();
|
||||||
@ -1270,9 +1405,28 @@ RED.palette.editor = (function() {
|
|||||||
RED.actions.invoke("core:show-event-log");
|
RED.actions.invoke("core:show-event-log");
|
||||||
});
|
});
|
||||||
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
|
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
|
||||||
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
|
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr, textStatus,err) {
|
||||||
spinner.remove();
|
spinner.remove();
|
||||||
if (xhr) {
|
if (err && xhr.status === 504) {
|
||||||
|
var notification = RED.notify(RED._("palette.editor.errors.installTimeout"), {
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.close"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
text: RED._("eventLog.view"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
RED.actions.invoke("core:show-event-log");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
} else if (xhr) {
|
||||||
if (xhr.responseJSON) {
|
if (xhr.responseJSON) {
|
||||||
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
|
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
@ -35,6 +35,10 @@ RED.palette = (function() {
|
|||||||
var categoryContainers = {};
|
var categoryContainers = {};
|
||||||
var sidebarControls;
|
var sidebarControls;
|
||||||
|
|
||||||
|
let paletteState = { filter: "", collapsed: [] };
|
||||||
|
|
||||||
|
let filterRefreshTimeout
|
||||||
|
|
||||||
function createCategory(originalCategory,rootCategory,category,ns) {
|
function createCategory(originalCategory,rootCategory,category,ns) {
|
||||||
if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
|
if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
|
||||||
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
|
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
|
||||||
@ -60,20 +64,57 @@ RED.palette = (function() {
|
|||||||
catDiv.data('label',label);
|
catDiv.data('label',label);
|
||||||
categoryContainers[category] = {
|
categoryContainers[category] = {
|
||||||
container: catDiv,
|
container: catDiv,
|
||||||
close: function() {
|
hide: function (instant) {
|
||||||
|
if (instant) {
|
||||||
|
catDiv.hide()
|
||||||
|
} else {
|
||||||
|
catDiv.slideUp()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show: function () {
|
||||||
|
catDiv.show()
|
||||||
|
},
|
||||||
|
isOpen: function () {
|
||||||
|
return !!catDiv.hasClass("red-ui-palette-open")
|
||||||
|
},
|
||||||
|
getNodeCount: function (visibleOnly) {
|
||||||
|
const nodes = catDiv.find(".red-ui-palette-node")
|
||||||
|
if (visibleOnly) {
|
||||||
|
return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
|
||||||
|
} else {
|
||||||
|
return nodes.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: function(instant, skipSaveState) {
|
||||||
catDiv.removeClass("red-ui-palette-open");
|
catDiv.removeClass("red-ui-palette-open");
|
||||||
catDiv.addClass("red-ui-palette-closed");
|
catDiv.addClass("red-ui-palette-closed");
|
||||||
$("#red-ui-palette-base-category-"+category).slideUp();
|
if (instant) {
|
||||||
|
$("#red-ui-palette-base-category-"+category).hide();
|
||||||
|
} else {
|
||||||
|
$("#red-ui-palette-base-category-"+category).slideUp();
|
||||||
|
}
|
||||||
$("#red-ui-palette-header-"+category+" i").removeClass("expanded");
|
$("#red-ui-palette-header-"+category+" i").removeClass("expanded");
|
||||||
|
if (!skipSaveState) {
|
||||||
|
if (!paletteState.collapsed.includes(category)) {
|
||||||
|
paletteState.collapsed.push(category);
|
||||||
|
savePaletteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
open: function() {
|
open: function(skipSaveState) {
|
||||||
catDiv.addClass("red-ui-palette-open");
|
catDiv.addClass("red-ui-palette-open");
|
||||||
catDiv.removeClass("red-ui-palette-closed");
|
catDiv.removeClass("red-ui-palette-closed");
|
||||||
$("#red-ui-palette-base-category-"+category).slideDown();
|
$("#red-ui-palette-base-category-"+category).slideDown();
|
||||||
$("#red-ui-palette-header-"+category+" i").addClass("expanded");
|
$("#red-ui-palette-header-"+category+" i").addClass("expanded");
|
||||||
|
if (!skipSaveState) {
|
||||||
|
if (paletteState.collapsed.includes(category)) {
|
||||||
|
paletteState.collapsed.splice(paletteState.collapsed.indexOf(category), 1);
|
||||||
|
savePaletteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggle: function() {
|
toggle: function() {
|
||||||
if (catDiv.hasClass("red-ui-palette-open")) {
|
if (categoryContainers[category].isOpen()) {
|
||||||
categoryContainers[category].close();
|
categoryContainers[category].close();
|
||||||
} else {
|
} else {
|
||||||
categoryContainers[category].open();
|
categoryContainers[category].open();
|
||||||
@ -415,8 +456,16 @@ RED.palette = (function() {
|
|||||||
|
|
||||||
var categoryNode = $("#red-ui-palette-container-"+rootCategory);
|
var categoryNode = $("#red-ui-palette-container-"+rootCategory);
|
||||||
if (categoryNode.find(".red-ui-palette-node").length === 1) {
|
if (categoryNode.find(".red-ui-palette-node").length === 1) {
|
||||||
categoryContainers[rootCategory].open();
|
if (!paletteState?.collapsed?.includes(rootCategory)) {
|
||||||
|
categoryContainers[rootCategory].open();
|
||||||
|
} else {
|
||||||
|
categoryContainers[rootCategory].close(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
clearTimeout(filterRefreshTimeout)
|
||||||
|
filterRefreshTimeout = setTimeout(() => {
|
||||||
|
refreshFilter()
|
||||||
|
}, 200)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -516,7 +565,8 @@ RED.palette = (function() {
|
|||||||
paletteNode.css("backgroundColor", sf.color);
|
paletteNode.css("backgroundColor", sf.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterChange(val) {
|
function refreshFilter() {
|
||||||
|
const val = $("#red-ui-palette-search input").val()
|
||||||
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
|
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
|
||||||
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
|
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
|
||||||
var currentLabel = $(el).attr("data-palette-label");
|
var currentLabel = $(el).attr("data-palette-label");
|
||||||
@ -528,16 +578,26 @@ RED.palette = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (var category in categoryContainers) {
|
for (let category in categoryContainers) {
|
||||||
if (categoryContainers.hasOwnProperty(category)) {
|
if (categoryContainers.hasOwnProperty(category)) {
|
||||||
if (categoryContainers[category].container
|
const categorySection = categoryContainers[category]
|
||||||
.find(".red-ui-palette-node")
|
if (categorySection.getNodeCount(true) === 0) {
|
||||||
.filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
|
categorySection.hide()
|
||||||
categoryContainers[category].close();
|
|
||||||
categoryContainers[category].container.slideUp();
|
|
||||||
} else {
|
} else {
|
||||||
categoryContainers[category].open();
|
categorySection.show()
|
||||||
categoryContainers[category].container.show();
|
if (val) {
|
||||||
|
// There is a filter being applied and it has matched
|
||||||
|
// something in this category - show the contents
|
||||||
|
categorySection.open(true)
|
||||||
|
} else {
|
||||||
|
// No filter. Only show the category if it isn't in lastState
|
||||||
|
if (!paletteState.collapsed.includes(category)) {
|
||||||
|
categorySection.open(true)
|
||||||
|
} else if (categorySection.isOpen()) {
|
||||||
|
// This section should be collapsed but isn't - so make it so
|
||||||
|
categorySection.close(true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -553,6 +613,9 @@ RED.palette = (function() {
|
|||||||
|
|
||||||
$("#red-ui-palette > .red-ui-palette-spinner").show();
|
$("#red-ui-palette > .red-ui-palette-spinner").show();
|
||||||
|
|
||||||
|
RED.events.on('logout', function () {
|
||||||
|
RED.settings.removeLocal('palette-state')
|
||||||
|
})
|
||||||
|
|
||||||
RED.events.on('registry:node-type-added', function(nodeType) {
|
RED.events.on('registry:node-type-added', function(nodeType) {
|
||||||
var def = RED.nodes.getType(nodeType);
|
var def = RED.nodes.getType(nodeType);
|
||||||
@ -596,14 +659,14 @@ RED.palette = (function() {
|
|||||||
|
|
||||||
RED.events.on("subflows:change",refreshSubflow);
|
RED.events.on("subflows:change",refreshSubflow);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$("#red-ui-palette-search input").searchBox({
|
$("#red-ui-palette-search input").searchBox({
|
||||||
delay: 100,
|
delay: 100,
|
||||||
change: function() {
|
change: function() {
|
||||||
filterChange($(this).val());
|
refreshFilter();
|
||||||
|
paletteState.filter = $(this).val();
|
||||||
|
savePaletteState();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
|
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
|
||||||
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
|
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
|
||||||
@ -669,7 +732,23 @@ RED.palette = (function() {
|
|||||||
togglePalette(state);
|
togglePalette(state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
|
||||||
|
if (paletteState.filter) {
|
||||||
|
// Apply the category filter
|
||||||
|
$("#red-ui-palette-search input").searchBox("value", paletteState.filter);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unexpected error loading palette state from localStorage: ", error);
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
// Lazily tidy up any categories that haven't been reloaded
|
||||||
|
paletteState.collapsed = paletteState.collapsed.filter(category => !!categoryContainers[category])
|
||||||
|
savePaletteState()
|
||||||
|
}, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePalette(state) {
|
function togglePalette(state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
$("#red-ui-main-container").addClass("red-ui-palette-closed");
|
$("#red-ui-main-container").addClass("red-ui-palette-closed");
|
||||||
@ -689,6 +768,15 @@ RED.palette = (function() {
|
|||||||
})
|
})
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function savePaletteState() {
|
||||||
|
try {
|
||||||
|
RED.settings.setLocal("palette-state", JSON.stringify(paletteState));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unexpected error saving palette state to localStorage: ", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
add:addNodeType,
|
add:addNodeType,
|
||||||
|
@ -287,7 +287,7 @@ RED.projects.settings = (function() {
|
|||||||
var notInstalledCount = 0;
|
var notInstalledCount = 0;
|
||||||
|
|
||||||
for (var m in modulesInUse) {
|
for (var m in modulesInUse) {
|
||||||
if (modulesInUse.hasOwnProperty(m)) {
|
if (modulesInUse.hasOwnProperty(m) && !activeProject.dependencies.hasOwnProperty(m)) {
|
||||||
depsList.editableList('addItem',{
|
depsList.editableList('addItem',{
|
||||||
id: modulesInUse[m].module,
|
id: modulesInUse[m].module,
|
||||||
version: modulesInUse[m].version,
|
version: modulesInUse[m].version,
|
||||||
@ -307,8 +307,8 @@ RED.projects.settings = (function() {
|
|||||||
|
|
||||||
if (activeProject.dependencies) {
|
if (activeProject.dependencies) {
|
||||||
for (var m in activeProject.dependencies) {
|
for (var m in activeProject.dependencies) {
|
||||||
if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) {
|
if (activeProject.dependencies.hasOwnProperty(m)) {
|
||||||
var installed = !!RED.nodes.registry.getModule(m);
|
var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m]?.version;
|
||||||
depsList.editableList('addItem',{
|
depsList.editableList('addItem',{
|
||||||
id: m,
|
id: m,
|
||||||
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,
|
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,
|
||||||
@ -1256,7 +1256,7 @@ RED.projects.settings = (function() {
|
|||||||
notification.close();
|
notification.close();
|
||||||
}
|
}
|
||||||
},{
|
},{
|
||||||
text: 'Delete branch',
|
text: RED._("sidebar.project.projectSettings.deleteBranch"),
|
||||||
click: function() {
|
click: function() {
|
||||||
notification.close();
|
notification.close();
|
||||||
var url = "projects/"+activeProject.name+"/branches/"+entry.name;
|
var url = "projects/"+activeProject.name+"/branches/"+entry.name;
|
||||||
|
@ -909,17 +909,19 @@ RED.subflow = (function() {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create interface for controlling env var UI definition
|
* Build the edit dialog for a subflow template (creating/modifying a subflow template)
|
||||||
|
* @param {Object} uiContainer - the jQuery container for the environment variable list
|
||||||
|
* @param {Object} node - the subflow template node
|
||||||
*/
|
*/
|
||||||
function buildEnvControl(envList,node) {
|
function buildEnvControl(uiContainer,node) {
|
||||||
var tabs = RED.tabs.create({
|
var tabs = RED.tabs.create({
|
||||||
id: "subflow-env-tabs",
|
id: "subflow-env-tabs",
|
||||||
onchange: function(tab) {
|
onchange: function(tab) {
|
||||||
if (tab.id === "subflow-env-tab-preview") {
|
if (tab.id === "subflow-env-tab-preview") {
|
||||||
var inputContainer = $("#subflow-input-ui");
|
var inputContainer = $("#subflow-input-ui");
|
||||||
var list = envList.editableList("items");
|
var list = uiContainer.editableList("items");
|
||||||
var exportedEnv = exportEnvList(list, true);
|
var exportedEnv = exportEnvList(list, true);
|
||||||
buildEnvUI(inputContainer, exportedEnv,node);
|
buildEnvUI(inputContainer, exportedEnv, node);
|
||||||
}
|
}
|
||||||
$("#subflow-env-tabs-content").children().hide();
|
$("#subflow-env-tabs-content").children().hide();
|
||||||
$("#" + tab.id).show();
|
$("#" + tab.id).show();
|
||||||
@ -957,12 +959,33 @@ RED.subflow = (function() {
|
|||||||
RED.editor.envVarList.setLocale(locale);
|
RED.editor.envVarList.setLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
function buildEnvUIRow(row, tenv, ui, node) {
|
* Build a UI row for a subflow instance environment variable
|
||||||
|
* Also used to build the UI row for subflow template preview
|
||||||
|
* @param {JQuery} row - A form row element
|
||||||
|
* @param {Object} tenv - A template environment variable
|
||||||
|
* @param {String} tenv.name - The name of the environment variable
|
||||||
|
* @param {String} tenv.type - The type of the environment variable
|
||||||
|
* @param {String} tenv.value - The value set for this environment variable
|
||||||
|
* @param {Object} tenv.parent - The parent environment variable
|
||||||
|
* @param {String} tenv.parent.value - The value set for the parent environment variable
|
||||||
|
* @param {String} tenv.parent.type - The type of the parent environment variable
|
||||||
|
* @param {Object} tenv.ui - The UI configuration for the environment variable
|
||||||
|
* @param {String} tenv.ui.icon - The icon for the environment variable
|
||||||
|
* @param {Object} tenv.ui.label - The label for the environment variable
|
||||||
|
* @param {String} tenv.ui.type - The type of the UI control for the environment variable
|
||||||
|
* @param {Object} node - The subflow instance node
|
||||||
|
*/
|
||||||
|
function buildEnvUIRow(row, tenv, node) {
|
||||||
|
if(RED.subflow.debug) { console.log("buildEnvUIRow", tenv) }
|
||||||
|
const ui = tenv.ui || {}
|
||||||
ui.label = ui.label||{};
|
ui.label = ui.label||{};
|
||||||
if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
|
if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
|
||||||
ui.type = "cred";
|
ui.type = "cred";
|
||||||
ui.opts = {};
|
ui.opts = {};
|
||||||
|
} else if (tenv.type === "conf-types") {
|
||||||
|
ui.type = "conf-types"
|
||||||
|
ui.opts = { types: ['conf-types'] }
|
||||||
} else if (!ui.type) {
|
} else if (!ui.type) {
|
||||||
ui.type = "input";
|
ui.type = "input";
|
||||||
ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
|
ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
|
||||||
@ -1006,9 +1029,10 @@ RED.subflow = (function() {
|
|||||||
if (tenv.hasOwnProperty('type')) {
|
if (tenv.hasOwnProperty('type')) {
|
||||||
val.type = tenv.type;
|
val.type = tenv.type;
|
||||||
}
|
}
|
||||||
|
const elId = getSubflowEnvPropertyName(tenv.name)
|
||||||
switch(ui.type) {
|
switch(ui.type) {
|
||||||
case "input":
|
case "input":
|
||||||
input = $('<input type="text">').css('width','70%').appendTo(row);
|
input = $('<input type="text">').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
if (ui.opts.types && ui.opts.types.length > 0) {
|
if (ui.opts.types && ui.opts.types.length > 0) {
|
||||||
var inputType = val.type;
|
var inputType = val.type;
|
||||||
if (ui.opts.types.indexOf(inputType) === -1) {
|
if (ui.opts.types.indexOf(inputType) === -1) {
|
||||||
@ -1035,7 +1059,7 @@ RED.subflow = (function() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "select":
|
case "select":
|
||||||
input = $('<select>').css('width','70%').appendTo(row);
|
input = $('<select>').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
if (ui.opts.opts) {
|
if (ui.opts.opts) {
|
||||||
ui.opts.opts.forEach(function(o) {
|
ui.opts.opts.forEach(function(o) {
|
||||||
$('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
|
$('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
|
||||||
@ -1046,7 +1070,7 @@ RED.subflow = (function() {
|
|||||||
case "checkbox":
|
case "checkbox":
|
||||||
label.css("cursor","default");
|
label.css("cursor","default");
|
||||||
var cblabel = $('<label>').css('width','70%').appendTo(row);
|
var cblabel = $('<label>').css('width','70%').appendTo(row);
|
||||||
input = $('<input type="checkbox">').css({
|
input = $('<input type="checkbox">').attr('id', elId).css({
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: '34px'
|
height: '34px'
|
||||||
@ -1064,7 +1088,7 @@ RED.subflow = (function() {
|
|||||||
input.prop("checked",boolVal);
|
input.prop("checked",boolVal);
|
||||||
break;
|
break;
|
||||||
case "spinner":
|
case "spinner":
|
||||||
input = $('<input>').css('width','70%').appendTo(row);
|
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
var spinnerOpts = {};
|
var spinnerOpts = {};
|
||||||
if (ui.opts.hasOwnProperty('min')) {
|
if (ui.opts.hasOwnProperty('min')) {
|
||||||
spinnerOpts.min = ui.opts.min;
|
spinnerOpts.min = ui.opts.min;
|
||||||
@ -1076,7 +1100,7 @@ RED.subflow = (function() {
|
|||||||
input.val(val.value);
|
input.val(val.value);
|
||||||
break;
|
break;
|
||||||
case "cred":
|
case "cred":
|
||||||
input = $('<input type="password">').css('width','70%').appendTo(row);
|
input = $('<input type="password">').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
if (node.credentials) {
|
if (node.credentials) {
|
||||||
if (node.credentials[tenv.name]) {
|
if (node.credentials[tenv.name]) {
|
||||||
input.val(node.credentials[tenv.name]);
|
input.val(node.credentials[tenv.name]);
|
||||||
@ -1093,18 +1117,25 @@ RED.subflow = (function() {
|
|||||||
default: 'cred'
|
default: 'cred'
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
}
|
case "conf-types":
|
||||||
if (input) {
|
// let clsId = 'config-node-input-' + val.type + '-' + val.value + '-' + Math.floor(Math.random() * 100000);
|
||||||
input.attr('id',getSubflowEnvPropertyName(tenv.name))
|
// clsId = clsId.replace(/\W/g, '-');
|
||||||
|
// input = $('<input>').css('width','70%').addClass(clsId).attr('id', elId).appendTo(row);
|
||||||
|
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
|
const _type = tenv.parent?.type || tenv.type;
|
||||||
|
RED.editor.prepareConfigNodeSelect(node, tenv.name, _type, 'node-input-subflow-env', null, tenv);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create environment variable input UI
|
* Build the edit form for a subflow instance
|
||||||
|
* Also used to build the preview form in the subflow template edit dialog
|
||||||
* @param uiContainer - container for UI
|
* @param uiContainer - container for UI
|
||||||
* @param envList - env var definitions of template
|
* @param envList - env var definitions of template
|
||||||
*/
|
*/
|
||||||
function buildEnvUI(uiContainer, envList, node) {
|
function buildEnvUI(uiContainer, envList, node) {
|
||||||
|
if(RED.subflow.debug) { console.log("buildEnvUI",envList) }
|
||||||
uiContainer.empty();
|
uiContainer.empty();
|
||||||
for (var i = 0; i < envList.length; i++) {
|
for (var i = 0; i < envList.length; i++) {
|
||||||
var tenv = envList[i];
|
var tenv = envList[i];
|
||||||
@ -1112,7 +1143,7 @@ RED.subflow = (function() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
|
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
|
||||||
buildEnvUIRow(row,tenv, tenv.ui || {}, node);
|
buildEnvUIRow(row, tenv, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// buildEnvUI
|
// buildEnvUI
|
||||||
@ -1185,6 +1216,9 @@ RED.subflow = (function() {
|
|||||||
delete ui.opts
|
delete ui.opts
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "conf-types":
|
||||||
|
delete ui.opts;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
delete ui.opts;
|
delete ui.opts;
|
||||||
}
|
}
|
||||||
@ -1207,8 +1241,9 @@ RED.subflow = (function() {
|
|||||||
if (/^subflow:/.test(node.type)) {
|
if (/^subflow:/.test(node.type)) {
|
||||||
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
||||||
if (subflowDef.env) {
|
if (subflowDef.env) {
|
||||||
subflowDef.env.forEach(function(env) {
|
subflowDef.env.forEach(function(env, i) {
|
||||||
var item = {
|
var item = {
|
||||||
|
index: i,
|
||||||
name:env.name,
|
name:env.name,
|
||||||
parent: {
|
parent: {
|
||||||
type: env.type,
|
type: env.type,
|
||||||
@ -1245,14 +1280,20 @@ RED.subflow = (function() {
|
|||||||
var nodePropValue = nodeProp;
|
var nodePropValue = nodeProp;
|
||||||
if (prop.ui && prop.ui.type === "cred") {
|
if (prop.ui && prop.ui.type === "cred") {
|
||||||
nodePropType = "cred";
|
nodePropType = "cred";
|
||||||
|
} else if (prop.ui && prop.ui.type === "conf-types") {
|
||||||
|
nodePropType = prop.value.type
|
||||||
} else {
|
} else {
|
||||||
switch(typeof nodeProp) {
|
switch(typeof nodeProp) {
|
||||||
case "string": nodePropType = "str"; break;
|
case "string": nodePropType = "str"; break;
|
||||||
case "number": nodePropType = "num"; break;
|
case "number": nodePropType = "num"; break;
|
||||||
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
|
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
|
||||||
default:
|
default:
|
||||||
nodePropType = nodeProp.type;
|
if (nodeProp) {
|
||||||
nodePropValue = nodeProp.value;
|
nodePropType = nodeProp.type;
|
||||||
|
nodePropValue = nodeProp.value;
|
||||||
|
} else {
|
||||||
|
nodePropType = 'str'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var item = {
|
var item = {
|
||||||
@ -1273,6 +1314,7 @@ RED.subflow = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportSubflowInstanceEnv(node) {
|
function exportSubflowInstanceEnv(node) {
|
||||||
|
if(RED.subflow.debug) { console.log("exportSubflowInstanceEnv",node) }
|
||||||
var env = [];
|
var env = [];
|
||||||
// First, get the values for the SubflowTemplate defined properties
|
// First, get the values for the SubflowTemplate defined properties
|
||||||
// - these are the ones with custom UI elements
|
// - these are the ones with custom UI elements
|
||||||
@ -1304,7 +1346,7 @@ RED.subflow = (function() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "cred":
|
case "cred":
|
||||||
item.value = input.val();
|
item.value = input.typedInput('value');
|
||||||
item.type = 'cred';
|
item.type = 'cred';
|
||||||
break;
|
break;
|
||||||
case "spinner":
|
case "spinner":
|
||||||
@ -1319,6 +1361,9 @@ RED.subflow = (function() {
|
|||||||
item.type = 'bool';
|
item.type = 'bool';
|
||||||
item.value = ""+input.prop("checked");
|
item.value = ""+input.prop("checked");
|
||||||
break;
|
break;
|
||||||
|
case "conf-types":
|
||||||
|
item.value = input.val() === "_ADD_" ? "" : input.val();
|
||||||
|
item.type = "conf-type"
|
||||||
}
|
}
|
||||||
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
||||||
env.push(item);
|
env.push(item);
|
||||||
@ -1332,8 +1377,15 @@ RED.subflow = (function() {
|
|||||||
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
|
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by subflow.oneditprepare for both instances and templates
|
|
||||||
|
/**
|
||||||
|
* Build the subflow edit form
|
||||||
|
* Called by subflow.oneditprepare for both instances and templates
|
||||||
|
* @param {"subflow"|"subflow-template"} type - the type of subflow being edited
|
||||||
|
* @param {Object} node - the node being edited
|
||||||
|
*/
|
||||||
function buildEditForm(type,node) {
|
function buildEditForm(type,node) {
|
||||||
|
if(RED.subflow.debug) { console.log("buildEditForm",type,node) }
|
||||||
if (type === "subflow-template") {
|
if (type === "subflow-template") {
|
||||||
// This is the tabbed UI that offers the env list - with UI options
|
// This is the tabbed UI that offers the env list - with UI options
|
||||||
// plus the preview tab
|
// plus the preview tab
|
||||||
|
@ -56,7 +56,16 @@ RED.sidebar.config = (function() {
|
|||||||
} else {
|
} else {
|
||||||
$('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
|
$('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('<span class="red-ui-sidebar-node-config-filter-info"></span>').appendTo(header);
|
$('<span class="red-ui-sidebar-node-config-filter-info"></span>').appendTo(header);
|
||||||
|
|
||||||
|
const changeBadgeContainer = $('<svg class="red-ui-sidebar-config-category-changed red-ui-flow-node-changed" width="10" height="10" viewBox="-1 -1 12 12"></svg>').appendTo(header);
|
||||||
|
const changeBadge = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
||||||
|
changeBadge.setAttribute("cx", "5");
|
||||||
|
changeBadge.setAttribute("cy", "5");
|
||||||
|
changeBadge.setAttribute("r", "5");
|
||||||
|
changeBadgeContainer.append(changeBadge);
|
||||||
|
|
||||||
category = $('<ul class="red-ui-palette-content red-ui-sidebar-node-config-list"></ul>').appendTo(container);
|
category = $('<ul class="red-ui-palette-content red-ui-sidebar-node-config-list"></ul>').appendTo(container);
|
||||||
category.on("click", function(e) {
|
category.on("click", function(e) {
|
||||||
$(content).find(".red-ui-palette-node").removeClass("selected");
|
$(content).find(".red-ui-palette-node").removeClass("selected");
|
||||||
@ -150,9 +159,6 @@ RED.sidebar.config = (function() {
|
|||||||
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
|
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
|
||||||
currentType = node.type;
|
currentType = node.type;
|
||||||
}
|
}
|
||||||
if (node.changed) {
|
|
||||||
labelText += "!!"
|
|
||||||
}
|
|
||||||
var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
|
var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
|
||||||
var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
|
var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
|
||||||
entry.data('node',node.id);
|
entry.data('node',node.id);
|
||||||
@ -181,15 +187,29 @@ RED.sidebar.config = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.changed) {
|
||||||
|
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-changed" width="10" height="10" viewBox="-1 -1 12 12"></svg>').appendTo(nodeDiv);
|
||||||
|
const changeBadge = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
||||||
|
changeBadge.setAttribute("cx", "5");
|
||||||
|
changeBadge.setAttribute("cy", "5");
|
||||||
|
changeBadge.setAttribute("r", "5");
|
||||||
|
nodeDivAnnotations.append($(changeBadge));
|
||||||
|
|
||||||
|
const categoryHeader = list.parent().find(".red-ui-sidebar-config-tray-header.red-ui-palette-header");
|
||||||
|
categoryHeader.addClass("red-ui-sidebar-config-changed");
|
||||||
|
nodeDiv.addClass("red-ui-palette-node-config-changed");
|
||||||
|
}
|
||||||
|
|
||||||
if (!node.valid) {
|
if (!node.valid) {
|
||||||
nodeDiv.addClass("red-ui-palette-node-config-invalid")
|
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv);
|
||||||
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv)
|
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||||
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
|
errorBadge.setAttribute("d", "M 0,9 l 10,0 -5,-8 z");
|
||||||
errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
|
nodeDivAnnotations.append($(errorBadge));
|
||||||
nodeDivAnnotations.append($(errorBadge))
|
|
||||||
|
nodeDiv.addClass("red-ui-palette-node-config-invalid");
|
||||||
RED.popover.tooltip(nodeDivAnnotations, function () {
|
RED.popover.tooltip(nodeDivAnnotations, function () {
|
||||||
if (node.validationErrors && node.validationErrors.length > 0) {
|
if (node.validationErrors && node.validationErrors.length > 0) {
|
||||||
return RED._("editor.errors.invalidProperties")+"<br> - "+node.validationErrors.join("<br> - ")
|
return RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -252,6 +272,10 @@ RED.sidebar.config = (function() {
|
|||||||
$(this).remove();
|
$(this).remove();
|
||||||
delete categories[id];
|
delete categories[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the `changed` badge from the category header
|
||||||
|
const categoryHeader = $(this).find(".red-ui-sidebar-config-tray-header.red-ui-palette-header");
|
||||||
|
categoryHeader.removeClass("red-ui-sidebar-config-changed");
|
||||||
})
|
})
|
||||||
var globalConfigNodes = [];
|
var globalConfigNodes = [];
|
||||||
var configList = {};
|
var configList = {};
|
||||||
|
@ -18,8 +18,6 @@ RED.sidebar.context = (function() {
|
|||||||
var content;
|
var content;
|
||||||
var sections;
|
var sections;
|
||||||
|
|
||||||
var localCache = {};
|
|
||||||
|
|
||||||
var flowAutoRefresh;
|
var flowAutoRefresh;
|
||||||
var nodeAutoRefresh;
|
var nodeAutoRefresh;
|
||||||
var nodeSection;
|
var nodeSection;
|
||||||
@ -27,6 +25,8 @@ RED.sidebar.context = (function() {
|
|||||||
var flowSection;
|
var flowSection;
|
||||||
var globalSection;
|
var globalSection;
|
||||||
|
|
||||||
|
const expandedPaths = {}
|
||||||
|
|
||||||
var currentNode;
|
var currentNode;
|
||||||
var currentFlow;
|
var currentFlow;
|
||||||
|
|
||||||
@ -212,14 +212,41 @@ RED.sidebar.context = (function() {
|
|||||||
var l = keys.length;
|
var l = keys.length;
|
||||||
for (var i = 0; i < l; i++) {
|
for (var i = 0; i < l; i++) {
|
||||||
sortedData[keys[i]].forEach(function(v) {
|
sortedData[keys[i]].forEach(function(v) {
|
||||||
var k = keys[i];
|
const k = keys[i];
|
||||||
var l2 = sortedData[k].length;
|
let payload = v.msg;
|
||||||
var propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
|
let format = v.format;
|
||||||
var obj = $(propRow.children()[0]);
|
const tools = $('<span class="button-group"></span>');
|
||||||
|
expandedPaths[id + "." + k] = expandedPaths[id + "." + k] || new Set()
|
||||||
|
const objectElementOptions = {
|
||||||
|
typeHint: format,
|
||||||
|
sourceId: id + "." + k,
|
||||||
|
tools,
|
||||||
|
path: k,
|
||||||
|
rootPath: k,
|
||||||
|
exposeApi: true,
|
||||||
|
ontoggle: function(path,state) {
|
||||||
|
path = path.substring(k.length+1)
|
||||||
|
if (state) {
|
||||||
|
expandedPaths[id+"."+k].add(path)
|
||||||
|
} else {
|
||||||
|
// if 'a' has been collapsed, we want to remove 'a.b' and 'a[0]...' from the set
|
||||||
|
// of collapsed paths
|
||||||
|
for (let expandedPath of expandedPaths[id+"."+k]) {
|
||||||
|
if (expandedPath.startsWith(path+".") || expandedPath.startsWith(path+"[")) {
|
||||||
|
expandedPaths[id+"."+k].delete(expandedPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expandedPaths[id+"."+k].delete(path)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expandPaths: [ ...expandedPaths[id+"."+k] ].sort(),
|
||||||
|
expandLeafNodes: true
|
||||||
|
}
|
||||||
|
const propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
|
||||||
|
const obj = $(propRow.children()[0]);
|
||||||
obj.text(k);
|
obj.text(k);
|
||||||
var tools = $('<span class="button-group"></span>');
|
|
||||||
const urlSafeK = encodeURIComponent(k)
|
const urlSafeK = encodeURIComponent(k)
|
||||||
var refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
|
const refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
|
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
|
||||||
@ -229,16 +256,14 @@ RED.sidebar.context = (function() {
|
|||||||
tools.detach();
|
tools.detach();
|
||||||
$(propRow.children()[1]).empty();
|
$(propRow.children()[1]).empty();
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
||||||
|
...objectElementOptions,
|
||||||
typeHint: data.format,
|
typeHint: data.format,
|
||||||
sourceId: id+"."+k,
|
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
}).appendTo(propRow.children()[1]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
|
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
|
||||||
var deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
|
const deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var popover = RED.popover.create({
|
var popover = RED.popover.create({
|
||||||
@ -246,7 +271,7 @@ RED.sidebar.context = (function() {
|
|||||||
target: propRow,
|
target: propRow,
|
||||||
direction: "left",
|
direction: "left",
|
||||||
content: function() {
|
content: function() {
|
||||||
var content = $('<div>');
|
const content = $('<div>');
|
||||||
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
|
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
|
||||||
var row = $('<p>').appendTo(content);
|
var row = $('<p>').appendTo(content);
|
||||||
var bg = $('<span class="button-group"></span>').appendTo(row);
|
var bg = $('<span class="button-group"></span>').appendTo(row);
|
||||||
@ -269,16 +294,15 @@ RED.sidebar.context = (function() {
|
|||||||
if (container.children().length === 0) {
|
if (container.children().length === 0) {
|
||||||
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
|
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
|
||||||
}
|
}
|
||||||
|
delete expandedPaths[id + "." + k]
|
||||||
} else {
|
} else {
|
||||||
payload = data.msg;
|
payload = data.msg;
|
||||||
format = data.format;
|
format = data.format;
|
||||||
tools.detach();
|
tools.detach();
|
||||||
$(propRow.children()[1]).empty();
|
$(propRow.children()[1]).empty();
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
||||||
typeHint: data.format,
|
...objectElementOptions,
|
||||||
sourceId: id+"."+k,
|
typeHint: data.format
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
}).appendTo(propRow.children()[1]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -293,14 +317,7 @@ RED.sidebar.context = (function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
|
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
|
||||||
var payload = v.msg;
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), objectElementOptions).appendTo(propRow.children()[1]);
|
||||||
var format = v.format;
|
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
|
||||||
typeHint: v.format,
|
|
||||||
sourceId: id+"."+k,
|
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
|
||||||
if (contextStores.length > 1) {
|
if (contextStores.length > 1) {
|
||||||
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ RED.sidebar.info.outliner = (function() {
|
|||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
RED.search.show("type:subflow:"+n.id);
|
RED.search.show("type:subflow:"+n.id);
|
||||||
})
|
})
|
||||||
// RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})});
|
RED.popover.tooltip(subflowInstanceBadge,function() { return RED._('subflow.subflowInstances',{count:n.instances.length})});
|
||||||
}
|
}
|
||||||
if (n._def.category === "config" && n.type !== "group") {
|
if (n._def.category === "config" && n.type !== "group") {
|
||||||
var userCountBadge = $('<button type="button" class="red-ui-info-outline-item-control-users red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').text(n.users.length).appendTo(controls).on("click",function(evt) {
|
var userCountBadge = $('<button type="button" class="red-ui-info-outline-item-control-users red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').text(n.users.length).appendTo(controls).on("click",function(evt) {
|
||||||
|
@ -204,7 +204,7 @@ RED.sidebar.info = (function() {
|
|||||||
|
|
||||||
propertiesPanelHeaderIcon.empty();
|
propertiesPanelHeaderIcon.empty();
|
||||||
RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon);
|
RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon);
|
||||||
propertiesPanelHeaderLabel.text("Selection");
|
propertiesPanelHeaderLabel.text(RED._("sidebar.info.selection"));
|
||||||
propertiesPanelHeaderReveal.hide();
|
propertiesPanelHeaderReveal.hide();
|
||||||
propertiesPanelHeaderHelp.hide();
|
propertiesPanelHeaderHelp.hide();
|
||||||
propertiesPanelHeaderCopyLink.hide();
|
propertiesPanelHeaderCopyLink.hide();
|
||||||
|
@ -435,10 +435,15 @@ RED.tourGuide = (function() {
|
|||||||
|
|
||||||
function listTour() {
|
function listTour() {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
id: "4_0",
|
||||||
|
label: "4.0",
|
||||||
|
path: "./tours/welcome.js"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "3_1",
|
id: "3_1",
|
||||||
label: "3.1",
|
label: "3.1",
|
||||||
path: "./tours/welcome.js"
|
path: "./tours/3.1/welcome.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3_0",
|
id: "3_0",
|
||||||
|
@ -264,6 +264,7 @@
|
|||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
oldTray.tray.detach();
|
oldTray.tray.detach();
|
||||||
showTray(options);
|
showTray(options);
|
||||||
|
RED.events.emit('editor:change')
|
||||||
},250)
|
},250)
|
||||||
} else {
|
} else {
|
||||||
if (stack.length > 0) {
|
if (stack.length > 0) {
|
||||||
@ -333,6 +334,7 @@
|
|||||||
RED.view.focus();
|
RED.view.focus();
|
||||||
} else {
|
} else {
|
||||||
stack[stack.length-1].tray.css("z-index", "auto");
|
stack[stack.length-1].tray.css("z-index", "auto");
|
||||||
|
RED.events.emit('editor:change')
|
||||||
}
|
}
|
||||||
},250)
|
},250)
|
||||||
}
|
}
|
||||||
|
@ -279,6 +279,11 @@ RED.typeSearch = (function() {
|
|||||||
if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
|
if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
|
||||||
opts.y = opts.y - 275;
|
opts.y = opts.y - 275;
|
||||||
}
|
}
|
||||||
|
const dialogWidth = dialog.width() || 300 // default is 300 (defined in class .red-ui-search)
|
||||||
|
const workspaceWidth = $('#red-ui-workspace').width()
|
||||||
|
if (workspaceWidth > dialogWidth && workspaceWidth - opts.x - dialogWidth < 0) {
|
||||||
|
opts.x = opts.x - (dialogWidth - RED.view.node_width)
|
||||||
|
}
|
||||||
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
|
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
|
||||||
searchResultsDiv.slideDown(300);
|
searchResultsDiv.slideDown(300);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
@ -330,13 +335,25 @@ RED.typeSearch = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function applyFilter(filter,type,def) {
|
function applyFilter(filter,type,def) {
|
||||||
return !def || !filter ||
|
if (!filter) {
|
||||||
(
|
// No filter; allow everything
|
||||||
(!filter.spliceMultiple) &&
|
return true
|
||||||
(!filter.type || type === filter.type) &&
|
}
|
||||||
(!filter.input || type === 'junction' || def.inputs > 0) &&
|
if (type === 'junction') {
|
||||||
(!filter.output || type === 'junction' || def.outputs > 0)
|
// Only allow Junction is there's no specific type filter
|
||||||
)
|
return !filter.type
|
||||||
|
}
|
||||||
|
if (filter.type) {
|
||||||
|
// Handle explicit type filter
|
||||||
|
return filter.type === type
|
||||||
|
}
|
||||||
|
if (!def) {
|
||||||
|
// No node definition available - allow it
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check if the filter is for input/outputs and apply
|
||||||
|
return (!filter.input || def.inputs > 0) &&
|
||||||
|
(!filter.output || def.outputs > 0)
|
||||||
}
|
}
|
||||||
function refreshTypeList(opts) {
|
function refreshTypeList(opts) {
|
||||||
var i;
|
var i;
|
||||||
@ -365,7 +382,7 @@ RED.typeSearch = (function() {
|
|||||||
var items = [];
|
var items = [];
|
||||||
RED.nodes.registry.getNodeTypes().forEach(function(t) {
|
RED.nodes.registry.getNodeTypes().forEach(function(t) {
|
||||||
var def = RED.nodes.getType(t);
|
var def = RED.nodes.getType(t);
|
||||||
if (def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
|
if (def.set?.enabled !== false && def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
|
||||||
items.push({type:t,def: def, label:getTypeLabel(t,def)});
|
items.push({type:t,def: def, label:getTypeLabel(t,def)});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -230,7 +230,7 @@ RED.utils = (function() {
|
|||||||
var pinnedPaths = {};
|
var pinnedPaths = {};
|
||||||
var formattedPaths = {};
|
var formattedPaths = {};
|
||||||
|
|
||||||
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools) {
|
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools,enablePinning) {
|
||||||
if (!pinnedPaths.hasOwnProperty(sourceId)) {
|
if (!pinnedPaths.hasOwnProperty(sourceId)) {
|
||||||
pinnedPaths[sourceId] = {}
|
pinnedPaths[sourceId] = {}
|
||||||
}
|
}
|
||||||
@ -250,7 +250,7 @@ RED.utils = (function() {
|
|||||||
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
||||||
})
|
})
|
||||||
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
||||||
if (strippedKey !== undefined && strippedKey !== '') {
|
if (enablePinning && strippedKey !== undefined && strippedKey !== '') {
|
||||||
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
|
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
|
||||||
|
|
||||||
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
|
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
@ -281,13 +281,16 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
|
function checkExpanded(strippedKey, expandPaths, { minRange, maxRange, expandLeafNodes }) {
|
||||||
if (expandPaths && expandPaths.length > 0) {
|
if (expandPaths && expandPaths.length > 0) {
|
||||||
if (strippedKey === '' && minRange === undefined) {
|
if (strippedKey === '' && minRange === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (var i=0;i<expandPaths.length;i++) {
|
for (var i=0;i<expandPaths.length;i++) {
|
||||||
var p = expandPaths[i];
|
var p = expandPaths[i];
|
||||||
|
if (expandLeafNodes && p === strippedKey) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
|
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
|
||||||
|
|
||||||
if (minRange !== undefined && p[strippedKey.length] === "[") {
|
if (minRange !== undefined && p[strippedKey.length] === "[") {
|
||||||
@ -394,6 +397,8 @@ RED.utils = (function() {
|
|||||||
var sourceId = options.sourceId;
|
var sourceId = options.sourceId;
|
||||||
var rootPath = options.rootPath;
|
var rootPath = options.rootPath;
|
||||||
var expandPaths = options.expandPaths;
|
var expandPaths = options.expandPaths;
|
||||||
|
const enablePinning = options.enablePinning
|
||||||
|
const expandLeafNodes = options.expandLeafNodes;
|
||||||
var ontoggle = options.ontoggle;
|
var ontoggle = options.ontoggle;
|
||||||
var exposeApi = options.exposeApi;
|
var exposeApi = options.exposeApi;
|
||||||
var tools = options.tools;
|
var tools = options.tools;
|
||||||
@ -416,11 +421,11 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
|
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
|
||||||
if (sourceId) {
|
if (sourceId) {
|
||||||
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools);
|
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools, enablePinning);
|
||||||
}
|
}
|
||||||
if (!key) {
|
if (!key) {
|
||||||
element.addClass("red-ui-debug-msg-top-level");
|
element.addClass("red-ui-debug-msg-top-level");
|
||||||
if (sourceId) {
|
if (sourceId && !expandPaths) {
|
||||||
var pinned = pinnedPaths[sourceId];
|
var pinned = pinnedPaths[sourceId];
|
||||||
expandPaths = [];
|
expandPaths = [];
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
@ -476,13 +481,23 @@ RED.utils = (function() {
|
|||||||
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
|
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
|
||||||
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
|
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
|
||||||
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
||||||
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
|
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
||||||
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
||||||
$('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
|
$('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let n = RED.nodes.node(obj) ?? RED.nodes.workspace(obj);
|
||||||
|
if (n) {
|
||||||
|
if (options.nodeSelector && "function" == typeof options.nodeSelector) {
|
||||||
|
e.css('cursor', 'pointer').on("click", function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
options.nodeSelector(n.id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (typeof obj === 'number') {
|
} else if (typeof obj === 'number') {
|
||||||
e = $('<span class="red-ui-debug-msg-type-number"></span>').appendTo(entryObj);
|
e = $('<span class="red-ui-debug-msg-type-number"></span>').appendTo(entryObj);
|
||||||
|
|
||||||
@ -582,13 +597,16 @@ RED.utils = (function() {
|
|||||||
typeHint: type==='buffer'?'hex':false,
|
typeHint: type==='buffer'?'hex':false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path+"["+i+"]",
|
path: path+"["+i+"]",
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
@ -612,20 +630,23 @@ RED.utils = (function() {
|
|||||||
typeHint: type==='buffer'?'hex':false,
|
typeHint: type==='buffer'?'hex':false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path+"["+i+"]",
|
path: path+"["+i+"]",
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
|
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
|
||||||
checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
|
checkExpanded(strippedKey,expandPaths,{ minRange, maxRange: Math.min(fullLength-1,(minRange+9)), expandLeafNodes}));
|
||||||
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
|
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
|
||||||
}
|
}
|
||||||
if (fullLength < originalLength) {
|
if (fullLength < originalLength) {
|
||||||
@ -634,7 +655,7 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
||||||
checkExpanded(strippedKey,expandPaths));
|
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
} else if (typeof obj === 'object') {
|
} else if (typeof obj === 'object') {
|
||||||
element.addClass('collapsed');
|
element.addClass('collapsed');
|
||||||
@ -668,13 +689,16 @@ RED.utils = (function() {
|
|||||||
typeHint: false,
|
typeHint: false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: newPath,
|
path: newPath,
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
@ -683,7 +707,7 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
||||||
checkExpanded(strippedKey,expandPaths));
|
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
if (key) {
|
if (key) {
|
||||||
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
|
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
|
||||||
@ -888,11 +912,25 @@ RED.utils = (function() {
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePropertyExpression(str) {
|
/**
|
||||||
|
* Validate a property expression
|
||||||
|
* @param {*} str - the property value
|
||||||
|
* @returns {boolean|string} whether the node proprty is valid. `true`: valid `false|String`: invalid
|
||||||
|
*/
|
||||||
|
function validatePropertyExpression(str, opt) {
|
||||||
try {
|
try {
|
||||||
var parts = normalisePropertyExpression(str);
|
const parts = normalisePropertyExpression(str);
|
||||||
return true;
|
return true;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
// If the validator has opt, it is a 3.x validator that
|
||||||
|
// can return a String to mean 'invalid' and provide a reason
|
||||||
|
if (opt) {
|
||||||
|
if (opt.label) {
|
||||||
|
return opt.label + ': ' + err.message;
|
||||||
|
}
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
// Otherwise, a 2.x returns a false value
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -910,22 +948,24 @@ RED.utils = (function() {
|
|||||||
// Allow ${ENV_VAR} value
|
// Allow ${ENV_VAR} value
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
let error
|
let error;
|
||||||
if (propertyType === 'json') {
|
if (propertyType === 'json') {
|
||||||
try {
|
try {
|
||||||
JSON.parse(propertyValue);
|
JSON.parse(propertyValue);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
error = RED._("validator.errors.invalid-json", {
|
error = RED._("validator.errors.invalid-json", {
|
||||||
error: err.message
|
error: err.message
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
|
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
|
||||||
if (!RED.utils.validatePropertyExpression(propertyValue)) {
|
// To avoid double label
|
||||||
error = RED._("validator.errors.invalid-prop")
|
const valid = RED.utils.validatePropertyExpression(propertyValue, opt ? {} : null);
|
||||||
|
if (valid !== true) {
|
||||||
|
error = opt ? valid : RED._("validator.errors.invalid-prop");
|
||||||
}
|
}
|
||||||
} else if (propertyType === 'num') {
|
} else if (propertyType === 'num') {
|
||||||
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
|
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
|
||||||
error = RED._("validator.errors.invalid-num")
|
error = RED._("validator.errors.invalid-num");
|
||||||
}
|
}
|
||||||
} else if (propertyType === 'jsonata') {
|
} else if (propertyType === 'jsonata') {
|
||||||
try {
|
try {
|
||||||
@ -933,16 +973,16 @@ RED.utils = (function() {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
error = RED._("validator.errors.invalid-expr", {
|
error = RED._("validator.errors.invalid-expr", {
|
||||||
error: err.message
|
error: err.message
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
if (opt && opt.label) {
|
if (opt && opt.label) {
|
||||||
return opt.label+': '+error
|
return opt.label + ': ' + error;
|
||||||
}
|
}
|
||||||
return error
|
return error;
|
||||||
}
|
}
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessageProperty(msg,expr) {
|
function getMessageProperty(msg,expr) {
|
||||||
|
@ -9,14 +9,27 @@ RED.view.annotations = (function() {
|
|||||||
addAnnotation(evt.node.__pendingAnnotation__,evt);
|
addAnnotation(evt.node.__pendingAnnotation__,evt);
|
||||||
delete evt.node.__pendingAnnotation__;
|
delete evt.node.__pendingAnnotation__;
|
||||||
}
|
}
|
||||||
var badgeDX = 0;
|
let badgeRDX = 0;
|
||||||
var controlDX = 0;
|
let badgeLDX = 0;
|
||||||
for (var i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
const scale = RED.view.scale()
|
||||||
var annotation = evt.el.__annotations__[i];
|
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
||||||
|
const annotation = evt.el.__annotations__[i];
|
||||||
if (annotations.hasOwnProperty(annotation.id)) {
|
if (annotations.hasOwnProperty(annotation.id)) {
|
||||||
var opts = annotations[annotation.id];
|
const opts = annotations[annotation.id];
|
||||||
var showAnnotation = true;
|
let showAnnotation = true;
|
||||||
var isBadge = opts.type === 'badge';
|
const isBadge = opts.type === 'badge';
|
||||||
|
if (opts.refresh !== undefined) {
|
||||||
|
let refreshAnnotation = false
|
||||||
|
if (typeof opts.refresh === "string") {
|
||||||
|
refreshAnnotation = !!evt.node[opts.refresh]
|
||||||
|
delete evt.node[opts.refresh]
|
||||||
|
} else if (typeof opts.refresh === "function") {
|
||||||
|
refreshAnnotation = opts.refresh(evnt.node)
|
||||||
|
}
|
||||||
|
if (refreshAnnotation) {
|
||||||
|
refreshAnnotationElement(annotation.id, annotation.node, annotation.element)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (opts.show !== undefined) {
|
if (opts.show !== undefined) {
|
||||||
if (typeof opts.show === "string") {
|
if (typeof opts.show === "string") {
|
||||||
showAnnotation = !!evt.node[opts.show]
|
showAnnotation = !!evt.node[opts.show]
|
||||||
@ -29,17 +42,26 @@ RED.view.annotations = (function() {
|
|||||||
}
|
}
|
||||||
if (isBadge) {
|
if (isBadge) {
|
||||||
if (showAnnotation) {
|
if (showAnnotation) {
|
||||||
var rect = annotation.element.getBoundingClientRect();
|
// getBoundingClientRect is in real-world scale so needs to be adjusted according to
|
||||||
badgeDX += rect.width;
|
// the current scale factor
|
||||||
annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)");
|
const rectWidth = annotation.element.getBoundingClientRect().width / scale;
|
||||||
badgeDX += 4;
|
let annotationX
|
||||||
}
|
if (!opts.align || opts.align === 'right') {
|
||||||
} else {
|
annotationX = evt.node.w - 3 - badgeRDX - rectWidth
|
||||||
if (showAnnotation) {
|
badgeRDX += rectWidth + 4;
|
||||||
var rect = annotation.element.getBoundingClientRect();
|
|
||||||
annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
|
} else if (opts.align === 'left') {
|
||||||
controlDX += rect.width + 4;
|
annotationX = 3 + badgeLDX
|
||||||
|
badgeLDX += rectWidth + 4;
|
||||||
|
}
|
||||||
|
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
|
||||||
}
|
}
|
||||||
|
// } else {
|
||||||
|
// if (showAnnotation) {
|
||||||
|
// var rect = annotation.element.getBoundingClientRect();
|
||||||
|
// annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
|
||||||
|
// controlDX += rect.width + 4;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
annotation.element.parentNode.removeChild(annotation.element);
|
annotation.element.parentNode.removeChild(annotation.element);
|
||||||
@ -95,15 +117,25 @@ RED.view.annotations = (function() {
|
|||||||
annotationGroup.setAttribute("class",opts.class || "");
|
annotationGroup.setAttribute("class",opts.class || "");
|
||||||
evt.el.__annotations__.push({
|
evt.el.__annotations__.push({
|
||||||
id:id,
|
id:id,
|
||||||
|
node: evt.node,
|
||||||
element: annotationGroup
|
element: annotationGroup
|
||||||
});
|
});
|
||||||
var annotation = opts.element(evt.node);
|
refreshAnnotationElement(id, evt.node, annotationGroup)
|
||||||
|
evt.el.appendChild(annotationGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAnnotationElement(id, node, annotationGroup) {
|
||||||
|
const opts = annotations[id];
|
||||||
|
const annotation = opts.element(node);
|
||||||
if (opts.tooltip) {
|
if (opts.tooltip) {
|
||||||
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip));
|
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation, node, opts.tooltip));
|
||||||
annotation.addEventListener("mouseleave", annotationMouseLeave);
|
annotation.addEventListener("mouseleave", annotationMouseLeave);
|
||||||
}
|
}
|
||||||
|
if (annotationGroup.hasChildNodes()) {
|
||||||
|
annotationGroup.removeChild(annotationGroup.firstChild)
|
||||||
|
}
|
||||||
annotationGroup.appendChild(annotation);
|
annotationGroup.appendChild(annotation);
|
||||||
evt.el.appendChild(annotationGroup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1102,18 +1102,27 @@ RED.view.tools = (function() {
|
|||||||
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
|
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
|
||||||
const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
|
const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
|
||||||
if (!typeIndex.hasOwnProperty(n.type)) {
|
if (!typeIndex.hasOwnProperty(n.type)) {
|
||||||
const existingNodes = RED.nodes.filterNodes({type: n.type})
|
const existingNodes = RED.nodes.filterNodes({ type: n.type });
|
||||||
let maxNameNumber = 0;
|
const existingIds = existingNodes.reduce((ids, node) => {
|
||||||
existingNodes.forEach(n => {
|
let match = defaultNodeNameRE.exec(node.name);
|
||||||
let match = defaultNodeNameRE.exec(n.name)
|
|
||||||
if (match) {
|
if (match) {
|
||||||
let nodeNumber = parseInt(match[1])
|
const nodeNumber = parseInt(match[1], 10);
|
||||||
if (nodeNumber > maxNameNumber) {
|
if (!ids.includes(nodeNumber)) {
|
||||||
maxNameNumber = nodeNumber
|
ids.push(nodeNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
return ids;
|
||||||
typeIndex[n.type] = maxNameNumber + 1
|
}, []).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
let availableNameNumber = 1;
|
||||||
|
for (let i = 0; i < existingIds.length; i++) {
|
||||||
|
if (existingIds[i] !== availableNameNumber) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
availableNameNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeIndex[n.type] = availableNameNumber;
|
||||||
}
|
}
|
||||||
if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
|
if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
|
||||||
if (generateHistory) {
|
if (generateHistory) {
|
||||||
@ -1145,11 +1154,11 @@ RED.view.tools = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addJunctionsToWires(wires) {
|
function addJunctionsToWires(options = {}) {
|
||||||
if (RED.workspaces.isLocked()) {
|
if (RED.workspaces.isLocked()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
|
let wiresToSplit = options.wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
|
||||||
if (!wiresToSplit) {
|
if (!wiresToSplit) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1197,21 +1206,26 @@ RED.view.tools = (function() {
|
|||||||
if (links.length === 0) {
|
if (links.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let pointCount = 0
|
if (addedJunctions.length === 0 && Object.hasOwn(options, 'x') && Object.hasOwn(options, 'y')) {
|
||||||
links.forEach(function(l) {
|
junction.x = options.x
|
||||||
if (l._sliceLocation) {
|
junction.y = options.y
|
||||||
junction.x += l._sliceLocation.x
|
} else {
|
||||||
junction.y += l._sliceLocation.y
|
let pointCount = 0
|
||||||
delete l._sliceLocation
|
links.forEach(function(l) {
|
||||||
pointCount++
|
if (l._sliceLocation) {
|
||||||
} else {
|
junction.x += l._sliceLocation.x
|
||||||
junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2
|
junction.y += l._sliceLocation.y
|
||||||
junction.y += l.source.y + l.target.y
|
delete l._sliceLocation
|
||||||
pointCount += 2
|
pointCount++
|
||||||
}
|
} else {
|
||||||
})
|
junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2
|
||||||
junction.x = Math.round(junction.x/pointCount)
|
junction.y += l.source.y + l.target.y
|
||||||
junction.y = Math.round(junction.y/pointCount)
|
pointCount += 2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
junction.x = Math.round(junction.x/pointCount)
|
||||||
|
junction.y = Math.round(junction.y/pointCount)
|
||||||
|
}
|
||||||
if (RED.view.snapGrid) {
|
if (RED.view.snapGrid) {
|
||||||
let gridSize = RED.view.gridSize()
|
let gridSize = RED.view.gridSize()
|
||||||
junction.x = (gridSize*Math.round(junction.x/gridSize));
|
junction.x = (gridSize*Math.round(junction.x/gridSize));
|
||||||
@ -1401,7 +1415,7 @@ RED.view.tools = (function() {
|
|||||||
RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
|
RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
|
||||||
|
|
||||||
RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
|
RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
|
||||||
RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() });
|
RED.actions.add("core:split-wires-with-junctions", function (options) { addJunctionsToWires(options) });
|
||||||
|
|
||||||
RED.actions.add("core:generate-node-names", generateNodeNames )
|
RED.actions.add("core:generate-node-names", generateNodeNames )
|
||||||
|
|
||||||
|
@ -288,7 +288,7 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
selectedLinks.clearUnselected()
|
selectedLinks.clearUnselected()
|
||||||
},
|
},
|
||||||
length: () => groups.length,
|
length: () => groups.size,
|
||||||
forEach: (func) => { groups.forEach(func) },
|
forEach: (func) => { groups.forEach(func) },
|
||||||
toArray: () => [...groups],
|
toArray: () => [...groups],
|
||||||
clear: function () {
|
clear: function () {
|
||||||
@ -321,8 +321,8 @@ RED.view = (function() {
|
|||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
RED.contextMenu.show({
|
RED.contextMenu.show({
|
||||||
type: 'workspace',
|
type: 'workspace',
|
||||||
x:evt.clientX-5,
|
x: evt.clientX,
|
||||||
y:evt.clientY-5
|
y: evt.clientY
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@ -1209,7 +1209,10 @@ RED.view = (function() {
|
|||||||
lasso = null;
|
lasso = null;
|
||||||
}
|
}
|
||||||
if (d3.event.touches || d3.event.button === 0) {
|
if (d3.event.touches || d3.event.button === 0) {
|
||||||
if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) {
|
if (
|
||||||
|
(mouse_mode === 0 && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) ||
|
||||||
|
mouse_mode === RED.state.QUICK_JOINING
|
||||||
|
) {
|
||||||
// Trigger quick add dialog
|
// Trigger quick add dialog
|
||||||
d3.event.stopPropagation();
|
d3.event.stopPropagation();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
@ -1262,11 +1265,6 @@ RED.view = (function() {
|
|||||||
var targetGroup = options.group;
|
var targetGroup = options.group;
|
||||||
var touchTrigger = options.touchTrigger;
|
var touchTrigger = options.touchTrigger;
|
||||||
|
|
||||||
if (targetGroup) {
|
|
||||||
selectedGroups.add(targetGroup,false);
|
|
||||||
RED.view.redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
// `point` is the place in the workspace the mouse has clicked.
|
// `point` is the place in the workspace the mouse has clicked.
|
||||||
// This takes into account scrolling and scaling of the workspace.
|
// This takes into account scrolling and scaling of the workspace.
|
||||||
var ox = point[0];
|
var ox = point[0];
|
||||||
@ -1285,7 +1283,6 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var mainPos = $("#red-ui-main-container").position();
|
var mainPos = $("#red-ui-main-container").position();
|
||||||
|
|
||||||
if (mouse_mode !== RED.state.QUICK_JOINING) {
|
if (mouse_mode !== RED.state.QUICK_JOINING) {
|
||||||
mouse_mode = RED.state.QUICK_JOINING;
|
mouse_mode = RED.state.QUICK_JOINING;
|
||||||
$(window).on('keyup',disableQuickJoinEventHandler);
|
$(window).on('keyup',disableQuickJoinEventHandler);
|
||||||
@ -1589,9 +1586,6 @@ RED.view = (function() {
|
|||||||
// auto select dropped node - so info shows (if visible)
|
// auto select dropped node - so info shows (if visible)
|
||||||
clearSelection();
|
clearSelection();
|
||||||
nn.selected = true;
|
nn.selected = true;
|
||||||
if (targetGroup) {
|
|
||||||
selectedGroups.add(targetGroup,false);
|
|
||||||
}
|
|
||||||
movingSet.add(nn);
|
movingSet.add(nn);
|
||||||
updateActiveNodes();
|
updateActiveNodes();
|
||||||
updateSelection();
|
updateSelection();
|
||||||
@ -2176,17 +2170,22 @@ RED.view = (function() {
|
|||||||
n.n.moved = true;
|
n.n.moved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If a node has moved and ends up being spliced into a link, keep
|
||||||
// Check to see if we need to splice a link
|
// track of which historyEvent to add the splice info to
|
||||||
|
let targetSpliceEvent = null
|
||||||
if (moveEvent.nodes.length > 0) {
|
if (moveEvent.nodes.length > 0) {
|
||||||
historyEvent.events.push(moveEvent)
|
historyEvent.events.push(moveEvent)
|
||||||
if (activeSpliceLink) {
|
targetSpliceEvent = moveEvent
|
||||||
var linkToSplice = d3.select(activeSpliceLink).data()[0];
|
|
||||||
spliceLink(linkToSplice, movingSet.get(0).n, moveEvent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (moveAndChangedGroupEvent.nodes.length > 0) {
|
if (moveAndChangedGroupEvent.nodes.length > 0) {
|
||||||
historyEvent.events.push(moveAndChangedGroupEvent)
|
historyEvent.events.push(moveAndChangedGroupEvent)
|
||||||
|
targetSpliceEvent = moveAndChangedGroupEvent
|
||||||
|
}
|
||||||
|
// activeSpliceLink will only be set if the movingSet has a single
|
||||||
|
// node that is able to splice.
|
||||||
|
if (targetSpliceEvent && activeSpliceLink) {
|
||||||
|
var linkToSplice = d3.select(activeSpliceLink).data()[0];
|
||||||
|
spliceLink(linkToSplice, movingSet.get(0).n, targetSpliceEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only continue if something has moved
|
// Only continue if something has moved
|
||||||
@ -2687,22 +2686,21 @@ RED.view = (function() {
|
|||||||
addToRemovedLinks(reconnectResult.removedLinks)
|
addToRemovedLinks(reconnectResult.removedLinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
var startDirty = RED.nodes.dirty();
|
const startDirty = RED.nodes.dirty();
|
||||||
var startChanged = false;
|
let movingSelectedGroups = [];
|
||||||
var selectedGroups = [];
|
|
||||||
if (movingSet.length() > 0) {
|
if (movingSet.length() > 0) {
|
||||||
|
|
||||||
for (var i=0;i<movingSet.length();i++) {
|
for (var i=0;i<movingSet.length();i++) {
|
||||||
node = movingSet.get(i).n;
|
node = movingSet.get(i).n;
|
||||||
if (node.type === "group") {
|
if (node.type === "group") {
|
||||||
selectedGroups.push(node);
|
movingSelectedGroups.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure we have identified all groups about to be deleted
|
// Make sure we have identified all groups about to be deleted
|
||||||
for (i=0;i<selectedGroups.length;i++) {
|
for (i=0;i<movingSelectedGroups.length;i++) {
|
||||||
selectedGroups[i].nodes.forEach(function(n) {
|
movingSelectedGroups[i].nodes.forEach(function(n) {
|
||||||
if (n.type === "group" && selectedGroups.indexOf(n) === -1) {
|
if (n.type === "group" && movingSelectedGroups.indexOf(n) === -1) {
|
||||||
selectedGroups.push(n);
|
movingSelectedGroups.push(n);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2719,7 +2717,7 @@ RED.view = (function() {
|
|||||||
addToRemovedLinks(removedEntities.links);
|
addToRemovedLinks(removedEntities.links);
|
||||||
if (node.g) {
|
if (node.g) {
|
||||||
var group = RED.nodes.group(node.g);
|
var group = RED.nodes.group(node.g);
|
||||||
if (selectedGroups.indexOf(group) === -1) {
|
if (movingSelectedGroups.indexOf(group) === -1) {
|
||||||
// Don't use RED.group.removeFromGroup as that emits
|
// Don't use RED.group.removeFromGroup as that emits
|
||||||
// a change event on the node - but we're deleting it
|
// a change event on the node - but we're deleting it
|
||||||
var index = group.nodes.indexOf(node);
|
var index = group.nodes.indexOf(node);
|
||||||
@ -2733,7 +2731,7 @@ RED.view = (function() {
|
|||||||
removedLinks = removedLinks.concat(result.links);
|
removedLinks = removedLinks.concat(result.links);
|
||||||
if (node.g) {
|
if (node.g) {
|
||||||
var group = RED.nodes.group(node.g);
|
var group = RED.nodes.group(node.g);
|
||||||
if (selectedGroups.indexOf(group) === -1) {
|
if (movingSelectedGroups.indexOf(group) === -1) {
|
||||||
// Don't use RED.group.removeFromGroup as that emits
|
// Don't use RED.group.removeFromGroup as that emits
|
||||||
// a change event on the node - but we're deleting it
|
// a change event on the node - but we're deleting it
|
||||||
var index = group.nodes.indexOf(node);
|
var index = group.nodes.indexOf(node);
|
||||||
@ -2755,8 +2753,8 @@ RED.view = (function() {
|
|||||||
|
|
||||||
// Groups must be removed in the right order - from inner-most
|
// Groups must be removed in the right order - from inner-most
|
||||||
// to outermost.
|
// to outermost.
|
||||||
for (i = selectedGroups.length-1; i>=0; i--) {
|
for (i = movingSelectedGroups.length-1; i>=0; i--) {
|
||||||
var g = selectedGroups[i];
|
var g = movingSelectedGroups[i];
|
||||||
removedGroups.push(g);
|
removedGroups.push(g);
|
||||||
RED.nodes.removeGroup(g);
|
RED.nodes.removeGroup(g);
|
||||||
}
|
}
|
||||||
@ -3057,8 +3055,8 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function disableQuickJoinEventHandler(evt) {
|
function disableQuickJoinEventHandler(evt) {
|
||||||
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
|
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari), or Escape
|
||||||
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
|
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91 || evt.keyCode === 27) {
|
||||||
resetMouseVars();
|
resetMouseVars();
|
||||||
hideDragLines();
|
hideDragLines();
|
||||||
redraw();
|
redraw();
|
||||||
@ -3189,27 +3187,59 @@ RED.view = (function() {
|
|||||||
|
|
||||||
for (i=0;i<drag_lines.length;i++) {
|
for (i=0;i<drag_lines.length;i++) {
|
||||||
if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
|
if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
|
||||||
var drag_line = drag_lines[i];
|
let drag_line = drag_lines[i];
|
||||||
var src,dst,src_port;
|
let src,dst,src_port;
|
||||||
|
let oldDst;
|
||||||
|
let oldSrc;
|
||||||
if (drag_line.portType === PORT_TYPE_OUTPUT) {
|
if (drag_line.portType === PORT_TYPE_OUTPUT) {
|
||||||
src = drag_line.node;
|
src = drag_line.node;
|
||||||
src_port = drag_line.port;
|
src_port = drag_line.port;
|
||||||
dst = mouseup_node;
|
dst = mouseup_node;
|
||||||
|
oldSrc = src;
|
||||||
|
if (drag_line.link) {
|
||||||
|
oldDst = drag_line.link.target;
|
||||||
|
}
|
||||||
} else if (drag_line.portType === PORT_TYPE_INPUT) {
|
} else if (drag_line.portType === PORT_TYPE_INPUT) {
|
||||||
src = mouseup_node;
|
src = mouseup_node;
|
||||||
dst = drag_line.node;
|
dst = drag_line.node;
|
||||||
src_port = portIndex || 0;
|
src_port = portIndex || 0;
|
||||||
|
oldSrc = dst;
|
||||||
|
if (drag_line.link) {
|
||||||
|
oldDst = drag_line.link.source
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var link = {source: src, sourcePort:src_port, target: dst};
|
var link = {source: src, sourcePort:src_port, target: dst};
|
||||||
if (drag_line.virtualLink) {
|
if (drag_line.virtualLink) {
|
||||||
if (/^link (in|out)$/.test(src.type) && /^link (in|out)$/.test(dst.type) && src.type !== dst.type) {
|
if (/^link (in|out)$/.test(src.type) && /^link (in|out)$/.test(dst.type) && src.type !== dst.type) {
|
||||||
if (src.links.indexOf(dst.id) === -1 && dst.links.indexOf(src.id) === -1) {
|
if (src.links.indexOf(dst.id) === -1 && dst.links.indexOf(src.id) === -1) {
|
||||||
var oldSrcLinks = $.extend(true,{},{v:src.links}).v
|
var oldSrcLinks = [...src.links]
|
||||||
var oldDstLinks = $.extend(true,{},{v:dst.links}).v
|
var oldDstLinks = [...dst.links]
|
||||||
|
|
||||||
src.links.push(dst.id);
|
src.links.push(dst.id);
|
||||||
dst.links.push(src.id);
|
dst.links.push(src.id);
|
||||||
|
|
||||||
|
if (oldDst) {
|
||||||
|
src.links = src.links.filter(id => id !== oldDst.id)
|
||||||
|
dst.links = dst.links.filter(id => id !== oldDst.id)
|
||||||
|
var oldOldDstLinks = [...oldDst.links]
|
||||||
|
oldDst.links = oldDst.links.filter(id => id !== oldSrc.id)
|
||||||
|
oldDst.dirty = true;
|
||||||
|
modifiedNodes.push(oldDst);
|
||||||
|
linkEditEvents.push({
|
||||||
|
t:'edit',
|
||||||
|
node: oldDst,
|
||||||
|
dirty: RED.nodes.dirty(),
|
||||||
|
changed: oldDst.changed,
|
||||||
|
changes: {
|
||||||
|
links:oldOldDstLinks
|
||||||
|
}
|
||||||
|
});
|
||||||
|
oldDst.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
src.dirty = true;
|
src.dirty = true;
|
||||||
dst.dirty = true;
|
dst.dirty = true;
|
||||||
|
|
||||||
modifiedNodes.push(src);
|
modifiedNodes.push(src);
|
||||||
modifiedNodes.push(dst);
|
modifiedNodes.push(dst);
|
||||||
|
|
||||||
@ -3237,6 +3267,7 @@ RED.view = (function() {
|
|||||||
links:oldDstLinks
|
links:oldDstLinks
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
src.changed = true;
|
src.changed = true;
|
||||||
dst.changed = true;
|
dst.changed = true;
|
||||||
}
|
}
|
||||||
@ -5140,8 +5171,8 @@ RED.view = (function() {
|
|||||||
var delta = Infinity;
|
var delta = Infinity;
|
||||||
for (var i = 0; i < lineLength; i++) {
|
for (var i = 0; i < lineLength; i++) {
|
||||||
var linePos = pathLine.getPointAtLength(i);
|
var linePos = pathLine.getPointAtLength(i);
|
||||||
var posDeltaX = Math.abs(linePos.x-d3.event.offsetX)
|
var posDeltaX = Math.abs(linePos.x-(d3.event.offsetX / scaleFactor))
|
||||||
var posDeltaY = Math.abs(linePos.y-d3.event.offsetY)
|
var posDeltaY = Math.abs(linePos.y-(d3.event.offsetY / scaleFactor))
|
||||||
var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
|
var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
|
||||||
if (posDelta < delta) {
|
if (posDelta < delta) {
|
||||||
pos = linePos
|
pos = linePos
|
||||||
@ -6266,6 +6297,10 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (selection.links) {
|
||||||
|
selectedLinks.clear();
|
||||||
|
selection.links.forEach(selectedLinks.add);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSelection();
|
updateSelection();
|
||||||
|
@ -183,25 +183,29 @@ RED.workspaces = (function() {
|
|||||||
},
|
},
|
||||||
null)
|
null)
|
||||||
}
|
}
|
||||||
menuItems.push(
|
if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
|
||||||
{
|
|
||||||
id:"red-ui-tabs-menu-option-add-flow",
|
|
||||||
label: RED._("workspace.addFlow"),
|
|
||||||
onselect: "core:add-flow"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (isMenuButton || !!tab) {
|
|
||||||
menuItems.push(
|
menuItems.push(
|
||||||
{
|
{
|
||||||
id:"red-ui-tabs-menu-option-add-flow-right",
|
id:"red-ui-tabs-menu-option-add-flow",
|
||||||
label: RED._("workspace.addFlowToRight"),
|
label: RED._("workspace.addFlow"),
|
||||||
shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
|
onselect: "core:add-flow"
|
||||||
onselect: function() {
|
}
|
||||||
RED.actions.invoke("core:add-flow-to-right", tab)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
if (isMenuButton || !!tab) {
|
||||||
|
if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
|
||||||
|
menuItems.push(
|
||||||
|
{
|
||||||
|
id:"red-ui-tabs-menu-option-add-flow-right",
|
||||||
|
label: RED._("workspace.addFlowToRight"),
|
||||||
|
shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
|
||||||
|
onselect: function() {
|
||||||
|
RED.actions.invoke("core:add-flow-to-right", tab)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
if (activeWorkspace && activeWorkspace.type === 'tab') {
|
if (activeWorkspace && activeWorkspace.type === 'tab') {
|
||||||
menuItems.push(
|
menuItems.push(
|
||||||
isFlowDisabled ? {
|
isFlowDisabled ? {
|
||||||
@ -255,7 +259,9 @@ RED.workspaces = (function() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
menuItems.push(null)
|
if (menuItems.length > 0) {
|
||||||
|
menuItems.push(null)
|
||||||
|
}
|
||||||
if (isMenuButton || !!tab) {
|
if (isMenuButton || !!tab) {
|
||||||
menuItems.push(
|
menuItems.push(
|
||||||
{
|
{
|
||||||
@ -299,19 +305,24 @@ RED.workspaces = (function() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (tab) {
|
if (tab) {
|
||||||
|
menuItems.push(null)
|
||||||
|
|
||||||
|
if (RED.settings.theme("menu.menu-item-workspace-delete", true)) {
|
||||||
|
menuItems.push(
|
||||||
|
{
|
||||||
|
label: RED._("common.label.delete"),
|
||||||
|
onselect: function() {
|
||||||
|
if (tab.type === 'tab') {
|
||||||
|
RED.workspaces.delete(tab)
|
||||||
|
} else if (tab.type === 'subflow') {
|
||||||
|
RED.subflow.delete(tab.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disabled: isCurrentLocked || (workspaceTabCount === 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
menuItems.push(
|
menuItems.push(
|
||||||
null,
|
|
||||||
{
|
|
||||||
label: RED._("common.label.delete"),
|
|
||||||
onselect: function() {
|
|
||||||
if (tab.type === 'tab') {
|
|
||||||
RED.workspaces.delete(tab)
|
|
||||||
} else if (tab.type === 'subflow') {
|
|
||||||
RED.subflow.delete(tab.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
disabled: isCurrentLocked || (workspaceTabCount === 1)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: RED._("menu.label.export"),
|
label: RED._("menu.label.export"),
|
||||||
shortcut: RED.keyboard.getShortcut("core:show-export-dialog"),
|
shortcut: RED.keyboard.getShortcut("core:show-export-dialog"),
|
||||||
@ -359,11 +370,17 @@ RED.workspaces = (function() {
|
|||||||
RED.sidebar.config.refresh();
|
RED.sidebar.config.refresh();
|
||||||
RED.view.focus();
|
RED.view.focus();
|
||||||
},
|
},
|
||||||
onclick: function(tab) {
|
onclick: function(tab, evt) {
|
||||||
if (tab.id !== activeWorkspace) {
|
if(evt.which === 2) {
|
||||||
addToViewStack(activeWorkspace);
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
RED.actions.invoke("core:hide-flow", tab)
|
||||||
|
} else {
|
||||||
|
if (tab.id !== activeWorkspace) {
|
||||||
|
addToViewStack(activeWorkspace);
|
||||||
|
}
|
||||||
|
RED.view.focus();
|
||||||
}
|
}
|
||||||
RED.view.focus();
|
|
||||||
},
|
},
|
||||||
ondblclick: function(tab) {
|
ondblclick: function(tab) {
|
||||||
if (tab.type != "subflow") {
|
if (tab.type != "subflow") {
|
||||||
@ -401,6 +418,7 @@ RED.workspaces = (function() {
|
|||||||
if (tab.type === "tab") {
|
if (tab.type === "tab") {
|
||||||
workspaceTabCount--;
|
workspaceTabCount--;
|
||||||
} else {
|
} else {
|
||||||
|
RED.events.emit("workspace:close",{workspace: tab.id})
|
||||||
hideStack.push(tab.id);
|
hideStack.push(tab.id);
|
||||||
}
|
}
|
||||||
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
|
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
|
||||||
@ -461,7 +479,7 @@ RED.workspaces = (function() {
|
|||||||
},
|
},
|
||||||
minimumActiveTabWidth: 150,
|
minimumActiveTabWidth: 150,
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
addButton: "core:add-flow",
|
addButton: RED.settings.theme("menu.menu-item-workspace-add", true) ? "core:add-flow" : undefined,
|
||||||
addButtonCaption: RED._("workspace.addFlow"),
|
addButtonCaption: RED._("workspace.addFlow"),
|
||||||
menu: function() { return getMenuItems(true) },
|
menu: function() { return getMenuItems(true) },
|
||||||
contextmenu: function(tab) { return getMenuItems(false, tab) }
|
contextmenu: function(tab) { return getMenuItems(false, tab) }
|
||||||
@ -518,19 +536,24 @@ RED.workspaces = (function() {
|
|||||||
$(window).on("resize", function() {
|
$(window).on("resize", function() {
|
||||||
workspace_tabs.resize();
|
workspace_tabs.resize();
|
||||||
});
|
});
|
||||||
|
if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
|
||||||
RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
|
RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
|
||||||
RED.actions.add("core:add-flow-to-right",function(workspace) {
|
RED.actions.add("core:add-flow-to-right",function(workspace) {
|
||||||
let index
|
let index
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
index = workspace_tabs.getTabIndex(workspace.id)+1
|
index = workspace_tabs.getTabIndex(workspace.id)+1
|
||||||
} else {
|
} else {
|
||||||
index = workspace_tabs.activeIndex()+1
|
index = workspace_tabs.activeIndex()+1
|
||||||
}
|
}
|
||||||
addWorkspace(undefined,undefined,index)
|
addWorkspace(undefined,undefined,index)
|
||||||
});
|
});
|
||||||
RED.actions.add("core:edit-flow",editWorkspace);
|
}
|
||||||
RED.actions.add("core:remove-flow",removeWorkspace);
|
if (RED.settings.theme("menu.menu-item-workspace-edit", true)) {
|
||||||
|
RED.actions.add("core:edit-flow",editWorkspace);
|
||||||
|
}
|
||||||
|
if (RED.settings.theme("menu.menu-item-workspace-delete", true)) {
|
||||||
|
RED.actions.add("core:remove-flow",removeWorkspace);
|
||||||
|
}
|
||||||
RED.actions.add("core:enable-flow",enableWorkspace);
|
RED.actions.add("core:enable-flow",enableWorkspace);
|
||||||
RED.actions.add("core:disable-flow",disableWorkspace);
|
RED.actions.add("core:disable-flow",disableWorkspace);
|
||||||
RED.actions.add("core:lock-flow",lockWorkspace);
|
RED.actions.add("core:lock-flow",lockWorkspace);
|
||||||
@ -897,6 +920,17 @@ RED.workspaces = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh: function() {
|
refresh: function() {
|
||||||
|
var workspace = RED.nodes.workspace(RED.workspaces.active());
|
||||||
|
if (workspace) {
|
||||||
|
document.title = `${documentTitle} : ${workspace.label}`;
|
||||||
|
} else {
|
||||||
|
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||||
|
if (subflow) {
|
||||||
|
document.title = `${documentTitle} : ${subflow.name}`;
|
||||||
|
} else {
|
||||||
|
document.title = documentTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
RED.nodes.eachWorkspace(function(ws) {
|
RED.nodes.eachWorkspace(function(ws) {
|
||||||
workspace_tabs.renameTab(ws.id,ws.label);
|
workspace_tabs.renameTab(ws.id,ws.label);
|
||||||
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
|
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
|
||||||
|
@ -168,6 +168,37 @@ RED.user = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (data.prompts) {
|
||||||
|
if (data.loginMessage) {
|
||||||
|
const sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
$('<div>').text(data.loginMessage).appendTo(sessionMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (;i<data.prompts.length;i++) {
|
||||||
|
var field = data.prompts[i];
|
||||||
|
var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
var loginButton = $('<a href="#" class="red-ui-button"></a>',{style: "padding: 10px"}).appendTo(row).on("click", function() {
|
||||||
|
document.location = field.url;
|
||||||
|
});
|
||||||
|
if (field.image) {
|
||||||
|
$("<img>",{src:field.image}).appendTo(loginButton);
|
||||||
|
} else if (field.label) {
|
||||||
|
var label = $('<span></span>').text(field.label);
|
||||||
|
if (field.icon) {
|
||||||
|
$('<i></i>',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton);
|
||||||
|
label.css({
|
||||||
|
"verticalAlign":"middle",
|
||||||
|
"marginLeft":"8px"
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
label.appendTo(loginButton);
|
||||||
|
}
|
||||||
|
loginButton.button();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (opts.cancelable) {
|
if (opts.cancelable) {
|
||||||
$("#node-dialog-login-cancel").button().on("click", function( event ) {
|
$("#node-dialog-login-cancel").button().on("click", function( event ) {
|
||||||
@ -187,6 +218,7 @@ RED.user = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
|
RED.events.emit('logout')
|
||||||
var tokens = RED.settings.get("auth-tokens");
|
var tokens = RED.settings.get("auth-tokens");
|
||||||
var token = tokens?tokens.access_token:"";
|
var token = tokens?tokens.access_token:"";
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -211,6 +243,8 @@ RED.user = (function() {
|
|||||||
|
|
||||||
function updateUserMenu() {
|
function updateUserMenu() {
|
||||||
$("#red-ui-header-button-user-submenu li").remove();
|
$("#red-ui-header-button-user-submenu li").remove();
|
||||||
|
const userMenu = $("#red-ui-header-button-user")
|
||||||
|
userMenu.empty()
|
||||||
if (RED.settings.user.anonymous) {
|
if (RED.settings.user.anonymous) {
|
||||||
RED.menu.addItem("red-ui-header-button-user",{
|
RED.menu.addItem("red-ui-header-button-user",{
|
||||||
id:"usermenu-item-login",
|
id:"usermenu-item-login",
|
||||||
@ -238,7 +272,8 @@ RED.user = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const userIcon = generateUserIcon(RED.settings.user)
|
||||||
|
userIcon.appendTo(userMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
@ -247,14 +282,6 @@ RED.user = (function() {
|
|||||||
|
|
||||||
var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
|
var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
|
||||||
.prependTo(".red-ui-header-toolbar");
|
.prependTo(".red-ui-header-toolbar");
|
||||||
if (RED.settings.user.image) {
|
|
||||||
$('<span class="user-profile"></span>').css({
|
|
||||||
backgroundImage: "url("+RED.settings.user.image+")",
|
|
||||||
}).appendTo(userMenu.find("a"));
|
|
||||||
} else {
|
|
||||||
$('<i class="fa fa-user"></i>').appendTo(userMenu.find("a"));
|
|
||||||
}
|
|
||||||
|
|
||||||
RED.menu.init({id:"red-ui-header-button-user",
|
RED.menu.init({id:"red-ui-header-button-user",
|
||||||
options: []
|
options: []
|
||||||
});
|
});
|
||||||
@ -317,12 +344,30 @@ RED.user = (function() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateUserIcon(user) {
|
||||||
|
const userIcon = $('<span class="red-ui-user-profile"></span>')
|
||||||
|
if (user.image) {
|
||||||
|
userIcon.addClass('has_profile_image')
|
||||||
|
userIcon.css({
|
||||||
|
backgroundImage: "url("+user.image+")",
|
||||||
|
})
|
||||||
|
} else if (user.anonymous || (!user.username && !user.email)) {
|
||||||
|
$('<i class="fa fa-user"></i>').appendTo(userIcon);
|
||||||
|
} else {
|
||||||
|
$('<span>').text((user.username || user.email).substring(0,2)).appendTo(userIcon);
|
||||||
|
}
|
||||||
|
if (user.profileColor !== undefined) {
|
||||||
|
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
|
||||||
|
}
|
||||||
|
return userIcon
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
login: login,
|
login: login,
|
||||||
logout: logout,
|
logout: logout,
|
||||||
hasPermission: hasPermission
|
hasPermission: hasPermission,
|
||||||
|
generateUserIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -38,7 +38,7 @@ body {
|
|||||||
}
|
}
|
||||||
#red-ui-main-container {
|
#red-ui-main-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top:40px; left:0; bottom: 0; right:0;
|
top: var(--red-ui-header-height); left:0; bottom: 0; right:0;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +259,8 @@ $deploy-button-background-disabled-hover: #555;
|
|||||||
|
|
||||||
$header-background: #000;
|
$header-background: #000;
|
||||||
$header-button-background-active: #121212;
|
$header-button-background-active: #121212;
|
||||||
$header-menu-color: #C7C7C7;
|
$header-accent: #C02020;
|
||||||
|
$header-menu-color: #eee;
|
||||||
$header-menu-color-disabled: #666;
|
$header-menu-color-disabled: #666;
|
||||||
$header-menu-heading-color: #fff;
|
$header-menu-heading-color: #fff;
|
||||||
$header-menu-sublabel-color: #aeaeae;
|
$header-menu-sublabel-color: #aeaeae;
|
||||||
@ -313,6 +314,16 @@ $spinner-color: #999;
|
|||||||
|
|
||||||
$tab-icon-color: #dedede;
|
$tab-icon-color: #dedede;
|
||||||
|
|
||||||
|
// Anonymous User Colors
|
||||||
|
|
||||||
|
$user-profile-colors: (
|
||||||
|
1: #822e81,
|
||||||
|
2: #955e42,
|
||||||
|
3: #9c914f,
|
||||||
|
4: #748e54,
|
||||||
|
5: #06bcc1
|
||||||
|
);
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
$text-color-green: $text-color-success;
|
$text-color-green: $text-color-success;
|
||||||
$info-text-code-color: $text-color-code;
|
$info-text-code-color: $text-color-code;
|
||||||
|
@ -23,16 +23,20 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: var(--red-ui-header-height);
|
||||||
background: var(--red-ui-header-background);
|
background: var(--red-ui-header-background);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0px 0px 0px 20px;
|
padding: 0px 0px 0px 20px;
|
||||||
color: var(--red-ui-header-menu-color);
|
color: var(--red-ui-header-menu-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid var(--red-ui-header-accent);
|
||||||
|
padding-top: 2px;
|
||||||
|
|
||||||
span.red-ui-header-logo {
|
span.red-ui-header-logo {
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -42,7 +46,7 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
margin-left: 5px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
@ -59,25 +63,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.red-ui-header-toolbar {
|
.red-ui-header-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
> li {
|
> li {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: stretch;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
height: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 40px;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
padding: 0px 12px;
|
padding: 0px 12px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -178,6 +186,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.red-ui-deploy-button-group.readOnly {
|
||||||
|
.fa-caret-down { display: none; }
|
||||||
|
.fa-lock { display: inline-block; }
|
||||||
|
}
|
||||||
|
.red-ui-deploy-button-group:not(.readOnly) {
|
||||||
|
.fa-caret-down { display: inline-block; }
|
||||||
|
.fa-lock { display: none; }
|
||||||
|
}
|
||||||
|
.red-ui-deploy-button-group.readOnly {
|
||||||
|
a {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
li.open .button {
|
li.open .button {
|
||||||
background: var(--red-ui-header-button-background-active);
|
background: var(--red-ui-header-button-background-active);
|
||||||
border-color: var(--red-ui-header-button-background-active);
|
border-color: var(--red-ui-header-button-background-active);
|
||||||
@ -266,18 +288,44 @@
|
|||||||
#usermenu-item-username > .red-ui-menu-label {
|
#usermenu-item-username > .red-ui-menu-label {
|
||||||
color: var(--red-ui-header-menu-heading-color);
|
color: var(--red-ui-header-menu-heading-color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#red-ui-header-button-user .user-profile {
|
|
||||||
background-position: center center;
|
.red-ui-user-profile {
|
||||||
background-repeat: no-repeat;
|
background-color: var(--red-ui-header-background);
|
||||||
background-size: contain;
|
border: 2px solid var(--red-ui-header-menu-color);
|
||||||
display: inline-block;
|
border-radius: 30px;
|
||||||
width: 40px;
|
overflow: hidden;
|
||||||
height: 35px;
|
|
||||||
vertical-align: middle;
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
&.red-ui-user-profile-color-1 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-1);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-2 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-2);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-3 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-3);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-4 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-4);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-5 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 450px) {
|
@media only screen and (max-width: 450px) {
|
||||||
span.red-ui-header-logo > span {
|
span.red-ui-header-logo > span {
|
||||||
display: none;
|
display: none;
|
||||||
|
116
packages/node_modules/@node-red/editor-client/src/sass/multiplayer.scss
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#red-ui-multiplayer-user-list {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 5px;
|
||||||
|
li {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-ui-multiplayer-user-icon {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--red-ui-header-menu-color);
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-ui-multiplayer-user.inactive & {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.red-ui-user-profile {
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-multiplayer-users-tray {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 20px;
|
||||||
|
line-height: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
// &:hover {
|
||||||
|
// .red-ui-multiplayer-user-location {
|
||||||
|
// margin-left: 1px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
$multiplayer-user-icon-background: var(--red-ui-primary-background);
|
||||||
|
$multiplayer-user-icon-border: var(--red-ui-view-background);
|
||||||
|
$multiplayer-user-icon-text-color: var(--red-ui-header-menu-color);
|
||||||
|
$multiplayer-user-icon-count-text-color: var(--red-ui-primary-color);
|
||||||
|
$multiplayer-user-icon-shadow: 0px 0px 4px var(--red-ui-shadow);
|
||||||
|
.red-ui-multiplayer-user-location {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: -6px;
|
||||||
|
transition: margin-left 0.2s;
|
||||||
|
.red-ui-user-profile {
|
||||||
|
border: 1px solid $multiplayer-user-icon-border;
|
||||||
|
color: $multiplayer-user-icon-text-color;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 18px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: $multiplayer-user-icon-shadow;
|
||||||
|
&.red-ui-multiplayer-user-count {
|
||||||
|
color: $multiplayer-user-icon-count-text-color;
|
||||||
|
background-color: $multiplayer-user-icon-background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-ui-multiplayer-annotation {
|
||||||
|
.red-ui-multiplayer-annotation-background {
|
||||||
|
filter: drop-shadow($multiplayer-user-icon-shadow);
|
||||||
|
fill: $multiplayer-user-icon-background;
|
||||||
|
&.red-ui-user-profile-color-1 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-1);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-2 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-2);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-3 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-3);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-4 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-4);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-5 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-multiplayer-annotation-border {
|
||||||
|
stroke: $multiplayer-user-icon-border;
|
||||||
|
stroke-width: 1px;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.red-ui-multiplayer-annotation-anon-label {
|
||||||
|
fill: $multiplayer-user-icon-text-color;
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
text {
|
||||||
|
user-select: none;
|
||||||
|
fill: $multiplayer-user-icon-text-color;
|
||||||
|
stroke: none;
|
||||||
|
font-size: 10px;
|
||||||
|
&.red-ui-multiplayer-user-count {
|
||||||
|
fill: $multiplayer-user-icon-count-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
packages/node_modules/@node-red/editor-client/src/sass/sizes.scss
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
$header-height: 48px;
|
@ -15,4 +15,5 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
@import "sizes";
|
||||||
@import "variables";
|
@import "variables";
|
@ -15,6 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
@import "sizes";
|
||||||
@import "variables";
|
@import "variables";
|
||||||
@import "mixins";
|
@import "mixins";
|
||||||
|
|
||||||
@ -72,3 +73,5 @@
|
|||||||
@import "radialMenu";
|
@import "radialMenu";
|
||||||
|
|
||||||
@import "tourGuide";
|
@import "tourGuide";
|
||||||
|
|
||||||
|
@import "multiplayer";
|
||||||
|
@ -84,6 +84,11 @@ ul.red-ui-sidebar-node-config-list {
|
|||||||
background: var(--red-ui-node-config-background);
|
background: var(--red-ui-node-config-background);
|
||||||
color: var(--red-ui-primary-text-color);
|
color: var(--red-ui-primary-text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&.red-ui-palette-node-config-invalid.red-ui-palette-node-config-changed {
|
||||||
|
.red-ui-palette-node-annotations.red-ui-flow-node-error {
|
||||||
|
left: calc(100% - 28px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
|
ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
|
||||||
color: var(--red-ui-secondary-text-color);
|
color: var(--red-ui-secondary-text-color);
|
||||||
@ -115,6 +120,15 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
|
|||||||
.red-ui-palette-node-config-invalid {
|
.red-ui-palette-node-config-invalid {
|
||||||
border-color: var(--red-ui-form-input-border-error-color)
|
border-color: var(--red-ui-form-input-border-error-color)
|
||||||
}
|
}
|
||||||
|
.red-ui-sidebar-config-tray-header.red-ui-palette-header:not(.red-ui-sidebar-config-changed) .red-ui-flow-node-changed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.red-ui-sidebar-config-tray-header.red-ui-palette-header.red-ui-sidebar-config-changed .red-ui-flow-node-changed {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 1px;
|
||||||
|
}
|
||||||
.red-ui-palette-node-annotations {
|
.red-ui-palette-node-annotations {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(100% - 15px);
|
left: calc(100% - 15px);
|
||||||
|
@ -151,8 +151,9 @@
|
|||||||
&.red-ui-tabs-add {
|
&.red-ui-tabs-add {
|
||||||
padding-right: 29px;
|
padding-right: 29px;
|
||||||
}
|
}
|
||||||
&.red-ui-tabs-add.red-ui-tabs-scrollable {
|
&.red-ui-tabs-add.red-ui-tabs-scrollable,
|
||||||
padding-right: 53px;
|
&.red-ui-tabs-menu.red-ui-tabs-scrollable {
|
||||||
|
padding-right: 53px;
|
||||||
}
|
}
|
||||||
&.red-ui-tabs-add.red-ui-tabs-menu.red-ui-tabs-scrollable,
|
&.red-ui-tabs-add.red-ui-tabs-menu.red-ui-tabs-scrollable,
|
||||||
&.red-ui-tabs-add.red-ui-tabs-search.red-ui-tabs-scrollable {
|
&.red-ui-tabs-add.red-ui-tabs-search.red-ui-tabs-scrollable {
|
||||||
@ -310,8 +311,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.red-ui-tabs.red-ui-tabs-add .red-ui-tab-scroll-right {
|
.red-ui-tabs.red-ui-tabs-add .red-ui-tab-scroll-right,
|
||||||
right: 32px;
|
.red-ui-tabs.red-ui-tabs-menu .red-ui-tab-scroll-right {
|
||||||
|
right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.red-ui-tabs.red-ui-tabs-add.red-ui-tabs-menu .red-ui-tab-scroll-right,
|
.red-ui-tabs.red-ui-tabs-add.red-ui-tabs-menu .red-ui-tab-scroll-right,
|
||||||
|
@ -2,4 +2,15 @@
|
|||||||
&.red-ui-popover-panel {
|
&.red-ui-popover-panel {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.red-ui-autoComplete-completion {
|
||||||
|
font-family: var(--red-ui-monospace-font);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
direction: rtl;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
--red-ui-shadow: #{$shadow};
|
--red-ui-shadow: #{$shadow};
|
||||||
|
|
||||||
|
// Header Height
|
||||||
|
--red-ui-header-height: #{$header-height};
|
||||||
|
|
||||||
// Main body text
|
// Main body text
|
||||||
--red-ui-primary-text-color: #{$primary-text-color};
|
--red-ui-primary-text-color: #{$primary-text-color};
|
||||||
// UI control label text
|
// UI control label text
|
||||||
@ -240,6 +243,7 @@
|
|||||||
|
|
||||||
|
|
||||||
--red-ui-header-background: #{$header-background};
|
--red-ui-header-background: #{$header-background};
|
||||||
|
--red-ui-header-accent: #{$header-accent};
|
||||||
--red-ui-header-button-background-active: #{$header-button-background-active};
|
--red-ui-header-button-background-active: #{$header-button-background-active};
|
||||||
--red-ui-header-menu-color: #{$header-menu-color};
|
--red-ui-header-menu-color: #{$header-menu-color};
|
||||||
--red-ui-header-menu-color-disabled: #{$header-menu-color-disabled};
|
--red-ui-header-menu-color-disabled: #{$header-menu-color-disabled};
|
||||||
@ -295,4 +299,7 @@
|
|||||||
|
|
||||||
--red-ui-tab-icon-color: #{$tab-icon-color};
|
--red-ui-tab-icon-color: #{$tab-icon-color};
|
||||||
|
|
||||||
|
@each $current-color in 1 2 3 4 5 {
|
||||||
|
--red-ui-user-profile-colors-#{"" + $current-color}: #{map-get($user-profile-colors, $current-color)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
231
packages/node_modules/@node-red/editor-client/src/tours/3.1/welcome.js
vendored
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
export default {
|
||||||
|
version: "3.1.0",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
titleIcon: "fa fa-map-o",
|
||||||
|
title: {
|
||||||
|
"en-US": "Welcome to Node-RED 3.1!",
|
||||||
|
"ja": "Node-RED 3.1へようこそ!",
|
||||||
|
"fr": "Bienvenue dans Node-RED 3.1!"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||||
|
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
|
||||||
|
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "New ways to work with groups",
|
||||||
|
"ja": "グループの新たな操作方法",
|
||||||
|
"fr": "De nouvelles façons de travailler avec les groupes"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
|
||||||
|
<ul>
|
||||||
|
<li>They don't get in the way when clicking on a node</li>
|
||||||
|
<li>They can be reordered using the Moving Forwards and Move Backwards actions</li>
|
||||||
|
<li>Multiple nodes can be dragged into a group in one go</li>
|
||||||
|
<li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li>
|
||||||
|
</ul>`,
|
||||||
|
"ja": `<p>エディタ上のグループの操作が変更されました。</p>
|
||||||
|
<ul>
|
||||||
|
<li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li>
|
||||||
|
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
|
||||||
|
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
|
||||||
|
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
|
||||||
|
</ul>`,
|
||||||
|
"fr": `<p>Nous avons modifié la façon dont vous interagissez avec les groupes dans l'éditeur.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li>
|
||||||
|
<li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li>
|
||||||
|
<li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li>
|
||||||
|
<li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li>
|
||||||
|
</ul>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Change notification on tabs",
|
||||||
|
"ja": "タブ上の変更通知",
|
||||||
|
"fr": "Notification de changement sur les onglets"
|
||||||
|
},
|
||||||
|
image: '3.1/images/tab-changes.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>When a tab contains undeployed changes it now shows the
|
||||||
|
same style of change icon used by nodes.</p>
|
||||||
|
<p>This will make it much easier to track down changes when you're
|
||||||
|
working across multiple flows.</p>`,
|
||||||
|
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
|
||||||
|
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`,
|
||||||
|
"fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le
|
||||||
|
même style d'icône de changement utilisé par les noeuds.</p>
|
||||||
|
<p>Cela facilitera grandement le suivi des modifications lorsque vous
|
||||||
|
travaillez sur plusieurs flux.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "A bigger canvas to work with",
|
||||||
|
"ja": "より広くなった作業キャンバス",
|
||||||
|
"fr": "Un canevas plus grand pour travailler"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>The default canvas size has been increased so you can fit more
|
||||||
|
into one flow.</p>
|
||||||
|
<p>We still recommend using tools such as subflows and Link Nodes to help
|
||||||
|
keep things organised, but now you have more room to work in.</p>`,
|
||||||
|
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
|
||||||
|
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`,
|
||||||
|
"fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus
|
||||||
|
sur un seul flux.</p>
|
||||||
|
<p>Nous recommandons toujours d'utiliser des outils tels que les sous-flux et les noeuds de lien pour vous aider
|
||||||
|
à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Finding help",
|
||||||
|
"ja": "ヘルプを見つける",
|
||||||
|
"fr": "Trouver de l'aide"
|
||||||
|
},
|
||||||
|
image: '3.1/images/node-help.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>All node edit dialogs now include a link to that node's help
|
||||||
|
in the footer.</p>
|
||||||
|
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
|
||||||
|
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
|
||||||
|
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`,
|
||||||
|
"fr": `<p>Toutes les boîtes de dialogue d'édition de noeud incluent désormais un lien vers l'aide de ce noeud
|
||||||
|
dans le pied de page.</p>
|
||||||
|
<p>Cliquer dessus ouvrira la barre latérale d'aide affichant l'aide pour ce noeud.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Improved Context Menu",
|
||||||
|
"ja": "コンテキストメニューの改善",
|
||||||
|
"fr": "Menu contextuel amélioré"
|
||||||
|
},
|
||||||
|
image: '3.1/images/context-menu.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>The editor's context menu has been expanded to make lots more of
|
||||||
|
the built-in actions available.</p>
|
||||||
|
<p>Adding nodes, working with groups and plenty
|
||||||
|
of other useful tools are now just a click away.</p>
|
||||||
|
<p>The flow tab bar also has its own context menu to make working
|
||||||
|
with your flows much easier.</p>`,
|
||||||
|
"ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p>
|
||||||
|
<p>ノードの追加、グループの操作、その他の便利なツールをクリックするだけで実行できるようになりました。</p>
|
||||||
|
<p>フローのタブバーには、フローの操作をより簡単にする独自のコンテキストメニューもあります。</p>`,
|
||||||
|
"fr": `<p>Le menu contextuel de l'éditeur a été étendu pour faire beaucoup plus d'actions intégrées disponibles.</p>
|
||||||
|
<p>Ajouter des noeuds, travailler avec des groupes et beaucoup d'autres outils utiles sont désormais à portée de clic.</p>
|
||||||
|
<p>La barre d'onglets de flux possède également son propre menu contextuel pour faciliter l'utilisation de vos flux.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Hiding Flows",
|
||||||
|
"ja": "フローを非表示",
|
||||||
|
"fr": "Masquage de flux"
|
||||||
|
},
|
||||||
|
image: '3.1/images/hiding-flows.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>Hiding flows is now done through the flow context menu.</p>
|
||||||
|
<p>The 'hide' button in previous releases has been removed from the tabs
|
||||||
|
as they were being clicked accidentally too often.</p>`,
|
||||||
|
"ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p>
|
||||||
|
<p>これまでのリリースでタブに存在していた「非表示」ボタンは、よく誤ってクリックされていたため、削除されました。</p>`,
|
||||||
|
"fr": `<p>Le masquage des flux s'effectue désormais via le menu contextuel du flux.</p>
|
||||||
|
<p>Le bouton "Masquer" des versions précédentes a été supprimé des onglets
|
||||||
|
car il était cliqué accidentellement trop souvent.</p>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Locking Flows",
|
||||||
|
"ja": "フローを固定",
|
||||||
|
"fr": "Verrouillage de flux"
|
||||||
|
},
|
||||||
|
image: '3.1/images/locking-flows.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>Flows can now be locked to prevent accidental changes being made.</p>
|
||||||
|
<p>When locked you cannot modify the nodes in any way.</p>
|
||||||
|
<p>The flow context menu provides the options to lock and unlock flows,
|
||||||
|
as well as in the Info sidebar explorer.</p>`,
|
||||||
|
"ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p>
|
||||||
|
<p>固定されている時は、ノードを修正することはできません。</p>
|
||||||
|
<p>フローのコンテキストメニューと、情報サイドバーのエクスプローラには、フローの固定や解除をするためのオプションが用意されています。</p>`,
|
||||||
|
"fr": `<p>Les flux peuvent désormais être verrouillés pour éviter toute modification accidentelle.</p>
|
||||||
|
<p>Lorsqu'il est verrouillé, vous ne pouvez en aucun cas modifier les noeuds.</p>
|
||||||
|
<p>Le menu contextuel du flux fournit les options pour verrouiller et déverrouiller les flux,
|
||||||
|
ainsi que dans l'explorateur de la barre latérale d'informations.</p>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Adding Images to node/flow descriptions",
|
||||||
|
"ja": "ノードやフローの説明へ画像を追加",
|
||||||
|
"fr": "Ajout d'images aux descriptions de noeud/flux"
|
||||||
|
},
|
||||||
|
// image: 'images/debug-path-tooltip.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>You can now add images to a node's or flows's description.</p>
|
||||||
|
<p>Simply drag the image into the text editor and it will get added inline.</p>
|
||||||
|
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
|
||||||
|
"ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p>
|
||||||
|
<p>画像をテキストエディタにドラッグするだけで、行内に埋め込まれます。</p>
|
||||||
|
<p>情報サイドバーの説明を開くと、その画像が表示されます。</p>`,
|
||||||
|
"fr": `<p>Vous pouvez désormais ajouter des images à la description d'un noeud ou d'un flux.</p>
|
||||||
|
<p>Faites simplement glisser l'image dans l'éditeur de texte et elle sera ajoutée en ligne.</p>
|
||||||
|
<p>Lorsque la description s'affiche dans la barre latérale d'informations, l'image s'affiche.</p>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Adding Mermaid Diagrams",
|
||||||
|
"ja": "Mermaid図を追加",
|
||||||
|
"fr": "Ajout de diagrammes Mermaid"
|
||||||
|
},
|
||||||
|
image: '3.1/images/mermaid.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
|
||||||
|
<p>This gives you much richer options for documenting your flows.</p>`,
|
||||||
|
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
|
||||||
|
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`,
|
||||||
|
"fr": `<p>Vous pouvez également ajouter des diagrammes <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> directement dans vos descriptions de noeud ou de flux.</p>
|
||||||
|
<p>Cela vous offre des options beaucoup plus riches pour documenter vos flux.</p>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Managing Global Environment Variables",
|
||||||
|
"ja": "グローバル環境変数の管理",
|
||||||
|
"fr": "Gestion des variables d'environnement globales"
|
||||||
|
},
|
||||||
|
image: '3.1/images/global-env-vars.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
|
||||||
|
'Global Environment Variables' section of User Settings.</p>`,
|
||||||
|
"ja": `<p>ユーザ設定に新しく追加された「グローバル環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`,
|
||||||
|
"fr": `<p>Vous pouvez définir des variables d'environnement qui s'appliquent à tous les noeuds et flux dans la nouvelle
|
||||||
|
section "Global Environment Variables" des paramètres utilisateur.</p>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Node Updates",
|
||||||
|
"ja": "ノードの更新",
|
||||||
|
"fr": "Mises à jour des noeuds"
|
||||||
|
},
|
||||||
|
// image: "images/",
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
||||||
|
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
|
||||||
|
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`,
|
||||||
|
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et
|
||||||
|
petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-auto-complete.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-background-deploy.png
vendored
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-config-select.png
vendored
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-diff-update.png
vendored
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer-location.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer.png
vendored
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-plugins.png
vendored
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-sf-config.png
vendored
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-timestamp-formatting.png
vendored
Normal file
After Width: | Height: | Size: 15 KiB |
@ -1,12 +1,12 @@
|
|||||||
export default {
|
export default {
|
||||||
version: "3.1.0",
|
version: "4.0.0",
|
||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
titleIcon: "fa fa-map-o",
|
titleIcon: "fa fa-map-o",
|
||||||
title: {
|
title: {
|
||||||
"en-US": "Welcome to Node-RED 3.1!",
|
"en-US": "Welcome to Node-RED 4.0!",
|
||||||
"ja": "Node-RED 3.1へようこそ!",
|
"ja": "Node-RED 4.0 へようこそ!",
|
||||||
"fr": "Bienvenue dans Node-RED 3.1!"
|
"fr": "Bienvenue dans Node-RED 4.0!"
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||||
@ -16,202 +16,184 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
"en-US": "New ways to work with groups",
|
"en-US": "Multiplayer Mode",
|
||||||
"ja": "グループの新たな操作方法",
|
"ja": "複数ユーザ同時利用モード",
|
||||||
"fr": "De nouvelles façons de travailler avec les groupes"
|
"fr": "Mode Multi-utilisateur"
|
||||||
},
|
},
|
||||||
|
image: 'images/nr4-multiplayer-location.png',
|
||||||
description: {
|
description: {
|
||||||
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
|
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
|
||||||
|
to work with when you have multiple people editing flows at the same time.</p>
|
||||||
|
<p>When this feature is enabled, you will now see who else has the editor open and some
|
||||||
|
basic information on where they are in the editor.</p>
|
||||||
|
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
|
||||||
|
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
|
||||||
|
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
|
||||||
|
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
|
||||||
|
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
|
||||||
|
lorsque plusieurs personnes modifient des flux en même temps.</p>
|
||||||
|
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si d’autres utilisateurs ont
|
||||||
|
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
|
||||||
|
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
|
||||||
|
dans votre fichier de paramètres.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Better background deploy handling",
|
||||||
|
"ja": "バックグラウンドのデプロイ処理の改善",
|
||||||
|
"fr": "Meilleure gestion du déploiement en arrière-plan"
|
||||||
|
},
|
||||||
|
image: 'images/nr4-background-deploy.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
|
||||||
|
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
|
||||||
|
of changes.</p>`,
|
||||||
|
"ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`,
|
||||||
|
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
|
||||||
|
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Improved flow diffs",
|
||||||
|
"ja": "フローの差分表示の改善",
|
||||||
|
"fr": "Amélioration des différences de flux"
|
||||||
|
},
|
||||||
|
image: 'images/nr4-diff-update.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
|
||||||
|
changes and those that have only been moved.<p>
|
||||||
|
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.</p>`,
|
||||||
|
"ja": `<p>フローの変更内容を表示する時に、Node-REDは設定が変更されたノードと、移動されただけのノードを区別するようになりました。<p>
|
||||||
|
<p>これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。</p>`,
|
||||||
|
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
|
||||||
|
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
|
||||||
|
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
|
||||||
|
plus importants.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Better Configuration Node UX",
|
||||||
|
"ja": "設定ノードのUXが向上",
|
||||||
|
"fr": "Meilleure expérience utilisateur du noeud de configuration"
|
||||||
|
},
|
||||||
|
image: 'images/nr4-config-select.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
|
||||||
|
next to the select box.</p>
|
||||||
|
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
|
||||||
|
"ja": `<p>設定ノードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
|
||||||
|
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
|
||||||
|
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
|
||||||
|
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
|
||||||
|
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
"en-US": "Timestamp formatting options",
|
||||||
|
"ja": "タイムスタンプの形式の項目",
|
||||||
|
"fr": "Options de formatage de l'horodatage"
|
||||||
|
},
|
||||||
|
image: 'images/nr4-timestamp-formatting.png',
|
||||||
|
description: {
|
||||||
|
"en-US": `<p>Nodes that let you set a timestamp now have options on what format that timestamp should be in.</p>
|
||||||
|
<p>We're keeping it simple to begin with by providing three options:<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>They don't get in the way when clicking on a node</li>
|
<li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li>
|
||||||
<li>They can be reordered using the Moving Forwards and Move Backwards actions</li>
|
<li>ISO 8601 - a common format used by many systems</li>
|
||||||
<li>Multiple nodes can be dragged into a group in one go</li>
|
<li>JavaScript Date Object</li>
|
||||||
<li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li>
|
|
||||||
</ul>`,
|
</ul>`,
|
||||||
"ja": `<p>エディタ上のグループの操作が変更されました。</p>
|
"ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p>
|
||||||
|
<p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li>
|
<li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li>
|
||||||
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
|
<li>ISO 8601 - 多くのシステムで使用されている共通の形式</li>
|
||||||
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
|
<li>JavaScript日付オブジェクト</li>
|
||||||
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
|
|
||||||
</ul>`,
|
</ul>`,
|
||||||
"fr": `<p>Nous avons modifié la façon dont vous interagissez avec les groupes dans l'éditeur.</p>
|
"fr": `<p>Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.</p>
|
||||||
|
<p>Nous gardons les choses simples en proposant trois options :<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li>
|
<li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li>
|
||||||
<li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li>
|
<li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li>
|
||||||
<li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li>
|
<li>Objet Date JavaScript</li>
|
||||||
<li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li>
|
|
||||||
</ul>`
|
</ul>`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
"en-US": "Change notification on tabs",
|
"en-US": "Auto-complete of flow/global and env types",
|
||||||
"ja": "タブ上の変更通知",
|
"ja": "フロー/グローバル、環境変数の型の自動補完",
|
||||||
"fr": "Notification de changement sur les onglets"
|
"fr": "Saisie automatique des types de flux/global et env"
|
||||||
},
|
},
|
||||||
image: 'images/tab-changes.png',
|
image: 'images/nr4-auto-complete.png',
|
||||||
description: {
|
description: {
|
||||||
"en-US": `<p>When a tab contains undeployed changes it now shows the
|
"en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input
|
||||||
same style of change icon used by nodes.</p>
|
now all include auto-complete suggestions based on the live state of your flows.</p>
|
||||||
<p>This will make it much easier to track down changes when you're
|
`,
|
||||||
working across multiple flows.</p>`,
|
"ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p>
|
||||||
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
|
`,
|
||||||
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`,
|
"fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code>
|
||||||
"fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le
|
incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p>
|
||||||
même style d'icône de changement utilisé par les noeuds.</p>
|
`,
|
||||||
<p>Cela facilitera grandement le suivi des modifications lorsque vous
|
|
||||||
travaillez sur plusieurs flux.</p>`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
"en-US": "A bigger canvas to work with",
|
"en-US": "Config node customisation in Subflows",
|
||||||
"ja": "より広くなった作業キャンバス",
|
"ja": "サブフローでの設定ノードのカスタマイズ",
|
||||||
"fr": "Un canevas plus grand pour travailler"
|
"fr": "Personnalisation du noeud de configuration dans les sous-flux"
|
||||||
},
|
},
|
||||||
|
image: 'images/nr4-sf-config.png',
|
||||||
description: {
|
description: {
|
||||||
"en-US": `<p>The default canvas size has been increased so you can fit more
|
"en-US": `<p>Subflows can now be customised to allow each instance to use a different
|
||||||
into one flow.</p>
|
config node of a selected type.</p>
|
||||||
<p>We still recommend using tools such as subflows and Link Nodes to help
|
<p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing
|
||||||
keep things organised, but now you have more room to work in.</p>`,
|
of the messages received can be pointed at a different broker.</p>
|
||||||
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
|
`,
|
||||||
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`,
|
"ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p>
|
||||||
"fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus
|
<p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p>
|
||||||
sur un seul flux.</p>
|
`,
|
||||||
<p>Nous recommandons toujours d'utiliser des outils tels que les sous-flux et les noeuds de lien pour vous aider
|
"fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un
|
||||||
à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>`
|
noeud de configuration d'un type sélectionné.</p>
|
||||||
|
<p>Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement
|
||||||
|
des messages reçus peut être pointée vers un autre courtier.</p>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
"en-US": "Finding help",
|
"en-US": "Remembering palette state",
|
||||||
"ja": "ヘルプを見つける",
|
"ja": "パレットの状態を維持",
|
||||||
"fr": "Trouver de l'aide"
|
"fr": "Mémorisation de l'état de la palette"
|
||||||
},
|
},
|
||||||
image: 'images/node-help.png',
|
|
||||||
description: {
|
description: {
|
||||||
"en-US": `<p>All node edit dialogs now include a link to that node's help
|
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
|
||||||
in the footer.</p>
|
filter you have applied.</p>`,
|
||||||
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
|
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
|
||||||
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
|
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
|
||||||
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`,
|
ainsi que le filtre que vous avez appliqué.</p>`
|
||||||
"fr": `<p>Toutes les boîtes de dialogue d'édition de noeud incluent désormais un lien vers l'aide de ce noeud
|
|
||||||
dans le pied de page.</p>
|
|
||||||
<p>Cliquer dessus ouvrira la barre latérale d'aide affichant l'aide pour ce noeud.</p>`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
"en-US": "Improved Context Menu",
|
"en-US": "Plugins shown in the Palette Manager",
|
||||||
"ja": "コンテキストメニューの改善",
|
"ja": "パレット管理にプラグインを表示",
|
||||||
"fr": "Menu contextuel amélioré"
|
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
|
||||||
},
|
},
|
||||||
image: 'images/context-menu.png',
|
image: 'images/nr4-plugins.png',
|
||||||
description: {
|
description: {
|
||||||
"en-US": `<p>The editor's context menu has been expanded to make lots more of
|
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
|
||||||
the built-in actions available.</p>
|
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
|
||||||
<p>Adding nodes, working with groups and plenty
|
nodes for the palette.</p>`,
|
||||||
of other useful tools are now just a click away.</p>
|
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
|
||||||
<p>The flow tab bar also has its own context menu to make working
|
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
|
||||||
with your flows much easier.</p>`,
|
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
|
||||||
"ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p>
|
des noeuds pour la palette.</p>`
|
||||||
<p>ノードの追加、グループの操作、その他の便利なツールをクリックするだけで実行できるようになりました。</p>
|
|
||||||
<p>フローのタブバーには、フローの操作をより簡単にする独自のコンテキストメニューもあります。</p>`,
|
|
||||||
"fr": `<p>Le menu contextuel de l'éditeur a été étendu pour faire beaucoup plus d'actions intégrées disponibles.</p>
|
|
||||||
<p>Ajouter des noeuds, travailler avec des groupes et beaucoup d'autres outils utiles sont désormais à portée de clic.</p>
|
|
||||||
<p>La barre d'onglets de flux possède également son propre menu contextuel pour faciliter l'utilisation de vos flux.</p>`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: {
|
|
||||||
"en-US": "Hiding Flows",
|
|
||||||
"ja": "フローを非表示",
|
|
||||||
"fr": "Masquage de flux"
|
|
||||||
},
|
|
||||||
image: 'images/hiding-flows.png',
|
|
||||||
description: {
|
|
||||||
"en-US": `<p>Hiding flows is now done through the flow context menu.</p>
|
|
||||||
<p>The 'hide' button in previous releases has been removed from the tabs
|
|
||||||
as they were being clicked accidentally too often.</p>`,
|
|
||||||
"ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p>
|
|
||||||
<p>これまでのリリースでタブに存在していた「非表示」ボタンは、よく誤ってクリックされていたため、削除されました。</p>`,
|
|
||||||
"fr": `<p>Le masquage des flux s'effectue désormais via le menu contextuel du flux.</p>
|
|
||||||
<p>Le bouton "Masquer" des versions précédentes a été supprimé des onglets
|
|
||||||
car il était cliqué accidentellement trop souvent.</p>`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: {
|
|
||||||
"en-US": "Locking Flows",
|
|
||||||
"ja": "フローを固定",
|
|
||||||
"fr": "Verrouillage de flux"
|
|
||||||
},
|
|
||||||
image: 'images/locking-flows.png',
|
|
||||||
description: {
|
|
||||||
"en-US": `<p>Flows can now be locked to prevent accidental changes being made.</p>
|
|
||||||
<p>When locked you cannot modify the nodes in any way.</p>
|
|
||||||
<p>The flow context menu provides the options to lock and unlock flows,
|
|
||||||
as well as in the Info sidebar explorer.</p>`,
|
|
||||||
"ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p>
|
|
||||||
<p>固定されている時は、ノードを修正することはできません。</p>
|
|
||||||
<p>フローのコンテキストメニューと、情報サイドバーのエクスプローラには、フローの固定や解除をするためのオプションが用意されています。</p>`,
|
|
||||||
"fr": `<p>Les flux peuvent désormais être verrouillés pour éviter toute modification accidentelle.</p>
|
|
||||||
<p>Lorsqu'il est verrouillé, vous ne pouvez en aucun cas modifier les noeuds.</p>
|
|
||||||
<p>Le menu contextuel du flux fournit les options pour verrouiller et déverrouiller les flux,
|
|
||||||
ainsi que dans l'explorateur de la barre latérale d'informations.</p>`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: {
|
|
||||||
"en-US": "Adding Images to node/flow descriptions",
|
|
||||||
"ja": "ノードやフローの説明へ画像を追加",
|
|
||||||
"fr": "Ajout d'images aux descriptions de noeud/flux"
|
|
||||||
},
|
|
||||||
// image: 'images/debug-path-tooltip.png',
|
|
||||||
description: {
|
|
||||||
"en-US": `<p>You can now add images to a node's or flows's description.</p>
|
|
||||||
<p>Simply drag the image into the text editor and it will get added inline.</p>
|
|
||||||
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
|
|
||||||
"ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p>
|
|
||||||
<p>画像をテキストエディタにドラッグするだけで、行内に埋め込まれます。</p>
|
|
||||||
<p>情報サイドバーの説明を開くと、その画像が表示されます。</p>`,
|
|
||||||
"fr": `<p>Vous pouvez désormais ajouter des images à la description d'un noeud ou d'un flux.</p>
|
|
||||||
<p>Faites simplement glisser l'image dans l'éditeur de texte et elle sera ajoutée en ligne.</p>
|
|
||||||
<p>Lorsque la description s'affiche dans la barre latérale d'informations, l'image s'affiche.</p>`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: {
|
|
||||||
"en-US": "Adding Mermaid Diagrams",
|
|
||||||
"ja": "Mermaid図を追加",
|
|
||||||
"fr": "Ajout de diagrammes Mermaid"
|
|
||||||
},
|
|
||||||
image: 'images/mermaid.png',
|
|
||||||
description: {
|
|
||||||
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
|
|
||||||
<p>This gives you much richer options for documenting your flows.</p>`,
|
|
||||||
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
|
|
||||||
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`,
|
|
||||||
"fr": `<p>Vous pouvez également ajouter des diagrammes <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> directement dans vos descriptions de noeud ou de flux.</p>
|
|
||||||
<p>Cela vous offre des options beaucoup plus riches pour documenter vos flux.</p>`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: {
|
|
||||||
"en-US": "Managing Global Environment Variables",
|
|
||||||
"ja": "グローバル環境変数の管理",
|
|
||||||
"fr": "Gestion des variables d'environnement globales"
|
|
||||||
},
|
|
||||||
image: 'images/global-env-vars.png',
|
|
||||||
description: {
|
|
||||||
"en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
|
|
||||||
'Global Environment Variables' section of User Settings.</p>`,
|
|
||||||
"ja": `<p>ユーザ設定に新しく追加された「大域環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`,
|
|
||||||
"fr": `<p>Vous pouvez définir des variables d'environnement qui s'appliquent à tous les noeuds et flux dans la nouvelle
|
|
||||||
section "Global Environment Variables" des paramètres utilisateur.</p>`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
"en-US": "Node Updates",
|
"en-US": "Node Updates",
|
||||||
@ -221,10 +203,28 @@ export default {
|
|||||||
// image: "images/",
|
// image: "images/",
|
||||||
description: {
|
description: {
|
||||||
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
||||||
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
|
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
|
||||||
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`,
|
<ul>
|
||||||
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et
|
<li>A fully RFC4180 compliant CSV mode</li>
|
||||||
petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>`
|
<li>Customisable headers on the WebSocket node</li>
|
||||||
|
<li>Split node now can operate on any message property</li>
|
||||||
|
<li>and lots more...</li>
|
||||||
|
</ul>`,
|
||||||
|
"ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
|
||||||
|
<ul>
|
||||||
|
<li>RFC4180に完全に準拠したCSVモード</li>
|
||||||
|
<li>WebSocketノードのカスタマイズ可能なヘッダ</li>
|
||||||
|
<li>Splitノードは、メッセージプロパティで操作できるようになりました</li>
|
||||||
|
<li>他にも沢山あります...</li>
|
||||||
|
</ul>`,
|
||||||
|
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
|
||||||
|
Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
|
||||||
|
<ul>
|
||||||
|
<li>Un mode CSV entièrement conforme à la norme RFC4180</li>
|
||||||
|
<li>En-têtes personnalisables pour le noeud WebSocket</li>
|
||||||
|
<li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
|
||||||
|
<li>Et bien plus encore...</li>
|
||||||
|
</ul>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
|
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
|
||||||
|
|
||||||
interface NodeMessage {
|
interface NodeMessage {
|
||||||
topic?: string;
|
topic?: string;
|
||||||
payload?: any;
|
payload?: any;
|
||||||
_msgid?: string;
|
/** `_msgid` is generated internally. It not something you typically need to set or modify. */ _msgid?: string;
|
||||||
[other: string]: any; //permit other properties
|
[other: string]: any; //permit other properties
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,15 +19,15 @@ declare const promisify:typeof import('util').promisify
|
|||||||
/**
|
/**
|
||||||
* @typedef NodeStatus
|
* @typedef NodeStatus
|
||||||
* @type {object}
|
* @type {object}
|
||||||
* @property {string} [fill] The fill property can be: red, green, yellow, blue or grey.
|
* @property {'red'|'green'|'yellow'|'blue'|'grey'|string} [fill] - The fill property can be: red, green, yellow, blue or grey.
|
||||||
* @property {string} [shape] The shape property can be: ring or dot.
|
* @property {'ring'|'dot'|string} [shape] The shape property can be: ring or dot.
|
||||||
* @property {string} [text] The text to display
|
* @property {string|boolean|number} [text] The text to display
|
||||||
*/
|
*/
|
||||||
interface NodeStatus {
|
interface NodeStatus {
|
||||||
/** The fill property can be: red, green, yellow, blue or grey */
|
/** The fill property can be: red, green, yellow, blue or grey */
|
||||||
fill?: string,
|
fill?: 'red'|'green'|'yellow'|'blue'|'grey'|string,
|
||||||
/** The shape property can be: ring or dot */
|
/** The shape property can be: ring or dot */
|
||||||
shape?: string,
|
shape?: 'ring'|'dot'|string,
|
||||||
/** The text to display */
|
/** The text to display */
|
||||||
text?: string|boolean|number
|
text?: string|boolean|number
|
||||||
}
|
}
|
||||||
@ -37,25 +37,24 @@ declare class node {
|
|||||||
* Send 1 or more messages asynchronously
|
* Send 1 or more messages asynchronously
|
||||||
* @param {object | object[]} msg The msg object
|
* @param {object | object[]} msg The msg object
|
||||||
* @param {Boolean} [clone=true] Flag to indicate the `msg` should be cloned. Default = `true`
|
* @param {Boolean} [clone=true] Flag to indicate the `msg` should be cloned. Default = `true`
|
||||||
* @see node-red documentation [writing-functions: sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously)
|
* @see Node-RED documentation [writing-functions: sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously)
|
||||||
*/
|
*/
|
||||||
static send(msg:object|object[], clone?:Boolean): void;
|
static send(msg:NodeMessage|NodeMessage[], clone?:Boolean): void;
|
||||||
/** Inform runtime this instance has completed its operation */
|
/** Inform runtime this instance has completed its operation */
|
||||||
static done();
|
static done();
|
||||||
/** Send an error to the console and debug side bar. Include `msg` in the 2nd parameter to trigger the catch node. */
|
/** Send an error to the console and debug side bar. Include `msg` in the 2nd parameter to trigger the catch node. */
|
||||||
static error(err:string|Error, msg?:object);
|
static error(err:string|Error, msg?:NodeMessage);
|
||||||
/** Log a warn message to the console and debug sidebar */
|
/** Log a warn message to the console and debug sidebar */
|
||||||
static warn(warning:string|object);
|
static warn(warning:string|object);
|
||||||
/** Log an info message to the console (not sent to sidebar)' */
|
/** Log an info message to the console (not sent to sidebar)' */
|
||||||
static log(info:string|object);
|
static log(info:string|object);
|
||||||
/** Sets the status icon and text underneath the node.
|
/** Sets the status icon and text underneath the node.
|
||||||
* @param {NodeStatus} status - The status object `{fill, shape, text}`
|
* @param {NodeStatus} status - The status object `{fill, shape, text}`
|
||||||
* @see node-red documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
|
* @see Node-RED documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
|
||||||
*/
|
*/
|
||||||
static status(status:NodeStatus);
|
static status(status:NodeStatus);
|
||||||
/** Sets the status text underneath the node.
|
/** Sets the status text underneath the node.
|
||||||
* @param {string} status - The status to display
|
* @see Node-RED documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
|
||||||
* @see node-red documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
|
|
||||||
*/
|
*/
|
||||||
static status(status:string|boolean|number);
|
static status(status:string|boolean|number);
|
||||||
/** the id of this node */
|
/** the id of this node */
|
||||||
@ -264,9 +263,12 @@ declare class global {
|
|||||||
/** Get an array of the keys in the context store */
|
/** Get an array of the keys in the context store */
|
||||||
static keys(store: string, callback: Function);
|
static keys(store: string, callback: Function);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (string & {}) is a workaround for offering string type completion without enforcing it. See https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939
|
||||||
|
type NR_ENV_NAME_STRING = 'NR_NODE_ID'|'NR_NODE_NAME'|'NR_NODE_PATH'|'NR_GROUP_ID'|'NR_GROUP_NAME'|'NR_FLOW_ID'|'NR_FLOW_NAME'|'NR_SUBFLOW_ID'|'NR_SUBFLOW_NAME'|'NR_SUBFLOW_PATH' | (string & {})
|
||||||
declare class env {
|
declare class env {
|
||||||
/**
|
/**
|
||||||
* Get an environment variable value
|
* Get an environment variable value defined in the OS, or in the global/flow/subflow/group environment variables.
|
||||||
*
|
*
|
||||||
* Predefined node-red variables...
|
* Predefined node-red variables...
|
||||||
* * `NR_NODE_ID` - the ID of the node
|
* * `NR_NODE_ID` - the ID of the node
|
||||||
@ -276,9 +278,16 @@ declare class env {
|
|||||||
* * `NR_GROUP_NAME` - the Name of the containing group
|
* * `NR_GROUP_NAME` - the Name of the containing group
|
||||||
* * `NR_FLOW_ID` - the ID of the flow the node is on
|
* * `NR_FLOW_ID` - the ID of the flow the node is on
|
||||||
* * `NR_FLOW_NAME` - the Name of the flow the node is on
|
* * `NR_FLOW_NAME` - the Name of the flow the node is on
|
||||||
* @param name Name of the environment variable to get
|
* * `NR_SUBFLOW_ID` - the ID of the subflow the node is in
|
||||||
|
* * `NR_SUBFLOW_NAME` - the Name of the subflow the node is in
|
||||||
|
* * `NR_SUBFLOW_PATH` - the Path of the subflow the node is in
|
||||||
|
* @param name - The name of the environment variable
|
||||||
* @example
|
* @example
|
||||||
* ```const flowName = env.get("NR_FLOW_NAME");```
|
* ```const flowName = env.get("NR_FLOW_NAME") // get the name of the flow```
|
||||||
|
* @example
|
||||||
|
* ```const systemHomeDir = env.get("HOME") // get the user's home directory```
|
||||||
|
* @example
|
||||||
|
* ```const systemHomeDir = env.get("LABEL1") // get the value of a global/flow/subflow/group defined variable named "LABEL1"```
|
||||||
*/
|
*/
|
||||||
static get(name:string) :any;
|
static get(name:NR_ENV_NAME_STRING) :any;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
|
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `assert` module provides a set of assertion functions for verifying
|
* The `assert` module provides a set of assertion functions for verifying
|
||||||
* invariants.
|
* invariants.
|
||||||
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/assert.js)
|
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/assert.js)
|
||||||
*/
|
*/
|
||||||
declare module 'assert' {
|
declare module 'assert' {
|
||||||
/**
|
/**
|
||||||
@ -290,8 +290,8 @@ declare module 'assert' {
|
|||||||
* > Stability: 3 - Legacy: Use {@link strictEqual} instead.
|
* > Stability: 3 - Legacy: Use {@link strictEqual} instead.
|
||||||
*
|
*
|
||||||
* Tests shallow, coercive equality between the `actual` and `expected` parameters
|
* Tests shallow, coercive equality between the `actual` and `expected` parameters
|
||||||
* using the [Abstract Equality Comparison](https://tc39.github.io/ecma262/#sec-abstract-equality-comparison) ( `==` ). `NaN` is special handled
|
* using the [`==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality). `NaN` is specially handled
|
||||||
* and treated as being identical in case both sides are `NaN`.
|
* and treated as being identical if both sides are `NaN`.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import assert from 'assert';
|
* import assert from 'assert';
|
||||||
@ -323,9 +323,8 @@ declare module 'assert' {
|
|||||||
*
|
*
|
||||||
* > Stability: 3 - Legacy: Use {@link notStrictEqual} instead.
|
* > Stability: 3 - Legacy: Use {@link notStrictEqual} instead.
|
||||||
*
|
*
|
||||||
* Tests shallow, coercive inequality with the [Abstract Equality Comparison](https://tc39.github.io/ecma262/#sec-abstract-equality-comparison)(`!=` ). `NaN` is special handled and treated as
|
* Tests shallow, coercive inequality with the [`!=` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality). `NaN` is
|
||||||
* being identical in case both
|
* specially handled and treated as being identical if both sides are `NaN`.
|
||||||
* sides are `NaN`.
|
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import assert from 'assert';
|
* import assert from 'assert';
|
||||||
@ -415,7 +414,7 @@ declare module 'assert' {
|
|||||||
function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void;
|
function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void;
|
||||||
/**
|
/**
|
||||||
* Tests strict equality between the `actual` and `expected` parameters as
|
* Tests strict equality between the `actual` and `expected` parameters as
|
||||||
* determined by the [SameValue Comparison](https://tc39.github.io/ecma262/#sec-samevalue).
|
* determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import assert from 'assert/strict';
|
* import assert from 'assert/strict';
|
||||||
@ -453,7 +452,7 @@ declare module 'assert' {
|
|||||||
function strictEqual<T>(actual: unknown, expected: T, message?: string | Error): asserts actual is T;
|
function strictEqual<T>(actual: unknown, expected: T, message?: string | Error): asserts actual is T;
|
||||||
/**
|
/**
|
||||||
* Tests strict inequality between the `actual` and `expected` parameters as
|
* Tests strict inequality between the `actual` and `expected` parameters as
|
||||||
* determined by the [SameValue Comparison](https://tc39.github.io/ecma262/#sec-samevalue).
|
* determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import assert from 'assert/strict';
|
* import assert from 'assert/strict';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
|
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
|
||||||
|
|
||||||
declare module 'assert/strict' {
|
declare module 'assert/strict' {
|
||||||
import { strict } from 'node:assert';
|
import { strict } from 'node:assert';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
|
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `async_hooks` module provides an API to track asynchronous resources. It
|
* The `async_hooks` module provides an API to track asynchronous resources. It
|
||||||
@ -9,7 +9,7 @@
|
|||||||
* import async_hooks from 'async_hooks';
|
* import async_hooks from 'async_hooks';
|
||||||
* ```
|
* ```
|
||||||
* @experimental
|
* @experimental
|
||||||
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/async_hooks.js)
|
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/async_hooks.js)
|
||||||
*/
|
*/
|
||||||
declare module 'async_hooks' {
|
declare module 'async_hooks' {
|
||||||
/**
|
/**
|
||||||
@ -367,7 +367,7 @@ declare module 'async_hooks' {
|
|||||||
*
|
*
|
||||||
* Each instance of `AsyncLocalStorage` maintains an independent storage context.
|
* Each instance of `AsyncLocalStorage` maintains an independent storage context.
|
||||||
* Multiple instances can safely exist simultaneously without risk of interfering
|
* Multiple instances can safely exist simultaneously without risk of interfering
|
||||||
* with each other data.
|
* with each other's data.
|
||||||
* @since v13.10.0, v12.17.0
|
* @since v13.10.0, v12.17.0
|
||||||
*/
|
*/
|
||||||
class AsyncLocalStorage<T> {
|
class AsyncLocalStorage<T> {
|
||||||
@ -398,8 +398,9 @@ declare module 'async_hooks' {
|
|||||||
getStore(): T | undefined;
|
getStore(): T | undefined;
|
||||||
/**
|
/**
|
||||||
* Runs a function synchronously within a context and returns its
|
* Runs a function synchronously within a context and returns its
|
||||||
* return value. The store is not accessible outside of the callback function or
|
* return value. The store is not accessible outside of the callback function.
|
||||||
* the asynchronous operations created within the callback.
|
* The store is accessible to any asynchronous operations created within the
|
||||||
|
* callback.
|
||||||
*
|
*
|
||||||
* The optional `args` are passed to the callback function.
|
* The optional `args` are passed to the callback function.
|
||||||
*
|
*
|
||||||
@ -413,6 +414,9 @@ declare module 'async_hooks' {
|
|||||||
* try {
|
* try {
|
||||||
* asyncLocalStorage.run(store, () => {
|
* asyncLocalStorage.run(store, () => {
|
||||||
* asyncLocalStorage.getStore(); // Returns the store object
|
* asyncLocalStorage.getStore(); // Returns the store object
|
||||||
|
* setTimeout(() => {
|
||||||
|
* asyncLocalStorage.getStore(); // Returns the store object
|
||||||
|
* }, 200);
|
||||||
* throw new Error();
|
* throw new Error();
|
||||||
* });
|
* });
|
||||||
* } catch (e) {
|
* } catch (e) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
|
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Buffer` objects are used to represent a fixed-length sequence of bytes. Many
|
* `Buffer` objects are used to represent a fixed-length sequence of bytes. Many
|
||||||
@ -44,7 +44,7 @@
|
|||||||
* // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74].
|
* // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74].
|
||||||
* const buf7 = Buffer.from('tést', 'latin1');
|
* const buf7 = Buffer.from('tést', 'latin1');
|
||||||
* ```
|
* ```
|
||||||
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/buffer.js)
|
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/buffer.js)
|
||||||
*/
|
*/
|
||||||
declare module 'buffer' {
|
declare module 'buffer' {
|
||||||
import { BinaryLike } from 'node:crypto';
|
import { BinaryLike } from 'node:crypto';
|
||||||
@ -117,18 +117,17 @@ declare module 'buffer' {
|
|||||||
/**
|
/**
|
||||||
* A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) encapsulates immutable, raw data that can be safely shared across
|
* A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) encapsulates immutable, raw data that can be safely shared across
|
||||||
* multiple worker threads.
|
* multiple worker threads.
|
||||||
* @since v15.7.0
|
* @since v15.7.0, v14.18.0
|
||||||
* @experimental
|
|
||||||
*/
|
*/
|
||||||
export class Blob {
|
export class Blob {
|
||||||
/**
|
/**
|
||||||
* The total size of the `Blob` in bytes.
|
* The total size of the `Blob` in bytes.
|
||||||
* @since v15.7.0
|
* @since v15.7.0, v14.18.0
|
||||||
*/
|
*/
|
||||||
readonly size: number;
|
readonly size: number;
|
||||||
/**
|
/**
|
||||||
* The content-type of the `Blob`.
|
* The content-type of the `Blob`.
|
||||||
* @since v15.7.0
|
* @since v15.7.0, v14.18.0
|
||||||
*/
|
*/
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
/**
|
/**
|
||||||
@ -143,13 +142,13 @@ declare module 'buffer' {
|
|||||||
/**
|
/**
|
||||||
* Returns a promise that fulfills with an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) containing a copy of
|
* Returns a promise that fulfills with an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) containing a copy of
|
||||||
* the `Blob` data.
|
* the `Blob` data.
|
||||||
* @since v15.7.0
|
* @since v15.7.0, v14.18.0
|
||||||
*/
|
*/
|
||||||
arrayBuffer(): Promise<ArrayBuffer>;
|
arrayBuffer(): Promise<ArrayBuffer>;
|
||||||
/**
|
/**
|
||||||
* Creates and returns a new `Blob` containing a subset of this `Blob` objects
|
* Creates and returns a new `Blob` containing a subset of this `Blob` objects
|
||||||
* data. The original `Blob` is not altered.
|
* data. The original `Blob` is not altered.
|
||||||
* @since v15.7.0
|
* @since v15.7.0, v14.18.0
|
||||||
* @param start The starting index.
|
* @param start The starting index.
|
||||||
* @param end The ending index.
|
* @param end The ending index.
|
||||||
* @param type The content-type for the new `Blob`
|
* @param type The content-type for the new `Blob`
|
||||||
@ -158,7 +157,7 @@ declare module 'buffer' {
|
|||||||
/**
|
/**
|
||||||
* Returns a promise that fulfills with the contents of the `Blob` decoded as a
|
* Returns a promise that fulfills with the contents of the `Blob` decoded as a
|
||||||
* UTF-8 string.
|
* UTF-8 string.
|
||||||
* @since v15.7.0
|
* @since v15.7.0, v14.18.0
|
||||||
*/
|
*/
|
||||||
text(): Promise<string>;
|
text(): Promise<string>;
|
||||||
/**
|
/**
|
||||||
@ -169,6 +168,12 @@ declare module 'buffer' {
|
|||||||
}
|
}
|
||||||
export import atob = globalThis.atob;
|
export import atob = globalThis.atob;
|
||||||
export import btoa = globalThis.btoa;
|
export import btoa = globalThis.btoa;
|
||||||
|
|
||||||
|
import { Blob as NodeBlob } from 'buffer';
|
||||||
|
// This conditional type will be the existing global Blob in a browser, or
|
||||||
|
// the copy below in a Node environment.
|
||||||
|
type __Blob = typeof globalThis extends { onmessage: any, Blob: infer T }
|
||||||
|
? T : NodeBlob;
|
||||||
global {
|
global {
|
||||||
// Buffer class
|
// Buffer class
|
||||||
type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
|
type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
|
||||||
@ -394,7 +399,7 @@ declare module 'buffer' {
|
|||||||
* @since v0.11.13
|
* @since v0.11.13
|
||||||
* @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details.
|
* @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details.
|
||||||
*/
|
*/
|
||||||
compare(buf1: Uint8Array, buf2: Uint8Array): number;
|
compare(buf1: Uint8Array, buf2: Uint8Array): -1 | 0 | 1;
|
||||||
/**
|
/**
|
||||||
* Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled.
|
* Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled.
|
||||||
*
|
*
|
||||||
@ -447,7 +452,7 @@ declare module 'buffer' {
|
|||||||
* Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown.
|
* Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown.
|
||||||
*
|
*
|
||||||
* The underlying memory for `Buffer` instances created in this way is _not_
|
* The underlying memory for `Buffer` instances created in this way is _not_
|
||||||
* _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes.
|
* _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import { Buffer } from 'buffer';
|
* import { Buffer } from 'buffer';
|
||||||
@ -485,7 +490,7 @@ declare module 'buffer' {
|
|||||||
* if `size` is 0.
|
* if `size` is 0.
|
||||||
*
|
*
|
||||||
* The underlying memory for `Buffer` instances created in this way is _not_
|
* The underlying memory for `Buffer` instances created in this way is _not_
|
||||||
* _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `buf.fill(0)` to initialize
|
* _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `buf.fill(0)` to initialize
|
||||||
* such `Buffer` instances with zeroes.
|
* such `Buffer` instances with zeroes.
|
||||||
*
|
*
|
||||||
* When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
|
* When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
|
||||||
@ -708,7 +713,7 @@ declare module 'buffer' {
|
|||||||
* @param [sourceStart=0] The offset within `buf` at which to begin comparison.
|
* @param [sourceStart=0] The offset within `buf` at which to begin comparison.
|
||||||
* @param [sourceEnd=buf.length] The offset within `buf` at which to end comparison (not inclusive).
|
* @param [sourceEnd=buf.length] The offset within `buf` at which to end comparison (not inclusive).
|
||||||
*/
|
*/
|
||||||
compare(target: Uint8Array, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number;
|
compare(target: Uint8Array, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1;
|
||||||
/**
|
/**
|
||||||
* Copies data from a region of `buf` to a region in `target`, even if the `target`memory region overlaps with `buf`.
|
* Copies data from a region of `buf` to a region in `target`, even if the `target`memory region overlaps with `buf`.
|
||||||
*
|
*
|
||||||
@ -767,8 +772,6 @@ declare module 'buffer' {
|
|||||||
* Returns a new `Buffer` that references the same memory as the original, but
|
* Returns a new `Buffer` that references the same memory as the original, but
|
||||||
* offset and cropped by the `start` and `end` indices.
|
* offset and cropped by the `start` and `end` indices.
|
||||||
*
|
*
|
||||||
* This is the same behavior as `buf.subarray()`.
|
|
||||||
*
|
|
||||||
* This method is not compatible with the `Uint8Array.prototype.slice()`,
|
* This method is not compatible with the `Uint8Array.prototype.slice()`,
|
||||||
* which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`.
|
* which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`.
|
||||||
*
|
*
|
||||||
@ -784,8 +787,17 @@ declare module 'buffer' {
|
|||||||
*
|
*
|
||||||
* console.log(buf.toString());
|
* console.log(buf.toString());
|
||||||
* // Prints: buffer
|
* // Prints: buffer
|
||||||
|
*
|
||||||
|
* // With buf.slice(), the original buffer is modified.
|
||||||
|
* const notReallyCopiedBuf = buf.slice();
|
||||||
|
* notReallyCopiedBuf[0]++;
|
||||||
|
* console.log(notReallyCopiedBuf.toString());
|
||||||
|
* // Prints: cuffer
|
||||||
|
* console.log(buf.toString());
|
||||||
|
* // Also prints: cuffer (!)
|
||||||
* ```
|
* ```
|
||||||
* @since v0.3.0
|
* @since v0.3.0
|
||||||
|
* @deprecated Use `subarray` instead.
|
||||||
* @param [start=0] Where the new `Buffer` will start.
|
* @param [start=0] Where the new `Buffer` will start.
|
||||||
* @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
|
* @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
|
||||||
*/
|
*/
|
||||||
@ -1952,7 +1964,7 @@ declare module 'buffer' {
|
|||||||
*
|
*
|
||||||
* * a string, `value` is interpreted according to the character encoding in`encoding`.
|
* * a string, `value` is interpreted according to the character encoding in`encoding`.
|
||||||
* * a `Buffer` or [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), `value` will be used in its entirety.
|
* * a `Buffer` or [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), `value` will be used in its entirety.
|
||||||
* To compare a partial `Buffer`, use `buf.slice()`.
|
* To compare a partial `Buffer`, use `buf.subarray`.
|
||||||
* * a number, `value` will be interpreted as an unsigned 8-bit integer
|
* * a number, `value` will be interpreted as an unsigned 8-bit integer
|
||||||
* value between `0` and `255`.
|
* value between `0` and `255`.
|
||||||
*
|
*
|
||||||
@ -2208,7 +2220,7 @@ declare module 'buffer' {
|
|||||||
* **binary data and predate the introduction of typed arrays in JavaScript.**
|
* **binary data and predate the introduction of typed arrays in JavaScript.**
|
||||||
* **For code running using Node.js APIs, converting between base64-encoded strings**
|
* **For code running using Node.js APIs, converting between base64-encoded strings**
|
||||||
* **and binary data should be performed using `Buffer.from(str, 'base64')` and`buf.toString('base64')`.**
|
* **and binary data should be performed using `Buffer.from(str, 'base64')` and`buf.toString('base64')`.**
|
||||||
* @since v15.13.0
|
* @since v15.13.0, v14.17.0
|
||||||
* @deprecated Use `Buffer.from(data, 'base64')` instead.
|
* @deprecated Use `Buffer.from(data, 'base64')` instead.
|
||||||
* @param data The Base64-encoded input string.
|
* @param data The Base64-encoded input string.
|
||||||
*/
|
*/
|
||||||
@ -2224,11 +2236,24 @@ declare module 'buffer' {
|
|||||||
* **binary data and predate the introduction of typed arrays in JavaScript.**
|
* **binary data and predate the introduction of typed arrays in JavaScript.**
|
||||||
* **For code running using Node.js APIs, converting between base64-encoded strings**
|
* **For code running using Node.js APIs, converting between base64-encoded strings**
|
||||||
* **and binary data should be performed using `Buffer.from(str, 'base64')` and`buf.toString('base64')`.**
|
* **and binary data should be performed using `Buffer.from(str, 'base64')` and`buf.toString('base64')`.**
|
||||||
* @since v15.13.0
|
* @since v15.13.0, v14.17.0
|
||||||
* @deprecated Use `buf.toString('base64')` instead.
|
* @deprecated Use `buf.toString('base64')` instead.
|
||||||
* @param data An ASCII (Latin1) string.
|
* @param data An ASCII (Latin1) string.
|
||||||
*/
|
*/
|
||||||
function btoa(data: string): string;
|
function btoa(data: string): string;
|
||||||
|
|
||||||
|
interface Blob extends __Blob {}
|
||||||
|
/**
|
||||||
|
* `Blob` class is a global reference for `require('node:buffer').Blob`
|
||||||
|
* https://nodejs.org/api/buffer.html#class-blob
|
||||||
|
* @since v18.0.0
|
||||||
|
*/
|
||||||
|
var Blob: typeof globalThis extends {
|
||||||
|
onmessage: any;
|
||||||
|
Blob: infer T;
|
||||||
|
}
|
||||||
|
? T
|
||||||
|
: typeof NodeBlob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
declare module 'node:buffer' {
|
declare module 'node:buffer' {
|
||||||
|