From 39602ff5f26c115c0dbed432eb583fc8ef6d0333 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 3 May 2022 13:58:02 +0100 Subject: [PATCH 001/195] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44dffa5be..6333fdb53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ #### 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 From f04d954882734c8cf9f068113b2f39e2e696ca30 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 3 May 2022 14:25:27 +0100 Subject: [PATCH 002/195] Drop node 12 from test runs --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e86c2afe3..8f3c8a6ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: path: 'node-red.github.io' - uses: actions/setup-node@v1 with: - node-version: '12' + node-version: '16' - run: node ./node-red/.github/scripts/update-node-red-docker.js - name: Create Docker Pull Request uses: peter-evans/create-pull-request@v2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dde581a01..0db909da6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12, 14, 16] + node-version: [14, 16] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} From 38410397285a787f666689f908ab47ae3a503384 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 3 May 2022 14:29:25 +0100 Subject: [PATCH 003/195] Update gen publish script for 3.x --- scripts/generate-publish-script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-publish-script.js b/scripts/generate-publish-script.js index 0c64cb832..82d98ea3c 100644 --- a/scripts/generate-publish-script.js +++ b/scripts/generate-publish-script.js @@ -4,7 +4,7 @@ const path = require("path"); const fs = require("fs-extra"); const should = require("should"); -const LATEST = "2"; +const LATEST = "3"; function generateScript() { return new Promise((resolve, reject) => { From 58085e39d153e57f0c075eaf81faeedb16d0bd25 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 4 May 2022 11:39:34 +0900 Subject: [PATCH 004/195] Add Japanese translations for welcome tour in 3.0-beta.1 --- .../editor-client/src/tours/welcome.js | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index e3f332da1..219aaec7b 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -5,17 +5,17 @@ export default { titleIcon: "fa fa-map-o", title: { "en-US": "Welcome to Node-RED 3.0 Beta 1!", - "ja": "Node-RED 3.0 Beta 1へようこそ!" + "ja": "Node-RED 3.0 ベータ1へようこそ!" }, description: { "en-US": "

This is the first Beta release of Node-RED 3.0. It contains just about everything we have planned for the final release.

Let's take a moment to discover the new features in this release.

", - "ja": "本リリースの新機能を見つけてみましょう。" + "ja": "

これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

本リリースの新機能を見つけてみましょう。

" } }, { title: { "en-US": "Wire Junctions", - // "ja": "" + "ja": "分岐点をワイヤーに追加" }, image: 'images/junction-slice.gif', description: { @@ -23,26 +23,28 @@ export default { add junction nodes that give you more control.

Junctions can be added to wires by holding the Shift key, then click and drag with the right-hand mouse button across the wires.

`, - // "ja": "" + "ja": `

フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

+

シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

` }, }, { title: { "en-US": "Wire Junctions", - // "ja": "" + "ja": "分岐点をワイヤーに追加" }, image: 'images/junction-quick-add.png', description: { "en-US": `

Junctions can also be added using the quick-add dialog.

The dialog is opened by holding the Ctrl (or Cmd) key when clicking in the workspace.

`, - // "ja": "" + "ja": `

クイック追加ダイアログを用いて、分岐点を追加することもできます。

+

本ダイアログを開くには、Ctrl(またはCmd)キーを押しながら、ワークスペース上でクリックします。

` }, }, { title: { "en-US": "Debug Path Tooltip", - // "ja": "" + "ja": "デバッグパスのツールチップ" }, image: 'images/debug-path-tooltip.png', description: { @@ -53,20 +55,22 @@ export default { the message.

Clicking on any item in the list will reveal it in the workspace.

`, - // "ja": "" + "ja": `

デバックサイドバー内のノード名の上にマウスカーソルを乗せると、新たにツールチップが表示され、ノードの場所が分かるようになっています。

+

これは、サブフローを用いる時に役立つ機能であり、メッセージがどのノードから出力されたかを正確に特定することが遥かに簡単になります。

+

本リスト内の要素をクリックすると、ワークスペース内にその要素が表示されます。

` }, }, { title: { "en-US": "Continuous Search", - // "ja": "" + "ja": "連続した検索" }, image: 'images/continuous-search.png', description: { "en-US": `

When searching for things in the editor, a new toolbar in the workspace provides options to quickly jump between the search results.

`, - // "ja": "" + "ja": `

ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。

` }, }, { @@ -81,13 +85,17 @@ export default {
  • Split Wire With Link Nodes
  • Actions can be accessed from the Action List in the main menu.

    `, - // "ja": `` + "ja": `

    ワイヤーを、接続されたLinkノードのペアに置き換える動作が新たに追加されました:

    +
      +
    • ワイヤーをlinkノードで分割
    • +
    +

    本アクションは、メインメニュー内の動作一覧から呼び出せます。

    `, }, }, { title: { "en-US": "Default node names", - // "ja": "" + "ja": "標準ノードの名前" }, // image: "images/", description: { @@ -100,7 +108,12 @@ export default {
  • Generate Node Names
  • Actions can be accessed from the Action List in the main menu.

    `, - // "ja": `` + "ja": `

    一部のノードは、ワークスペース上に新インスタンスとして追加した際に、一意の名前を付けるよう変更されました。この変更は、DebugFunctionLinkノードに適用されています。

    +

    選択したノードに対して、標準の名前を生成する動作も新たに追加されました:

    +
      +
    • ノード名を生成
    • +

    本アクションは、メインメニュー内の動作一覧から呼び出せます。

    + ` } }, { @@ -115,7 +128,11 @@ export default {
  • The Link Call node can use a message property to dynamically target the link it should call
  • The HTTP Request node can be preconfigured with HTTP headers
  • `, - // "ja": `` + "ja": `
      +
    • Debugノードは、受信したメッセージの数をカウントするよう設定できるようになりました。
    • +
    • Link Callノードは、メッセージのプロパティによって、呼び出し対象のlinkを動的に指定できるようになりました。
    • +
    • HTTP Requestノードは、HTTPヘッダを事前設定できるようになりました。
    • +
    ` } } ] From 7955a17a17e05a643dc4013b9c224e5070f20ac8 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Wed, 4 May 2022 06:37:52 -0400 Subject: [PATCH 005/195] Fix typedInput label not visible --- .../editor-client/src/js/ui/common/typedInput.js | 5 +---- .../editor-client/src/sass/ui/common/typedInput.scss | 10 ++++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index aa05ad695..549479d85 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -1026,10 +1026,7 @@ $(opt.icon).prependTo(this.selectLabel); } else if (opt.icon.indexOf("/") !== -1) { - image = new Image(); - image.name = opt.icon; - image.src = mapDeprecatedIcon(opt.icon); - $('',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); + $('',{class:"red-ui-typedInput-icon", style:"mask-image: url("+opt.icon+"); -webkit-mask-image: url("+opt.icon+"); margin-right: 4px;height: 18px;width:13px"}).prependTo(this.selectLabel); } else { $('',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index da53e1e2d..76345e980 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -153,6 +153,16 @@ button.red-ui-typedInput-option-trigger img { max-width: none; } + .red-ui-typedInput-icon:not(.fa) { + display: inline-block; + -webkit-mask-size: cover; + mask-size: cover; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + background-color: $primary-text-color; + } } &:not(.disabled):hover { From bd19c203e122d8de82932f85b1ff2a1fb426c307 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 4 May 2022 15:08:52 +0100 Subject: [PATCH 006/195] Prevent error when `uses` search term is used fixes #3578 --- .../node_modules/@node-red/editor-client/src/js/ui/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index e477fbc22..ec50c532e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -144,7 +144,7 @@ RED.search = (function() { var key = keys[i]; var kpos = keys[i].indexOf(val); if (kpos > -1) { - var ids = Object.keys(index[key]); + var ids = Object.keys(index[key]||{}); for (j=0;j Date: Wed, 4 May 2022 15:10:02 +0100 Subject: [PATCH 007/195] declare undeclared loop var --- .../node_modules/@node-red/editor-client/src/js/ui/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index ec50c532e..6b73a1c41 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -255,7 +255,7 @@ RED.search = (function() { } currentResults = search(value); if (currentResults.length > 0) { - for (i=0;i 25) { From bf8bfa582a4aa2a7ac4d1351b9867a7c4ce9a9eb Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 4 May 2022 15:16:35 +0100 Subject: [PATCH 008/195] fix error initialising flow tab editor fixes #3577 --- .../editor-client/src/js/ui/editors/panes/flowProperties.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js index 826814bab..2db4d0c85 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js @@ -19,7 +19,6 @@ this.tabflowEditor = RED.editor.createEditor({ id: 'node-input-info', mode: 'ace/mode/markdown', - stateId: options.stateId, value: "" }); From 50ae29a08cab052669269cfc634c8f79ddabb703 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 4 May 2022 23:29:40 +0900 Subject: [PATCH 009/195] Add Japanese translations for v3.0-beta.1 --- .../editor-client/locales/ja/editor.json | 41 +++++---- .../@node-red/nodes/locales/ja/messages.json | 89 ++++++++++--------- .../@node-red/runtime/locales/ja/runtime.json | 4 +- 3 files changed, 71 insertions(+), 63 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index d4a638137..9713e2bab 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -302,9 +302,9 @@ "successfulRestart": "フローの再起動が成功しました", "deployFailed": "デプロイが失敗しました: __message__", "unusedConfigNodes": "使われていない設定ノードがあります。", - "unusedConfigNodesButton":"未使用の構成ノードを検索", - "unknownNodesButton":"不明なノードを検索する", - "invalidNodesButton":"無効なノードを検索する", + "unusedConfigNodesButton": "未使用の構成ノードを検索", + "unknownNodesButton": "不明なノードを検索する", + "invalidNodesButton": "無効なノードを検索する", "errors": { "noResponse": "サーバの応答がありません" }, @@ -1159,6 +1159,9 @@ "start": "開始", "next": "次へ" }, + "diagnostics": { + "title": "システム情報" + }, "languages": { "de": "ドイツ語", "en-US": "英語", @@ -1168,6 +1171,22 @@ "zh-CN": "中国語(簡体)", "zh-TW": "中国語(繁体)" }, + "validator": { + "errors": { + "invalid-json": "JSONデータが不正: __error__", + "invalid-json-prop": "__prop__: JSONデータが不正: __error__", + "invalid-prop": "プロパティ式が不正", + "invalid-prop-prop": "__prop__: プロパティ式が不正", + "invalid-num": "数値が不正", + "invalid-num-prop": "__prop__: 数値が不正", + "invalid-regexp": "入力パターンが不正", + "invalid-regex-prop": "__prop__: 入力パターンが不正", + "missing-required-prop": "__prop__: プロパティが未設定", + "invalid-config": "__prop__: 設定ノードが不正", + "missing-config": "__prop__: 設定ノードが存在しません", + "validation-error": "__prop__: チェックエラー: __node__, __id__: __error__" + } + }, "action-list": { "toggle-show-tips": "ヒント表示切替", "show-about": "Node-REDの説明を表示", @@ -1305,21 +1324,5 @@ "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", "show-system-info": "システムインフォメーション" - }, - "validator": { - "errors": { - "invalid-json": "JSONデータが不正: __error__", - "invalid-json-prop": "__prop__: JSONデータが不正: __error__", - "invalid-prop": "プロパティ式が不正", - "invalid-prop-prop": "__prop__: プロパティ式が不正", - "invalid-num": "数値が不正", - "invalid-num-prop": "__prop__: 数値が不正", - "invalid-regexp": "入力パターンが不正", - "invalid-regex-prop": "__prop__: 入力パターンが不正", - "missing-required-prop": "__prop__: プロパティが未設定", - "invalid-config": "__prop__: 設定ノードが不正", - "missing-config": "__prop__: 設定ノードが存在しません", - "validation-error": "__prop__: チェックエラー: __node__, __id__: __error__" - } } } diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 79d13a01c..a88dc3c55 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -86,10 +86,10 @@ "failed": "inject処理が失敗しました。詳細はログを確認してください。", "toolong": "時間間隔が大き過ぎます", "invalid-expr": "JSONata式が不正: __error__", - "invalid-jsonata": "__prop__: プロパティ式が不正: __error__", - "invalid-prop": "__prop__: プロパティ式が不正: __error__", - "invalid-json": "__prop__: JSONデータが不正: __error__", - "invalid-repeat": "繰り返し数が不正" + "invalid-jsonata": "__prop__: プロパティ式が不正: __error__", + "invalid-prop": "__prop__: プロパティ式が不正: __error__", + "invalid-json": "__prop__: JSONデータが不正: __error__", + "invalid-repeat": "繰り返し数が不正" } }, "catch": { @@ -129,6 +129,7 @@ "msgprop": "メッセージプロパティ", "msgobj": "msgオブジェクト全体", "autostatus": "デバッグ出力と同じ", + "messageCount": "メッセージ数をカウント", "to": "出力先", "debtab": "デバッグタブ", "tabcon": "デバッグタブとコンソール", @@ -206,8 +207,8 @@ }, "error": { "missing-file": "証明書と秘密鍵のファイルが設定されていません", - "invalid-cert": "証明書が指定されていません", - "invalid-key": "秘密鍵が指定されていません" + "invalid-cert": "証明書が指定されていません", + "invalid-key": "秘密鍵が指定されていません" } }, "exec": { @@ -263,8 +264,8 @@ "moduleNameReserved": "予約された変数名です: __name__", "inputListener": "コード内で'input'イベントのリスナを設定できません", "non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました", - "invalid-js": "JavaScriptコードのエラー", - "missing-module": "モジュール __module__ が存在しません" + "invalid-js": "JavaScriptコードのエラー", + "missing-module": "モジュール __module__ が存在しません" } }, "template": { @@ -318,9 +319,9 @@ "limit": "limit", "limitTopic": "limit topic", "random": "random", - "rate": "流量", - "random-first": "ランダム最小値", - "random-last": "ランダム最大値", + "rate": "流量", + "random-first": "ランダム最小値", + "random-last": "ランダム最大値", "units": { "second": { "plural": "秒", @@ -342,11 +343,11 @@ }, "errors": { "too-many": "delayノード内で保持しているメッセージが多すぎます", - "invalid-timeout": "遅延時間が不正", - "invalid-rate": "流量値が不正", - "invalid-rate-unit": "流量単位時間が不正", - "invalid-random-first": "ランダム最小値が不正", - "invalid-random-last": "ランダム最大値が不正" + "invalid-timeout": "遅延時間が不正", + "invalid-rate": "流量値が不正", + "invalid-rate-unit": "流量単位時間が不正", + "invalid-random-first": "ランダム最小値が不正", + "invalid-random-last": "ランダム最大値が不正" } }, "trigger": { @@ -384,8 +385,8 @@ "resetMessage": "msg.resetを設定", "resetPayload": "msg.payloadが次の値", "resetprompt": "任意", - "duration": "時間間隔", - "topic": "トピック" + "duration": "時間間隔", + "topic": "トピック" } }, "comment": { @@ -443,7 +444,8 @@ "action": "動作", "staticTopic": "1つのトピックを購読", "dynamicTopic": "動的な購読", - "auto-connect": "自動接続" + "auto-connect": "自動接続", + "auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。" }, "sections-label": { "birth-message": "接続時の送信メッセージ(Birthメッセージ)", @@ -490,7 +492,7 @@ "invalid-action-action": "指定された動作が不正です", "invalid-action-alreadyconnected": "接続する前にブローカから切断してください", "invalid-action-badsubscription": "msg.topicが存在しないか不正です", - "invalid-client-id": "クライアントIDが未指定" + "invalid-client-id": "クライアントIDが未指定" } }, "httpin": { @@ -581,8 +583,8 @@ "send-error": "送信中にエラーが発生しました: ", "missing-conf": "サーバ設定が不足しています", "duplicate-path": "同じパスに対して2つのWebSocketリスナは指定できません: __path__", - "missing-server": "サーバが設定されていません", - "missing-client": "クライアントが設定されていません" + "missing-server": "サーバが設定されていません", + "missing-client": "クライアントが設定されていません" } }, "watch": { @@ -611,7 +613,8 @@ "ms": "ミリ秒", "chars": "文字", "close": "終了", - "optional": "(任意)" + "optional": "(任意)", + "reattach": "区切り文字を再追加" }, "type": { "listen": "待ち受け", @@ -652,8 +655,8 @@ "connect-timeout": "接続がタイムアウトしました", "connect-fail": "接続に失敗しました", "bad-string": "文字列への変換に失敗しました", - "invalid-host": "ホスト名が不正", - "invalid-port": "ポートが不正" + "invalid-host": "ホスト名が不正", + "invalid-port": "ポートが不正" } }, "udp": { @@ -668,7 +671,7 @@ "toport": "ポート", "address": "アドレス", "decode-base64": "Base64形式のペイロードを復号", - "port": "ポート" + "port": "ポート" }, "placeholder": { "interface": "(任意) 使用するローカルインターフェイスもしくはアドレス", @@ -716,7 +719,7 @@ "port-invalid": "udp: ポート番号が不正です", "alreadyused": "udp: 既に__port__番ポートが使用されています", "ifnotfound": "udp: インターフェイス __iface__ がありません", - "invalid-group": "マルチキャストグループが不正" + "invalid-group": "マルチキャストグループが不正" } }, "switch": { @@ -781,8 +784,8 @@ "invalid-json": "対象の値のJSONプロパティが不正", "invalid-expr": "JSONata式が不正: __error__", "no-override": "オブジェクト型でないプロパティを設定できません: __property__", - "invalid-prop": "プロパティ式が不正: __property__", - "invalid-json-data": "JSONデータが不正: __error__" + "invalid-prop": "プロパティ式が不正: __property__", + "invalid-json-data": "JSONデータが不正: __error__" } }, "range": { @@ -794,10 +797,10 @@ "from": "最小値", "to": "最大値", "roundresult": "小数値を四捨五入し整数値へ変換", - "minin": "入力最小値", - "maxin": "入力最大値", - "minout": "出力最小値", - "maxout": "出力最大値" + "minin": "入力最小値", + "maxin": "入力最大値", + "minout": "出力最小値", + "maxout": "出力最大値" }, "placeholder": { "min": "例) 0", @@ -1022,8 +1025,8 @@ "complete": "msg.complete プロパティが設定されたメッセージ受信後", "tip": "このモードでは、本ノードが split ノードと組となるか、 msg.parts プロパティが設定されたメッセージを受け取ることが前提となります。", "too-many": "joinノード内部で保持しているメッセージが多すぎます", - "message-prop": "メッセージプロパティ", - "merge": { + "message-prop": "メッセージプロパティ", + "merge": { "topics-label": "対象トピック", "topics": "トピック", "topic": "トピック", @@ -1081,11 +1084,11 @@ "too-many": "batchノード内で保持しているメッセージが多すぎます", "unexpected": "想定外のモード", "no-parts": "メッセージにpartsプロパティがありません", - "error": { - "invalid-count": "メッセージ数が不正", - "invalid-overlap": "オーバラップが不正", - "invalid-interval": "時間間隔が不正" - } + "error": { + "invalid-count": "メッセージ数が不正", + "invalid-overlap": "オーバラップが不正", + "invalid-interval": "時間間隔が不正" + } }, "rbe": { "rbe": "filter", @@ -1095,9 +1098,9 @@ "start": "初期値", "name": "名前", "septopics": "個別に動作を適用", - "gap": "変化量", - "property": "プロパティ", - "topic": "トピック" + "gap": "変化量", + "property": "プロパティ", + "topic": "トピック" }, "placeholder": { "bandgap": "例:10、5%", diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index fdf8b2249..df5f9fa08 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -100,7 +100,9 @@ "error": "クレデンシャルの読み込みエラー: __message__", "error-saving": "クレデンシャルの保存エラー: __message__", "not-registered": "クレデンシャル '__type__' は登録されていません", - "system-key-warning": "\n\n---------------------------------------------------------------------\nフローのクレデンシャルファイルはシステム生成キーで暗号化されています。\n\nシステム生成キーを何らかの理由で失った場合、クレデンシャルファイルを\n復元することはできません。その場合、ファイルを削除してクレデンシャルを\n再入力しなければなりません。\n\n設定ファイル内で 'credentialSecret' オプションを使って独自キーを設定\nします。変更を次にデプロイする際、Node-REDは選択したキーを用いてクレ\nデンシャルを再暗号化します。 \n\n---------------------------------------------------------------------\n" + "system-key-warning": "\n\n---------------------------------------------------------------------\nフローのクレデンシャルファイルはシステム生成キーで暗号化されています。\n\nシステム生成キーを何らかの理由で失った場合、クレデンシャルファイルを\n復元することはできません。その場合、ファイルを削除してクレデンシャルを\n再入力しなければなりません。\n\n設定ファイル内で 'credentialSecret' オプションを使って独自キーを設定\nします。変更を次にデプロイする際、Node-REDは選択したキーを用いてクレ\nデンシャルを再暗号化します。 \n\n---------------------------------------------------------------------\n", + "unencrypted": "暗号化されていないクレデンシャルを使用", + "encryptedNotFound": "暗号化されたクレデンシャルが存在しません" }, "flows": { "safe-mode": "セーフモードでフローを停止しました。開始するためにはデプロイしてください", From 84a3884ffc735b4db51a3920efc52031c18548e6 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 4 May 2022 23:54:23 +0900 Subject: [PATCH 010/195] Fix i18n in typedInput of header area --- .../@node-red/nodes/core/network/21-httprequest.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index 3e18f4fe7..6dc8af72a 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -127,12 +127,14 @@ { value: "Cache-Control", label: "Cache-Control", hasValue: false }, { value: "User-Agent", label: "User-Agent", hasValue: false }, { value: "Location", label: "Location", hasValue: false }, - { value: "other", label: "other", hasValue: true, icon: "red/images/typedInput/az.png" }, + { value: "other", label: RED._("node-red:httpin.label.other"), + hasValue: true, icon: "red/images/typedInput/az.png" }, { value: "msg", label: "msg.", hasValue: true }, ] const headerOptions = {}; const defaultOptions = [ - { value: "other", label: "other", hasValue: true, icon: "red/images/typedInput/az.png" }, + { value: "other", label: RED._("node-red:httpin.label.other"), + hasValue: true, icon: "red/images/typedInput/az.png" }, { value: "msg", label: "msg.", hasValue: true }, ]; headerOptions["accept"] = [ From 5fb811eb4c33e72824cdfdc8d793b285b83d37a6 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 5 May 2022 01:20:41 +0900 Subject: [PATCH 011/195] Change icon path from png to svg in typedInput --- .../@node-red/nodes/core/network/21-httprequest.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index 6dc8af72a..8b1320496 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -128,13 +128,13 @@ { value: "User-Agent", label: "User-Agent", hasValue: false }, { value: "Location", label: "Location", hasValue: false }, { value: "other", label: RED._("node-red:httpin.label.other"), - hasValue: true, icon: "red/images/typedInput/az.png" }, + hasValue: true, icon: "red/images/typedInput/az.svg" }, { value: "msg", label: "msg.", hasValue: true }, ] const headerOptions = {}; const defaultOptions = [ { value: "other", label: RED._("node-red:httpin.label.other"), - hasValue: true, icon: "red/images/typedInput/az.png" }, + hasValue: true, icon: "red/images/typedInput/az.svg" }, { value: "msg", label: "msg.", hasValue: true }, ]; headerOptions["accept"] = [ From bc5eafce666055ea6a7a7fe9608bdbf16d28a07b Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 5 May 2022 01:28:42 +0900 Subject: [PATCH 012/195] Update translations in file and http request nodes --- .../nodes/locales/ja/network/21-httprequest.html | 2 +- .../nodes/locales/ja/storage/10-file.html | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/ja/network/21-httprequest.html index d592c1d6e..1835b88f6 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/21-httprequest.html @@ -24,7 +24,7 @@
    method 文字列
    ノードの設定で指定していない場合、このプロパティでリクエストに用いるHTTPメソッドを設定します。GET, PUT, POST, PATCH, DELETEのいずれかを指定してください。
    headers オブジェクト
    -
    リクエストのHTTPヘッダを指定します。
    +
    リクエストのHTTPヘッダを指定します。注釈: msg.headersのヘッダは、ノード設定のヘッダで上書きされます。
    cookies オブジェクト
    設定すると、リクエストと共にクッキーを送ることができます。
    payload
    diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html index 3e67368d3..69765304c 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html @@ -15,11 +15,12 @@ --> \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.js new file mode 100644 index 000000000..624cfe6d8 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose1.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from loose1.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2.svg new file mode 100644 index 000000000..04bebc370 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2b.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2b.svg new file mode 100644 index 000000000..250348861 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/icons/loose2b.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.html b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.html new file mode 100644 index 000000000..e23e2880a --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.js new file mode 100644 index 000000000..e1cde2a82 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/loose2/loose2.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from loose2.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/icons/test.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/icons/test.svg new file mode 100644 index 000000000..04bebc370 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/icons/test.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/main.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/main.js new file mode 100644 index 000000000..e2e0781d1 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/main.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from regular module main.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/package.json new file mode 100644 index 000000000..26d4151fb --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/node-red-node-testnode/package.json @@ -0,0 +1,19 @@ +{ + "name": "node-red-node-testnode", + "version": "1.0.0", + "description": "A node-red node that does nothing other than exist", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red" + ], + "node-red": { + "nodes": { + "testnode": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/icons/test.svg b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/icons/test.svg new file mode 100644 index 000000000..04bebc370 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/icons/test.svg @@ -0,0 +1 @@ + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/main.js b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/main.js new file mode 100644 index 000000000..e2e0781d1 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/main.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from regular module main.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/package.json new file mode 100644 index 000000000..23cfd7e86 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir1/regular_module/package.json @@ -0,0 +1,14 @@ +{ + "name": "regular_node", + "version": "1.0.0", + "description": "A regular node that does nothing other than exist", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "test" + ], + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.html new file mode 100644 index 000000000..0e45b3007 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.js new file mode 100644 index 000000000..1fe9d89a3 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from @test/testnode index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/package.json new file mode 100644 index 000000000..a32462631 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@test/testnode/package.json @@ -0,0 +1,20 @@ +{ + "name": "@test/testnode", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.html new file mode 100644 index 000000000..5d9f2b0ec --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.js new file mode 100644 index 000000000..0f0eba392 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from testnode2 index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/package.json new file mode 100644 index 000000000..f9e8aecbe --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/testnode2/package.json @@ -0,0 +1,20 @@ +{ + "name": "testnode2", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode2": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/clientside.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/clientside.js new file mode 100644 index 000000000..fb2e22289 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/clientside.js @@ -0,0 +1,3 @@ +(function() { + console.log("Hi from test plugin client side") +})() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/plugin.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/plugin.js new file mode 100644 index 000000000..3202aeb8b --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/plugin.js @@ -0,0 +1,14 @@ +module.exports = function (RED) { + RED.plugins.registerPlugin('test-theme', { + type: 'node-red-theme', + scripts: [ + 'files/clientside.js' + ], + css: [ + 'files/theme.css', + ], + monacoOptions: { + theme: "vs" + } + }) +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/theme.css b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/theme.css new file mode 100644 index 000000000..872c93e10 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/files/theme.css @@ -0,0 +1 @@ +:root{--red-ui-primary-background: #f2f3fb;} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/package.json new file mode 100644 index 000000000..b8608c175 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/theme-plugin2/package.json @@ -0,0 +1,24 @@ +{ + "name": "test-theme2", + "version": "0.0.1", + "description": "test theme for Node-RED", + + "keywords": [ + "node-red", + "plugin", + "theme" + ], + "author": { + "name": "testy-McTesterson" + }, + "license": "MIT", + "node-red": { + "version": ">=2.2.0", + "plugins": { + "test-theme2": "files/plugin.js" + } + }, + "engines": { + "node": ">=12.x" + } +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.html new file mode 100644 index 000000000..0e45b3007 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.js new file mode 100644 index 000000000..1fe9d89a3 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from @test/testnode index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/package.json new file mode 100644 index 000000000..a32462631 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/testnode/package.json @@ -0,0 +1,20 @@ +{ + "name": "@test/testnode", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/clientside.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/clientside.js new file mode 100644 index 000000000..fb2e22289 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/clientside.js @@ -0,0 +1,3 @@ +(function() { + console.log("Hi from test plugin client side") +})() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/plugin.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/plugin.js new file mode 100644 index 000000000..3202aeb8b --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/plugin.js @@ -0,0 +1,14 @@ +module.exports = function (RED) { + RED.plugins.registerPlugin('test-theme', { + type: 'node-red-theme', + scripts: [ + 'files/clientside.js' + ], + css: [ + 'files/theme.css', + ], + monacoOptions: { + theme: "vs" + } + }) +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/theme.css b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/theme.css new file mode 100644 index 000000000..872c93e10 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/files/theme.css @@ -0,0 +1 @@ +:root{--red-ui-primary-background: #f2f3fb;} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/package.json new file mode 100644 index 000000000..56b1bfb6d --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/@test/theme-plugin3/package.json @@ -0,0 +1,24 @@ +{ + "name": "@test/test-theme3", + "version": "0.0.1", + "description": "test theme for Node-RED", + + "keywords": [ + "node-red", + "plugin", + "theme" + ], + "author": { + "name": "testy-McTesterson" + }, + "license": "MIT", + "node-red": { + "version": ">=2.2.0", + "plugins": { + "test-theme3": "files/plugin.js" + } + }, + "engines": { + "node": ">=12.x" + } +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.html b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.html new file mode 100644 index 000000000..a4034f340 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.js b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.js new file mode 100644 index 000000000..855768ad8 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/index.js @@ -0,0 +1,4 @@ + + (function() { + console.log("hello from testnode3 index.js") + })() diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/package.json new file mode 100644 index 000000000..41e9bb8f8 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir3/node_modules/testnode3/package.json @@ -0,0 +1,20 @@ +{ + "name": "testnode3", + "version": "1.0.0", + "description": "A test node that does nothing other than exist", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "test" + ], + "node-red": { + "nodes": { + "testnode3": "index.js" + } + }, + "author": "@testyMcTersterson", + "license": "MIT" +} From 62a2a4a9f5b90b660d790eaf626ac20d16e91db4 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 16 Jun 2022 11:38:19 +0100 Subject: [PATCH 077/195] Further simplify file node filename entry UX (v3) fixes #3668 --- .../@node-red/nodes/core/storage/10-file.html | 16 ++++++++-------- .../@node-red/nodes/locales/en-US/messages.json | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html index b726b537d..6b19ebaa8 100755 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html @@ -198,8 +198,8 @@ category: 'storage', defaults: { name: {value:""}, - filename: {value:"filename"}, - filenameType: {value:"msg"}, + filename: {value:""}, + filenameType: {value:"str"}, appendNewline: {value:true}, createDir: {value:false}, overwriteFile: {value:"false"}, @@ -236,8 +236,8 @@ label: node._("file.encoding.setbymsg") }).text(label).appendTo(encSel); $("#node-input-filename").typedInput({ - default: "msg", - types: ["str", "msg", "jsonata", "env"], + default: "str", + types: [{label:RED._("node-red:file.label.path"), value:"str", icon:""}, "msg", "jsonata", "env"], typeField: $("#node-input-filenameType") }); if(typeof node.filenameType == 'undefined') { @@ -297,8 +297,8 @@ category: 'storage', defaults: { name: {value:""}, - filename: {value:"filename"}, - filenameType: {value:"msg"}, + filename: {value:""}, + filenameType: {value:"str"}, format: {value:"utf8"}, chunk: {value:false}, sendError: {value: false}, @@ -341,8 +341,8 @@ label: label }).text(label).appendTo(encSel); $("#node-input-filename").typedInput({ - default: "msg", - types: ["str", "msg", "jsonata", "env"], + default: "str", + types: [{label:RED._("node-red:file.label.path"), value:"str", icon:""}, "msg", "jsonata", "env"], typeField: $("#node-input-filenameType") }); if(typeof node.filenameType == 'undefined') { diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 77d832abd..62d5f351f 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -928,6 +928,7 @@ "write": "write file", "read": "read file", "filename": "Filename", + "path": "path", "action": "Action", "addnewline": "Add newline (\\n) to each payload?", "createdir": "Create directory if it doesn't exist?", From ea469470541de61bcb13dc8dc4a120a9c6905673 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 13:48:36 +0100 Subject: [PATCH 078/195] Add RED.actions.getLabel to retrieve action i18n label --- .../editor-client/src/js/ui/actions.js | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js b/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js index 2273ae9ab..5bd9ea034 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/actions.js @@ -21,6 +21,34 @@ RED.actions = (function() { function getAction(name) { return actions[name].handler; } + function getActionLabel(name) { + let def = actions[name] + if (!def) { + return '' + } + if (!def.label) { + var options = def.options; + var key = options ? options.label : undefined; + if (!key) { + key = "action-list." +name.replace(/^.*:/,""); + } + var label = RED._(key); + if (label === key) { + // no translation. convert `name` to description + label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() { + if (arguments[5] === 0) { + return arguments[2].toUpperCase(); + } else { + return " "+arguments[4].toUpperCase(); + } + }); + } + def.label = label; + } + return def.label + } + + function invokeAction() { var args = Array.prototype.slice.call(arguments); var name = args.shift(); @@ -31,7 +59,7 @@ RED.actions = (function() { } function listActions() { var result = []; - var missing = []; + Object.keys(actions).forEach(function(action) { var def = actions[action]; var shortcut = RED.keyboard.getShortcut(action); @@ -42,28 +70,8 @@ RED.actions = (function() { isUser = !!RED.keyboard.getUserShortcut(action); } if (!def.label) { - var name = action; - var options = def.options; - var key = options ? options.label : undefined; - if (!key) { - key = "action-list." +name.replace(/^.*:/,""); - } - var label = RED._(key); - if (label === key) { - // no translation. convert `name` to description - label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() { - if (arguments[5] === 0) { - return arguments[2].toUpperCase(); - } else { - return " "+arguments[4].toUpperCase(); - } - }); - missing.push(key); - } - def.label = label; + def.label = getActionLabel(action) } - //console.log("; missing:", missing); - result.push({ id:action, scope:shortcut?shortcut.scope:undefined, @@ -79,6 +87,7 @@ RED.actions = (function() { add: addAction, remove: removeAction, get: getAction, + getLabel: getActionLabel, invoke: invokeAction, list: listActions } From 6bea3dabbb4a999a4c3fb77926ec0b0b154664b6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 13:49:25 +0100 Subject: [PATCH 079/195] Allow popover position to be set via absolute pos rather than relative --- .../@node-red/editor-client/src/js/ui/common/popover.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js index f1728ed83..9ddd3d866 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js @@ -610,10 +610,13 @@ RED.popover = (function() { var target = options.target; var align = options.align || "right"; var offset = options.offset || [0,0]; + var xPos = options.x; + var yPos = options.y; + var isAbsolutePosition = (xPos !== undefined && yPos !== undefined) - var pos = target.offset(); - var targetWidth = target.width(); - var targetHeight = target.outerHeight(); + var pos = isAbsolutePosition?{left:xPos, top: yPos}:target.offset(); + var targetWidth = isAbsolutePosition?0:target.width(); + var targetHeight = isAbsolutePosition?0:target.outerHeight(); var panelHeight = panel.height(); var panelWidth = panel.width(); From cce4f6f7f764675d6c9eecd611fde310f657f054 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 13:50:05 +0100 Subject: [PATCH 080/195] Add onpre/postselect, direction opts to menu and make id optional --- .../editor-client/src/js/ui/common/menu.js | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js index 417189b33..6327f5268 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js @@ -16,6 +16,7 @@ RED.menu = (function() { var menuItems = {}; + let menuItemCount = 0 function createMenuItem(opt) { var item; @@ -59,15 +60,16 @@ RED.menu = (function() { item = $('
  • '); } else { item = $('
  • '); - + if (!opt.id) { + opt.id = 'red-ui-menu-item-'+(menuItemCount++) + } if (opt.group) { item.addClass("red-ui-menu-group-"+opt.group); - } var linkContent = ''; if (opt.toggle) { - linkContent += ''; - linkContent += ''; + linkContent += ''; + linkContent += ''; } if (opt.icon !== undefined) { @@ -77,12 +79,15 @@ RED.menu = (function() { linkContent += ' '; } } - + let label = opt.label + if (!opt.label && typeof opt.onselect === 'string') { + label = RED.actions.getLabel(opt.onselect) + } if (opt.sublabel) { - linkContent += ''+opt.label+''+ + linkContent += ''+label+''+ ''+opt.sublabel+'' } else { - linkContent += ''+opt.label+'' + linkContent += ''+label+'' } linkContent += ''; @@ -126,10 +131,21 @@ RED.menu = (function() { }); } if (opt.options) { - item.addClass("red-ui-menu-dropdown-submenu pull-left"); + item.addClass("red-ui-menu-dropdown-submenu"+(opt.direction!=='right'?" pull-left":"")); var submenu = $('
      ').appendTo(item); for (var i=0;i",{class:"red-ui-menu red-ui-menu-dropdown pull-right"}); - + if (options.direction) { + topMenu.addClass("red-ui-menu-dropdown-direction-"+options.direction) + } if (options.id) { topMenu.attr({id:options.id+"-submenu"}); var menuParent = $("#"+options.id); @@ -175,6 +193,15 @@ RED.menu = (function() { var lastAddedSeparator = false; for (var i=0;i Date: Thu, 16 Jun 2022 13:57:50 +0100 Subject: [PATCH 081/195] Allow typeSearch to list actions A proof-of-concept which needs a bit more UI polish, but capturing the current code for the future. We do not add actions to the list, so the code is unused. --- .../editor-client/src/js/ui/typeSearch.js | 33 +++++++++++++++---- .../editor-client/src/js/ui/utils.js | 2 ++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 0fc633071..fc5b8e99e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -104,7 +104,9 @@ RED.typeSearch = (function() { var index = Math.max(0,selected); if (index < children.length) { var n = $(children[index]).find(".red-ui-editableList-item-content").data('data'); - typesUsed[n.type] = Date.now(); + if (!/^_action_:/.test(n.type)) { + typesUsed[n.type] = Date.now(); + } if (n.def.outputs === 0) { confirm(n); } else { @@ -173,6 +175,8 @@ RED.typeSearch = (function() { var nodeDiv = $('
      ',{class:"red-ui-search-result-node"}).appendTo(div); if (object.type === "junction") { nodeDiv.addClass("red-ui-palette-icon-junction"); + } else if (/^_action_:/.test(object.type)) { + nodeDiv.addClass("red-ui-palette-icon-junction") } else { var colour = RED.utils.getNodeColor(object.type,def); nodeDiv.css('backgroundColor',colour); @@ -182,11 +186,14 @@ RED.typeSearch = (function() { var iconContainer = $('
      ',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); RED.utils.createIconElement(icon_url, iconContainer, false); - if (object.type !== "junction" && def.inputs > 0) { - $('
      ',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv); - } - if (object.type !== "junction" && def.outputs > 0) { - $('
      ',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv); + + if (!/^_action_:/.test(object.type) && object.type !== "junction") { + if (def.inputs > 0) { + $('
      ',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv); + } + if (def.outputs > 0) { + $('
      ',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv); + } } var contentDiv = $('
      ',{class:"red-ui-search-result-description"}).appendTo(div); @@ -207,7 +214,9 @@ RED.typeSearch = (function() { } function confirm(def) { hide(); - typesUsed[def.type] = Date.now(); + if (!/^_action_:/.test(def.type)) { + typesUsed[def.type] = Date.now(); + } addCallback(def.type); } @@ -316,6 +325,7 @@ RED.typeSearch = (function() { function applyFilter(filter,type,def) { return !filter || ( + (!filter.spliceMultiple) && (!filter.type || type === filter.type) && (!filter.input || type === 'junction' || def.inputs > 0) && (!filter.output || type === 'junction' || def.outputs > 0) @@ -330,6 +340,13 @@ RED.typeSearch = (function() { 'inject','debug','function','change','switch','junction' ].filter(function(t) { return applyFilter(opts.filter,t,RED.nodes.getType(t)); }); + // if (opts.filter && opts.filter.input && opts.filter.output && !opts.filter.type) { + // if (opts.filter.spliceMultiple) { + // common.push('_action_:core:split-wires-with-junctions') + // } + // common.push('_action_:core:split-wire-with-link-nodes') + // } + var recentlyUsed = Object.keys(typesUsed); recentlyUsed.sort(function(a,b) { return typesUsed[b]-typesUsed[a]; @@ -354,6 +371,8 @@ RED.typeSearch = (function() { var itemDef = RED.nodes.getType(common[i]); if (common[i] === 'junction') { itemDef = { inputs:1, outputs: 1, label: 'junction', type: 'junction'} + } else if (/^_action_:/.test(common[i]) ) { + itemDef = { inputs:1, outputs: 1, label: common[i], type: common[i]} } if (itemDef) { item = { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index c3ea0ddd1..2c4cdca6b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -1032,6 +1032,8 @@ RED.utils = (function() { return "font-awesome/fa-circle-o" } else if (def.category === 'config') { return RED.settings.apiRootUrl+"icons/node-red/cog.svg" + } else if ((node && /^_action_:/.test(node.type)) || /^_action_:/.test(def.type)) { + return "font-awesome/fa-cogs" } else if (node && node.type === 'tab') { return "red-ui-icons/red-ui-icons-flow" // return RED.settings.apiRootUrl+"images/subflow_tab.svg" From 0eba4bdd61e196b446981b786d30edb5ccbd3665 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 13:59:14 +0100 Subject: [PATCH 082/195] Add right-click context menu to workspace --- Gruntfile.js | 1 + .../editor-client/src/js/ui/contextMenu.js | 175 +++++++++++ .../editor-client/src/js/ui/view-tools.js | 155 +++++++++- .../@node-red/editor-client/src/js/ui/view.js | 272 ++++++++++-------- .../editor-client/src/sass/dropdownMenu.scss | 35 ++- 5 files changed, 505 insertions(+), 133 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js diff --git a/Gruntfile.js b/Gruntfile.js index 979b38051..a168d7ce4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -192,6 +192,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/library.js", "packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js", "packages/node_modules/@node-red/editor-client/src/js/ui/search.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js new file mode 100644 index 000000000..1a0dee2bb --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -0,0 +1,175 @@ +RED.contextMenu = (function() { + + let menu; + function createMenu() { + // menu = RED.popover.menu({ + // options: [ + // { + // label: 'delete selection', + // onselect: function() { + // RED.actions.invoke('core:delete-selection') + // RED.view.focus() + // } + // }, + // { label: 'world' } + // ], + // width: 200, + // }) + + + + + } + + function disposeMenu() { + $(document).off("mousedown.red-ui-workspace-context-menu"); + if (menu) { + menu.remove(); + } + menu = null; + } + function show(options) { + if (menu) { + menu.remove() + } + + const selection = RED.view.selection() + const hasSelection = (selection.nodes && selection.nodes.length > 0); + const hasMultipleSelection = hasSelection && selection.nodes.length > 1; + const hasLinks = selection.links && selection.links.length > 0; + const isSingleLink = !hasSelection && hasLinks && selection.links.length === 1 + const isMultipleLinks = !hasSelection && hasLinks && selection.links.length > 1 + const canDelete = hasSelection || hasLinks + const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' + + const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g + + + const menuItems = [ + { onselect: 'core:show-action-list', onpostselect: function() {} }, + { + label: 'Insert', + options: [ + { + label: 'Node', + onselect: function() { + RED.view.showQuickAddDialog({ + position: [ options.x - offset.left, options.y - offset.top ], + touchTrigger: true, + splice: isSingleLink?selection.links[0]:undefined, + // spliceMultiple: isMultipleLinks + }) + } + }, + { + label: 'Junction', + onselect: 'core:split-wires-with-junctions', + disabled: hasSelection || !hasLinks + }, + { + label: 'Link Nodes', + onselect: 'core:split-wire-with-link-nodes', + disabled: hasSelection || !hasLinks + } + ] + + + + } + ] + // menuItems.push( + // { + // label: (isSingleLink || isMultipleLinks)?'Insert into wire...':'Add node...', + // onselect: function() { + // RED.view.showQuickAddDialog({ + // position: [ options.x - offset.left, options.y - offset.top ], + // touchTrigger: true, + // splice: isSingleLink?selection.links[0]:undefined, + // spliceMultiple: isMultipleLinks + // }) + // } + // }, + // ) + // if (hasLinks && !hasSelection) { + // menuItems.push({ onselect: 'core:split-wires-with-junctions', label: 'Insert junction'}) + // } + menuItems.push( + null, + { onselect: 'core:undo', disabled: RED.history.list().length === 0 }, + { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, + null, + { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection}, + { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection }, + { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() }, + { onselect: 'core:delete-selection', disabled: !canDelete }, + { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, + { onselect: 'core:select-all-nodes' }, + ) + + if (hasSelection) { + menuItems.push( + null, + isGroup + ? { onselect: 'core:ungroup-selection', disabled: !isGroup } + : { onselect: 'core:group-selection', disabled: !hasSelection } + ) + if (canRemoveFromGroup) { + menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }) + } + + } + const offset = $("#red-ui-workspace-chart").offset() + menu = RED.menu.init({ + direction: 'right', + onpreselect: function() { + disposeMenu() + }, + onpostselect: function() { + RED.view.focus() + }, + options: menuItems + }); + + menu.attr("id","red-ui-workspace-context-menu"); + menu.css({ + position: "absolute" + }) + menu.appendTo("body"); + + // TODO: prevent the menu from overflowing the window. + + var top = options.y + var left = options.x + + if (top+menu.height()-$(document).scrollTop() > $(window).height()) { + top -= (top+menu.height())-$(window).height() + 22; + } + if (left+menu.width()-$(document).scrollLeft() > $(window).width()) { + left -= (left+menu.width())-$(window).width() + 18; + } + menu.css({ + top: top+"px", + left: left+"px" + }) + $(".red-ui-menu.red-ui-menu-dropdown").hide(); + $(document).on("mousedown.red-ui-workspace-context-menu", function(evt) { + if (menu && menu[0].contains(evt.target)) { + return + } + disposeMenu() + }); + menu.show(); + + // menu.show({ + // target: $('#red-ui-main-container'), + // x: options.x, + // y: options.y + // }) + + + } + + return { + show: show + } +})() diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 699e5f222..888fb4d7f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -336,17 +336,17 @@ RED.view.tools = (function() { } - function addNode() { - var selection = RED.view.selection(); - if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].outputs > 0) { - var selectedNode = selection.nodes[0]; - RED.view.showQuickAddDialog([ - selectedNode.x + selectedNode.w + 50,selectedNode.y - ]) - } else { - RED.view.showQuickAddDialog(); - } - } + // function addNode() { + // var selection = RED.view.selection(); + // if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].outputs > 0) { + // var selectedNode = selection.nodes[0]; + // RED.view.showQuickAddDialog([ + // selectedNode.x + selectedNode.w + 50,selectedNode.y + // ]) + // } else { + // RED.view.showQuickAddDialog(); + // } + // } function gotoNearestNode(direction) { @@ -815,6 +815,9 @@ RED.view.tools = (function() { */ function splitWiresWithLinkNodes(wires) { let wiresToSplit = wires || RED.view.selection().links; + if (!wiresToSplit) { + return + } if (!Array.isArray(wiresToSplit)) { wiresToSplit = [wiresToSplit]; } @@ -1047,6 +1050,135 @@ RED.view.tools = (function() { } } + function addJunctionsToWires(wires) { + let wiresToSplit = wires || RED.view.selection().links; + if (!wiresToSplit) { + return + } + if (!Array.isArray(wiresToSplit)) { + wiresToSplit = [wiresToSplit]; + } + if (wiresToSplit.length === 0) { + return; + } + + var removedLinks = new Set() + var addedLinks = [] + var addedJunctions = [] + + var groupedLinks = {} + wiresToSplit.forEach(function(l) { + var sourceId = l.source.id+":"+l.sourcePort + groupedLinks[sourceId] = groupedLinks[sourceId] || [] + groupedLinks[sourceId].push(l) + + groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] + groupedLinks[l.target.id].push(l) + }); + var linkGroups = Object.keys(groupedLinks) + linkGroups.sort(function(A,B) { + return groupedLinks[B].length - groupedLinks[A].length + }) + linkGroups.forEach(function(gid) { + var links = groupedLinks[gid] + var junction = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + links = links.filter(function(l) { return !removedLinks.has(l) }) + if (links.length === 0) { + return + } + let pointCount = 0 + links.forEach(function(l) { + if (l._sliceLocation) { + junction.x += l._sliceLocation.x + junction.y += l._sliceLocation.y + delete l._sliceLocation + pointCount++ + } else { + junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2 + junction.y += l.source.y + l.target.y + pointCount += 2 + } + }) + junction.x = Math.round(junction.x/pointCount) + junction.y = Math.round(junction.y/pointCount) + if (RED.view.snapGrid) { + let gridSize = RED.view.gridSize() + junction.x = (gridSize*Math.round(junction.x/gridSize)); + junction.y = (gridSize*Math.round(junction.y/gridSize)); + } + + var nodeGroups = new Set() + + RED.nodes.addJunction(junction) + addedJunctions.push(junction) + let newLink + if (gid === links[0].source.id+":"+links[0].sourcePort) { + newLink = { + source: links[0].source, + sourcePort: links[0].sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: links[0].target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + links.forEach(function(l) { + removedLinks.add(l) + RED.nodes.removeLink(l) + let newLink + if (gid === l.target.id) { + newLink = { + source: l.source, + sourcePort: l.sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: l.target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + nodeGroups.add(l.source.g || "__NONE__") + nodeGroups.add(l.target.g || "__NONE__") + }) + if (nodeGroups.size === 1) { + var group = nodeGroups.values().next().value + if (group !== "__NONE__") { + RED.group.addToGroup(RED.nodes.group(group), junction) + } + } + }) + if (addedJunctions.length > 0) { + RED.history.push({ + t: 'add', + links: addedLinks, + junctions: addedJunctions, + removedLinks: Array.from(removedLinks) + }) + RED.nodes.dirty(true) + } + RED.view.redraw(true); + } + return { init: function() { RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) @@ -1109,6 +1241,7 @@ RED.view.tools = (function() { RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() }) 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:generate-node-names", generateNodeNames ) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 5ac9525ff..9f28d7191 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -206,7 +206,15 @@ RED.view = (function() { function init() { chart = $("#red-ui-workspace-chart"); - + chart.on('contextmenu', function(evt) { + evt.preventDefault() + evt.stopPropagation() + RED.contextMenu.show({ + x:evt.clientX-5, + y:evt.clientY-5 + }) + return false + }) outer = d3.select("#red-ui-workspace-chart") .append("svg:svg") .attr("width", space_width) @@ -992,7 +1000,10 @@ RED.view = (function() { scroll_position = [chart.scrollLeft(),chart.scrollTop()]; return; } - if (!mousedown_node && !mousedown_link && !mousedown_group) { + if (d3.event.button === 2) { + return + } + if (!mousedown_node && !mousedown_link && !mousedown_group && !d3.event.shiftKey) { selectedLinks.clear(); updateSelection(); } @@ -1046,6 +1057,7 @@ RED.view = (function() { options = options || {}; var point = options.position || lastClickPosition; var spliceLink = options.splice; + var spliceMultipleLinks = options.spliceMultiple var targetGroup = options.group; var touchTrigger = options.touchTrigger; @@ -1058,6 +1070,10 @@ RED.view = (function() { var ox = point[0]; var oy = point[1]; + const offset = $("#red-ui-workspace-chart").offset() + var clientX = ox + offset.left + var clientY = oy + offset.top + if (RED.settings.get("editor").view['view-snap-grid']) { // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') point[0] = Math.round(point[0] / gridSize) * gridSize; @@ -1109,8 +1125,12 @@ RED.view = (function() { } hideDragLines(); } - if (spliceLink) { - filter = {input:true, output:true} + if (spliceLink || spliceMultipleLinks) { + filter = { + input:true, + output:true, + spliceMultiple: spliceMultipleLinks + } } var rebuildQuickAddLink = function() { @@ -1135,8 +1155,8 @@ RED.view = (function() { var lastAddedWidth; RED.typeSearch.show({ - x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), - y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), + x:clientX-mainPos.left-node_width/2 - (ox-point[0]), + y:clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), disableFocus: touchTrigger, filter: filter, move: function(dx,dy) { @@ -1164,7 +1184,7 @@ RED.view = (function() { hideDragLines(); redraw(); }, - add: function(type,keepAdding) { + add: function(type, keepAdding) { if (touchTrigger) { keepAdding = false; resetMouseVars(); @@ -1172,7 +1192,13 @@ RED.view = (function() { var nn; var historyEvent; - if (type === 'junction') { + if (/^_action_:/.test(type)) { + const actionName = type.substring(9) + quickAddActive = false; + ghostNode.remove(); + RED.actions.invoke(actionName) + return + } else if (type === 'junction') { nn = { _def: {defaults:{}}, type: 'junction', @@ -1844,8 +1870,20 @@ RED.view = (function() { } } }) - - + activeLinks.forEach(function(link) { + if (!link.selected) { + var sourceY = link.source.y + var targetY = link.target.y + var sourceX = link.source.x+(link.source.w/2) + 10 + var targetX = link.target.x-(link.target.w/2) - 10 + if ( + sourceX > x && sourceX < x2 && sourceY > y && sourceY < y2 && + targetX > x && targetX < x2 && targetY > y && targetY < y2 + ) { + selectedLinks.add(link); + } + } + }) // var selectionChanged = false; // do { @@ -1893,114 +1931,118 @@ RED.view = (function() { slicePath = null; RED.view.redraw(true); } else if (mouse_mode == RED.state.SLICING_JUNCTION) { - var removedLinks = new Set() - var addedLinks = [] - var addedJunctions = [] - - var groupedLinks = {} - selectedLinks.forEach(function(l) { - var sourceId = l.source.id+":"+l.sourcePort - groupedLinks[sourceId] = groupedLinks[sourceId] || [] - groupedLinks[sourceId].push(l) - - groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] - groupedLinks[l.target.id].push(l) - }); - var linkGroups = Object.keys(groupedLinks) - linkGroups.sort(function(A,B) { - return groupedLinks[B].length - groupedLinks[A].length - }) - linkGroups.forEach(function(gid) { - var links = groupedLinks[gid] - var junction = { - _def: {defaults:{}}, - type: 'junction', - z: RED.workspaces.active(), - id: RED.nodes.id(), - x: 0, - y: 0, - w: 0, h: 0, - outputs: 1, - inputs: 1, - dirty: true - } - links = links.filter(function(l) { return !removedLinks.has(l) }) - if (links.length === 0) { - return - } - links.forEach(function(l) { - junction.x += l._sliceLocation.x - junction.y += l._sliceLocation.y - }) - junction.x = Math.round(junction.x/links.length) - junction.y = Math.round(junction.y/links.length) - if (snapGrid) { - junction.x = (gridSize*Math.round(junction.x/gridSize)); - junction.y = (gridSize*Math.round(junction.y/gridSize)); - } - - var nodeGroups = new Set() - - RED.nodes.addJunction(junction) - addedJunctions.push(junction) - let newLink - if (gid === links[0].source.id+":"+links[0].sourcePort) { - newLink = { - source: links[0].source, - sourcePort: links[0].sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: links[0].target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - links.forEach(function(l) { - removedLinks.add(l) - RED.nodes.removeLink(l) - let newLink - if (gid === l.target.id) { - newLink = { - source: l.source, - sourcePort: l.sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: l.target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - nodeGroups.add(l.source.g || "__NONE__") - nodeGroups.add(l.target.g || "__NONE__") - }) - if (nodeGroups.size === 1) { - var group = nodeGroups.values().next().value - if (group !== "__NONE__") { - RED.group.addToGroup(RED.nodes.group(group), junction) - } - } - }) + RED.actions.invoke("core:split-wires-with-junctions") slicePath.remove(); slicePath = null; - if (addedJunctions.length > 0) { - RED.history.push({ - t: 'add', - links: addedLinks, - junctions: addedJunctions, - removedLinks: Array.from(removedLinks) - }) - RED.nodes.dirty(true) - } - RED.view.redraw(true); + // var removedLinks = new Set() + // var addedLinks = [] + // var addedJunctions = [] + // + // var groupedLinks = {} + // selectedLinks.forEach(function(l) { + // var sourceId = l.source.id+":"+l.sourcePort + // groupedLinks[sourceId] = groupedLinks[sourceId] || [] + // groupedLinks[sourceId].push(l) + // + // groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] + // groupedLinks[l.target.id].push(l) + // }); + // var linkGroups = Object.keys(groupedLinks) + // linkGroups.sort(function(A,B) { + // return groupedLinks[B].length - groupedLinks[A].length + // }) + // linkGroups.forEach(function(gid) { + // var links = groupedLinks[gid] + // var junction = { + // _def: {defaults:{}}, + // type: 'junction', + // z: RED.workspaces.active(), + // id: RED.nodes.id(), + // x: 0, + // y: 0, + // w: 0, h: 0, + // outputs: 1, + // inputs: 1, + // dirty: true + // } + // links = links.filter(function(l) { return !removedLinks.has(l) }) + // if (links.length === 0) { + // return + // } + // links.forEach(function(l) { + // junction.x += l._sliceLocation.x + // junction.y += l._sliceLocation.y + // }) + // junction.x = Math.round(junction.x/links.length) + // junction.y = Math.round(junction.y/links.length) + // if (snapGrid) { + // junction.x = (gridSize*Math.round(junction.x/gridSize)); + // junction.y = (gridSize*Math.round(junction.y/gridSize)); + // } + // + // var nodeGroups = new Set() + // + // RED.nodes.addJunction(junction) + // addedJunctions.push(junction) + // let newLink + // if (gid === links[0].source.id+":"+links[0].sourcePort) { + // newLink = { + // source: links[0].source, + // sourcePort: links[0].sourcePort, + // target: junction + // } + // } else { + // newLink = { + // source: junction, + // sourcePort: 0, + // target: links[0].target + // } + // } + // addedLinks.push(newLink) + // RED.nodes.addLink(newLink) + // links.forEach(function(l) { + // removedLinks.add(l) + // RED.nodes.removeLink(l) + // let newLink + // if (gid === l.target.id) { + // newLink = { + // source: l.source, + // sourcePort: l.sourcePort, + // target: junction + // } + // } else { + // newLink = { + // source: junction, + // sourcePort: 0, + // target: l.target + // } + // } + // addedLinks.push(newLink) + // RED.nodes.addLink(newLink) + // nodeGroups.add(l.source.g || "__NONE__") + // nodeGroups.add(l.target.g || "__NONE__") + // }) + // if (nodeGroups.size === 1) { + // var group = nodeGroups.values().next().value + // if (group !== "__NONE__") { + // RED.group.addToGroup(RED.nodes.group(group), junction) + // } + // } + // }) + // slicePath.remove(); + // slicePath = null; + // + // if (addedJunctions.length > 0) { + // RED.history.push({ + // t: 'add', + // links: addedLinks, + // junctions: addedJunctions, + // removedLinks: Array.from(removedLinks) + // }) + // RED.nodes.dirty(true) + // } + // RED.view.redraw(true); } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (movingSet.length() > 0) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 98ab3bd3b..4104fd83e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -46,7 +46,7 @@ & > li > a, & > li > a:focus { display: block; - padding: 4px 12px 4px 32px; + padding: 4px 20px 4px 12px; clear: both; font-weight: normal; line-height: 20px; @@ -54,7 +54,10 @@ white-space: normal !important; outline: none; } - + & > li.pull-left > a, + & > li.pull-left > a:focus { + padding: 4px 12px 4px 32px; + } & > .active > a, & > .active > a:hover, & > .active > a:focus { @@ -145,8 +148,8 @@ position: relative; & > .red-ui-menu-dropdown { top: 0; - left: 100%; - margin-top: -6px; + left: calc(100% - 5px); + margin-top: 0; margin-left: -1px; } &.open > .red-ui-menu-dropdown, @@ -175,10 +178,10 @@ } } -.red-ui-menu-dropdown-submenu>a:after { +.red-ui-menu-dropdown-submenu.pull-left>a:after { display: none; } -.red-ui-menu-dropdown-submenu>a:before { +.red-ui-menu-dropdown-submenu.pull-left>a:before { display: block; float: left; width: 0; @@ -192,7 +195,25 @@ border-width: 5px 5px 5px 0; content: " "; } - +.red-ui-menu-dropdown-direction-right { + .red-ui-menu-dropdown-submenu>a:after { + display: none; + } + .red-ui-menu-dropdown-submenu>a:before { + display: block; + float: right; + width: 0; + height: 0; + margin-top: 5px; + margin-right: -15px; + /* Caret Arrow */ + border-color: transparent; + border-left-color: $menuCaret; + border-style: solid; + border-width: 5px 0 5px 5px; + content: " "; + } +} .red-ui-menu-dropdown-submenu.disabled > a:before { border-right-color: $menuCaret; } From ad32677263c3219161f0382973df9be550f52f13 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 14:37:48 +0100 Subject: [PATCH 083/195] Fix lint errors in contextMenu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 1a0dee2bb..379ed5433 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -103,14 +103,14 @@ RED.contextMenu = (function() { { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() }, { onselect: 'core:delete-selection', disabled: !canDelete }, { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, - { onselect: 'core:select-all-nodes' }, + { onselect: 'core:select-all-nodes' } ) if (hasSelection) { menuItems.push( null, - isGroup - ? { onselect: 'core:ungroup-selection', disabled: !isGroup } + isGroup ? + { onselect: 'core:ungroup-selection', disabled: !isGroup } : { onselect: 'core:group-selection', disabled: !hasSelection } ) if (canRemoveFromGroup) { From 1780cb9b91a3ac0d954cf2f5ddb821f82f5535a4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 14:47:11 +0100 Subject: [PATCH 084/195] Update dependencies --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d22cf89dd..87518820e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", - "i18next": "21.8.2", + "i18next": "21.8.10", "iconv-lite": "0.6.3", "is-utf8": "0.2.1", "js-yaml": "4.1.0", @@ -62,7 +62,7 @@ "moment": "2.29.3", "moment-timezone": "0.5.34", "mqtt": "4.3.7", - "multer": "1.4.4", + "multer": "1.4.5-lts.1", "mustache": "4.2.0", "node-red-admin": "^3.0.0", "node-watch": "0.7.3", @@ -76,7 +76,7 @@ "semver": "7.3.7", "tar": "6.1.11", "tough-cookie": "4.0.0", - "uglify-js": "3.15.5", + "uglify-js": "3.16.0", "uuid": "8.3.2", "ws": "7.5.6", "xml2js": "0.4.23" @@ -105,16 +105,16 @@ "grunt-sass": "~3.1.0", "grunt-simple-mocha": "~0.4.1", "grunt-simple-nyc": "^3.0.1", - "i18next-http-backend": "1.4.0", + "i18next-http-backend": "1.4.1", "jquery-i18next": "1.2.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "4.0.15", + "marked": "4.0.17", "minami": "1.2.3", "mocha": "9.2.2", "node-red-node-test-helper": "^0.2.7", "nodemon": "2.0.16", "proxy": "^1.0.2", - "sass": "1.51.0", + "sass": "1.52.3", "should": "13.2.3", "sinon": "11.1.2", "stoppable": "^1.1.0", From 16644284296faf6aea7108533cf430bde41c42f2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 15:37:07 +0100 Subject: [PATCH 085/195] Update tour for 3.0-beta.3 --- .../src/tours/images/context-menu.png | Bin 0 -> 67976 bytes .../editor-client/src/tours/welcome.js | 35 ++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/images/context-menu.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/context-menu.png b/packages/node_modules/@node-red/editor-client/src/tours/images/context-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..1acaab48b867480c8927079552f7e9431374573d GIT binary patch literal 67976 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≧h`;Ao2cW=E&fS`~X;_=(6n8!UyaT&;l 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 literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 219aaec7b..4b6f5f66d 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,15 +1,27 @@ export default { - version: "3.0.0-beta.1", + version: "3.0.0-beta.3", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 3.0 Beta 1!", - "ja": "Node-RED 3.0 ベータ1へようこそ!" + "en-US": "Welcome to Node-RED 3.0 Beta 3!", + "ja": "Node-RED 3.0 ベータ3へようこそ!" }, description: { - "en-US": "

      This is the first Beta release of Node-RED 3.0. It contains just about everything we have planned for the final release.

      Let's take a moment to discover the new features in this release.

      ", - "ja": "

      これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

      本リリースの新機能を見つけてみましょう。

      " + "en-US": "

      This is the final beta release of Node-RED 3.0.

      Let's take a moment to discover the new features in this release.

      ", + // "ja": "

      これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

      本リリースの新機能を見つけてみましょう。

      " + } + }, + { + title: { + "en-US": "Context Menu" + }, + image: 'images/context-menu.png', + description: { + "en-US": `

      The editor now has its own context menu when you + right-click in the workspace.

      +

      This makes many of the built-in actions much easier + to access.

      ` } }, { @@ -19,12 +31,13 @@ export default { }, image: 'images/junction-slice.gif', description: { - "en-US": `

      To make it easier to route wires around your flows, it is now possible to - add junction nodes that give you more control.

      -

      Junctions can be added to wires by holding the Shift key, then click and drag with - the right-hand mouse button across the wires.

      `, - "ja": `

      フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

      -

      シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

      ` + "en-US": `

      To make it easier to route wires around your flows, + it is now possible to add junction nodes that give + you more control.

      +

      Junctions can be added to wires by holding the Alt key + then click and drag the mouse across the wires.

      `, + // "ja": `

      フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

      + //

      シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

      ` }, }, { From ce529a9b9f3bbd9c8c71a2450191525813ea94ca Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 15:39:42 +0100 Subject: [PATCH 086/195] Update module dependencies --- packages/node_modules/@node-red/editor-api/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 2 +- packages/node_modules/@node-red/util/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 793c5e516..769eb8f5a 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -26,7 +26,7 @@ "express": "4.18.1", "memorystore": "1.6.7", "mime": "3.0.0", - "multer": "1.4.4", + "multer": "1.4.5-lts.1", "mustache": "4.2.0", "oauth2orize": "1.11.1", "passport-http-bearer": "1.0.1", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 2a0d7e7c1..8c9d5adf7 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -36,7 +36,7 @@ "js-yaml": "4.1.0", "media-typer": "1.1.0", "mqtt": "4.3.7", - "multer": "1.4.4", + "multer": "1.4.5-lts.1", "mustache": "4.2.0", "node-watch": "0.7.3", "on-headers": "1.0.2", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 29e1b8dd6..b13fefc9f 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -21,6 +21,6 @@ "fs-extra": "10.1.0", "semver": "7.3.7", "tar": "6.1.11", - "uglify-js": "3.15.5" + "uglify-js": "3.16.0" } } diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 302e6f84e..a75ecd7ff 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -16,7 +16,7 @@ ], "dependencies": { "fs-extra": "10.1.0", - "i18next": "21.8.2", + "i18next": "21.8.10", "json-stringify-safe": "5.0.1", "jsonata": "1.8.6", "lodash.clonedeep": "^4.5.0", From 4ed559af95ae845792549a5f4061b873b2971be5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 15:44:51 +0100 Subject: [PATCH 087/195] Update changelog and version for 3-beta.3 --- CHANGELOG.md | 38 +++++++++++++++++++ package.json | 2 +- .../@node-red/editor-api/package.json | 6 +-- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 2 +- .../@node-red/registry/package.json | 4 +- .../@node-red/runtime/package.json | 6 +-- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 ++--- 9 files changed, 55 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd3793487..254e6a977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +#### 3.0.0-beta.3: Beta Release + +Editor + + - 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** diff --git a/package.json b/package.json index d22cf89dd..854626fb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 793c5e516..7c403ac4b 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.2", - "@node-red/editor-client": "3.0.0-beta.2", + "@node-red/util": "3.0.0-beta.3", + "@node-red/editor-client": "3.0.0-beta.3", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 4380f5170..2a32cbded 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 2a0d7e7c1..f0a3b0b1b 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 29e1b8dd6..43ab2f00c 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.2", + "@node-red/util": "3.0.0-beta.3", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 7ee872153..dcf7bf47c 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0-beta.2", - "@node-red/util": "3.0.0-beta.2", + "@node-red/registry": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.3", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 302e6f84e..3b4f90418 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index e089fea71..36207c17e 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0-beta.2", - "@node-red/runtime": "3.0.0-beta.2", - "@node-red/util": "3.0.0-beta.2", - "@node-red/nodes": "3.0.0-beta.2", + "@node-red/editor-api": "3.0.0-beta.3", + "@node-red/runtime": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.3", + "@node-red/nodes": "3.0.0-beta.3", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From 83655a749c4eb3b410ec261ae1d908b3671f96ca Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 17:01:11 +0100 Subject: [PATCH 088/195] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254e6a977..87fcc30bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ 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 From 1844633ff1f22fed00b4b7cd0fcda3cb29cc1fd0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 22:56:54 +0100 Subject: [PATCH 089/195] Include scroll offset when positioning quick-add dialog Fixes #3684 --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 9f28d7191..522b86cbc 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1071,8 +1071,8 @@ RED.view = (function() { var oy = point[1]; const offset = $("#red-ui-workspace-chart").offset() - var clientX = ox + offset.left - var clientY = oy + offset.top + var clientX = ox + offset.left - $("#red-ui-workspace-chart").scrollLeft() + var clientY = oy + offset.top - $("#red-ui-workspace-chart").scrollTop() if (RED.settings.get("editor").view['view-snap-grid']) { // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') From 53184715bc2568440efba79109fc203b2569f93c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 23:18:02 +0100 Subject: [PATCH 090/195] Fix menu padding to handle both icons and submenus Fixes #3683 --- .../editor-client/src/js/ui/common/menu.js | 23 ++++++++++++++++++- .../editor-client/src/sass/dropdownMenu.scss | 14 ++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js index 6327f5268..c5b8d1d06 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js @@ -133,6 +133,8 @@ RED.menu = (function() { if (opt.options) { item.addClass("red-ui-menu-dropdown-submenu"+(opt.direction!=='right'?" pull-left":"")); var submenu = $('
        ').appendTo(item); + var hasIcons = false + var hasSubmenus = false for (var i=0;i li > a, & > li > a:focus { display: block; - padding: 4px 20px 4px 12px; + padding: 4px 12px 4px 32px; clear: both; font-weight: normal; line-height: 20px; @@ -58,6 +58,18 @@ & > li.pull-left > a:focus { padding: 4px 12px 4px 32px; } + &.red-ui-menu-dropdown-noicons > li > a, + &.red-ui-menu-dropdown-noicons > li > a:focus { + padding: 4px 12px 4px 12px; + } + + &.red-ui-menu-dropdown-submenus > li > a, + &.red-ui-menu-dropdown-submenus > li > a:focus { + padding-right: 20px; + } + + + & > .active > a, & > .active > a:hover, & > .active > a:focus { From e3d6d242ac5396a2d7ec3f0bcf235e3eedbaf345 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 23:29:46 +0100 Subject: [PATCH 091/195] Fix handling of spacebar inside JSON visual editor Fixes #3682 The treeList keyboard handling was consuming the event - used to select the item in the list. The fix here adds a 'selectable' flag on the treeList that can be used to disabled (=false) the keyboard selection of items. --- .../@node-red/editor-client/src/js/ui/common/treeList.js | 2 ++ .../@node-red/editor-client/src/js/ui/editors/json.js | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js index 3b6e2cde3..85c6f0992 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js @@ -21,6 +21,7 @@ * - multi : boolean - if true, .selected will return an array of results * otherwise, returns the first selected item * - sortable: boolean/string - TODO: see editableList + * - selectable: boolean - default true - whether individual items can be selected * - rootSortable: boolean - if 'sortable' is set, then setting this to * false, prevents items being sorted to the * top level of the tree @@ -118,6 +119,7 @@ switch(evt.keyCode) { case 32: // SPACE case 13: // ENTER + if (!that.options.selectable) { return } if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) { return } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js index 8531ba2c5..394350f26 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js @@ -534,6 +534,7 @@ var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"}); var filterDepth = Infinity; var list = $('
        ').appendTo(container).treeList({ + selectable: false, rootSortable: false, sortable: ".red-ui-editor-type-json-editor-item-handle", }).on("treelistchangeparent", function(event, evt) { From 0000f2a34d274d0785d6d7c6a8021ff38d8000fa Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 18 Jun 2022 00:51:05 +0900 Subject: [PATCH 092/195] Support i18n in context menu --- .../@node-red/editor-client/locales/en-US/editor.json | 6 ++++++ .../@node-red/editor-client/locales/ja/editor.json | 6 ++++++ .../@node-red/editor-client/src/js/ui/contextMenu.js | 8 ++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 01107d49d..e3028272b 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1187,5 +1187,11 @@ "missing-config": "__prop__: missing configuration node", "validation-error": "__prop__: validation error: __node__, __id__: __error__" } + }, + "contextMenu": { + "insert": "Insert", + "node": "Node", + "junction": "Junction", + "linkNodes": "Link Nodes" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 4c918b615..a78fb7d24 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1188,6 +1188,12 @@ "validation-error": "__prop__: チェックエラー: __node__, __id__: __error__" } }, + "contextMenu": { + "insert": "挿入", + "node": "ノード", + "junction": "分岐点", + "linkNodes": "Linkノード" + }, "action-list": { "toggle-show-tips": "ヒント表示切替", "show-about": "Node-REDの説明を表示", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 379ed5433..9920789f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -48,10 +48,10 @@ RED.contextMenu = (function() { const menuItems = [ { onselect: 'core:show-action-list', onpostselect: function() {} }, { - label: 'Insert', + label: RED._("contextMenu.insert"), options: [ { - label: 'Node', + label: RED._("contextMenu.node"), onselect: function() { RED.view.showQuickAddDialog({ position: [ options.x - offset.left, options.y - offset.top ], @@ -62,12 +62,12 @@ RED.contextMenu = (function() { } }, { - label: 'Junction', + label: RED._("contextMenu.junction"), onselect: 'core:split-wires-with-junctions', disabled: hasSelection || !hasLinks }, { - label: 'Link Nodes', + label: RED._("contextMenu.linkNodes"), onselect: 'core:split-wire-with-link-nodes', disabled: hasSelection || !hasLinks } From 418b3ee7d68f280e30340032b28df97106a8d87e Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 18 Jun 2022 00:56:34 +0900 Subject: [PATCH 093/195] Fix Japanese translations to be same as others --- .../@node-red/editor-client/locales/ja/editor.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index a78fb7d24..70cb0aa22 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1308,7 +1308,7 @@ "search": "検索", "search-previous": "前を検索", "search-next": "次を検索", - "show-action-list": "アクション一覧を表示", + "show-action-list": "動作一覧を表示", "confirm-edit-tray": "編集を完了", "cancel-edit-tray": "編集をキャンセル", "show-remote-diff": "リモートとの変更差分を表示", @@ -1330,6 +1330,6 @@ "zoom-out": "ズームアウト", "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", - "show-system-info": "システムインフォメーション" + "show-system-info": "システム情報" } } From 3ad95bec3ca553ddd35e48e95aaf171e504d7a97 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 18 Jun 2022 00:57:30 +0900 Subject: [PATCH 094/195] Add Japanese translation for file nodes --- packages/node_modules/@node-red/nodes/locales/ja/messages.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index c508a3e76..6e16daa6f 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -928,6 +928,7 @@ "write": "write file", "read": "read file", "filename": "ファイル名", + "path": "パス", "action": "動作", "addnewline": "メッセージの入力のたびに改行を追加", "createdir": "ディレクトリが存在しない場合は作成", From 01d9affe61efbf35fdeb304985dfc5061d24299e Mon Sep 17 00:00:00 2001 From: cow0w Date: Fri, 17 Jun 2022 22:18:14 +0300 Subject: [PATCH 095/195] Add support for evalulating {{env.}} within a template node --- .../nodes/core/function/80-template.js | 24 +++++++++++++++++++ test/nodes/core/function/80-template_spec.js | 22 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/function/80-template.js b/packages/node_modules/@node-red/nodes/core/function/80-template.js index d4c27cfd0..69954aba2 100644 --- a/packages/node_modules/@node-red/nodes/core/function/80-template.js +++ b/packages/node_modules/@node-red/nodes/core/function/80-template.js @@ -44,6 +44,14 @@ module.exports = function(RED) { return undefined; } + function parseEnv(key) { + var match = /^env\.(.+)/.exec(key); + if (match) { + return match[1]; + } + return undefined; + } + /** * Custom Mustache Context capable to collect message property and node * flow and global context @@ -74,6 +82,11 @@ module.exports = function(RED) { return value; } + // try env + if (parseEnv(name)) { + return this.cachedContextTokens[name]; + } + // try flow/global context: var context = parseContext(name); if (context) { @@ -156,6 +169,17 @@ module.exports = function(RED) { var tokens = extractTokens(mustache.parse(template)); var resolvedTokens = {}; tokens.forEach(function(name) { + var env_name = parseEnv(name); + if (env_name) { + var promise = new Promise((resolve, reject) => { + var val = RED.util.evaluateNodeProperty(env_name, 'env', node) + resolvedTokens[name] = val; + resolve(); + }); + promises.push(promise); + return; + } + var context = parseContext(name); if (context) { var type = context.type; diff --git a/test/nodes/core/function/80-template_spec.js b/test/nodes/core/function/80-template_spec.js index e944824b3..b4f530d05 100644 --- a/test/nodes/core/function/80-template_spec.js +++ b/test/nodes/core/function/80-template_spec.js @@ -144,6 +144,28 @@ describe('template node', function() { }); }); + describe('env var', function() { + before(function() { + process.env.TEST = 'xyzzy'; + }) + after(function() { + delete process.env.TEST; + }) + + it('should modify payload from env variable', function(done) { + var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{env.TEST}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', 'payload=xyzzy'); + done(); + }); + n1.receive({payload:"foo",topic: "bar"}); + }); + }); + }); + it('should modify payload from flow context', function(done) { var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; helper.load(templateNode, flow, function() { From 75c9353cbddbbeb7203ddc5ebb24dc3fb8b0b0ea Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:23:28 +0900 Subject: [PATCH 096/195] Fix keys to insert junction --- .../node_modules/@node-red/editor-client/src/tours/welcome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 4b6f5f66d..1289e6bee 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -34,7 +34,7 @@ export default { "en-US": `

        To make it easier to route wires around your flows, it is now possible to add junction nodes that give you more control.

        -

        Junctions can be added to wires by holding the Alt key +

        Junctions can be added to wires by holding both the Alt key and the Shift key then click and drag the mouse across the wires.

        `, // "ja": `

        フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

        //

        シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

        ` From 13885493acbc09d0578894717fff65daaf6e3a61 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:26:48 +0900 Subject: [PATCH 097/195] Update Japanese translations in welcome tour --- .../@node-red/editor-client/src/tours/welcome.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 1289e6bee..98d83c7e5 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -9,19 +9,22 @@ export default { }, description: { "en-US": "

        This is the final beta release of Node-RED 3.0.

        Let's take a moment to discover the new features in this release.

        ", - // "ja": "

        これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

        本リリースの新機能を見つけてみましょう。

        " + "ja": "

        これはNode-RED 3.0の最後のベータリリースです。

        本リリースの新機能を見つけてみましょう。

        " } }, { title: { - "en-US": "Context Menu" + "en-US": "Context Menu", + "ja": "コンテキストメニュー" }, image: 'images/context-menu.png', description: { "en-US": `

        The editor now has its own context menu when you right-click in the workspace.

        This makes many of the built-in actions much easier - to access.

        ` + to access.

        `, + "ja": `

        ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。

        +

        これによって多くの組み込み動作を、より簡単に利用できます。

        ` } }, { @@ -36,8 +39,8 @@ export default { you more control.

        Junctions can be added to wires by holding both the Alt key and the Shift key then click and drag the mouse across the wires.

        `, - // "ja": `

        フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

        - //

        シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

        ` + "ja": `

        フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

        +

        Altキーとシフトキーを押しながらマウスをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

        ` }, }, { From 643541eebdc4394937c0038e5582ff21586a9589 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:37:55 +0900 Subject: [PATCH 098/195] Remove unnecessary spaces --- .../@node-red/nodes/locales/ja/function/10-function.html | 2 +- .../@node-red/nodes/locales/ja/network/10-mqtt.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html index a18e5e8a8..960b755f6 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html @@ -28,7 +28,7 @@

        返却/sendの対象は次のとおりです:

        • 単一メッセージオブジェクト - 最初の出力に接続されたノードに渡されます
        • -
        • メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます
        • +
        • メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます

        注: 初期化処理の実行はノードの初期化中に行われます。そのため、初期化処理タブにsendを記述した場合に後続ノードでメッセージを受け取れないことがあります。

        配列要素が配列の場合には、複数のメッセージを対応する出力に送出します。

        diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html index 1b43ea097..435829e1e 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html @@ -89,7 +89,7 @@
        userProperties オブジェクト
        MQTTv5: メッセージのユーザプロパティ
        messageExpiryInterval 数値
        -
        MQTTv5: 秒単位のメッセージの有効期限
        +
        MQTTv5: 秒単位のメッセージの有効期限
        topicAlias 数値
        MQTTv5: 使用するMQTTトピックエイリアス
        From 5e592427e940bd095440bb2ea0bf65636fe30194 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:56:59 +0900 Subject: [PATCH 099/195] Add Japanese translation in action list for junction --- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 70cb0aa22..ac09a1e87 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1330,6 +1330,7 @@ "zoom-out": "ズームアウト", "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", - "show-system-info": "システム情報" + "show-system-info": "システム情報", + "split-wires-with-junctions": "分岐点によりワイヤーを分割" } } From 6c2297c3659ddbd5cc784ba07e97680d4816eaea Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sat, 18 Jun 2022 19:41:46 +0200 Subject: [PATCH 100/195] Update german translation --- .../editor-client/locales/de/editor.json | 191 ++++++++++++------ 1 file changed, 131 insertions(+), 60 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index 11d8e5d96..dd931a812 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -44,7 +44,8 @@ "loadNodes": "Lade Nodes __count__", "loadFlows": "Lade Flows", "importFlows": "Füge Flows dem Arbeitsbereich hinzu", - "importError": "

        Fehler beim Laden von Flows.

        __message__

        " + "importError": "

        Fehler beim Laden von Flows.

        __message__

        ", + "loadingProject": "Lade Projekt" }, "workspace": { "defaultName": "Flow __number__", @@ -53,7 +54,16 @@ "delete": "Sind Sie sicher, dass '__label__' gelöscht werden soll?", "dropFlowHere": "Hier kann der Flow eingefügt werden", "addFlow": "Flow hinzufügen", + "addFlowToRight": "Flow zum Arbeitsbereich rechts hinzufügen", + "hideFlow": "Flow ausblenden", + "hideOtherFlows": "Andere Flows ausblenden", + "showAllFlows": "Alle Flows anzeigen", + "hideAllFlows": "Alle Flows ausblenden", + "hiddenFlows": "Liste __count__ ausgeblendeten Flow auf", + "hiddenFlows_plural": "Liste __count__ ausgeblendete Flows auf", + "showLastHiddenFlow": "Letzten ausgeblendeten Flow anzeigen", "listFlows": "Flows auflisten", + "listSubflows": "Subflows auflisten", "status": "Status", "enabled": "Aktiviert", "disabled": "Deaktiviert", @@ -65,6 +75,8 @@ "view": { "view": "Ansicht", "grid": "Raster", + "storeZoom": "Zoomstufe beim Laden wiederherstellen", + "storePosition": "Scrollposition beim Laden wiederherstellen", "showGrid": "Raster anzeigen", "snapGrid": "Am Raster ausrichten", "gridSize": "Rastergröße", @@ -82,8 +94,9 @@ "palette": { "show": "Palette anzeigen" }, + "edit": "Bearbeiten", "settings": "Einstellungen", - "userSettings": "Einstellungen", + "userSettings": "Benutzereinstellungen", "nodes": "Nodes", "displayStatus": "Node-Status anzeigen", "displayConfig": "Konfigurations-Nodes", @@ -92,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Erstellen", + "createSubflow": "Subflow erstellen", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -104,24 +117,42 @@ "editPalette": "Palette verwalten", "other": "Sonstige", "showTips": "Tipps anzeigen", + "showWelcomeTours": "Geführte Touren für neue Versionen anzeigen", "help": "Node-RED-Website", - "projects": "Projekt", + "projects": "Projekte", "projects-new": "Neu", "projects-open": "Öffnen", - "projects-settings": "Einstellungen", + "projects-settings": "Projekteinstellungen", "showNodeLabelDefault": "Zeige Namen von neu hinzugefügten Nodes", - "groups": "Gruppe", + "codeEditor": "Code-Editor", + "groups": "Gruppen", "groupSelection": "Auswahl gruppieren", "ungroupSelection": "Gruppe auflösen", "groupMergeSelection": "Auswahl der Gruppe hinzufügen", - "groupRemoveSelection": "Auswahl aus der Gruppe entfernen" + "groupRemoveSelection": "Auswahl aus der Gruppe entfernen", + "arrange": "Anordnen", + "alignLeft": "Links ausrichten", + "alignCenter": "Zentrieren", + "alignRight": "Rechts ausrichten", + "alignTop": "Oben ausrichten", + "alignMiddle": "Mittig ausrichten", + "alignBottom": "Unten ausrichten", + "distributeHorizontally": "Horizontal verteilen", + "distributeVertically": "Vertikal verteilen", + "moveToBack": "Nach hinten verschieben", + "moveToFront": "Nach vorne verschieben", + "moveBackwards": "Rückwärts verschieben", + "moveForwards": "Vorwärts verschieben" } }, "actions": { "toggle-navigator": "Navigator ein-/ausblenden", "zoom-out": "Verkleinern", "zoom-reset": "Vergrößerung rücksetzen", - "zoom-in": "Vergrößern" + "zoom-in": "Vergrößern", + "search-flows": "Flows durchsuchen", + "search-prev": "Vorherige", + "search-next": "Nächste" }, "user": { "loggedInAs": "Angemeldet als __name__", @@ -131,7 +162,7 @@ "loginFailed": "Anmeldung fehlgeschlagen", "notAuthorized": "Nicht berechtigt", "errors": { - "settings": "Sie müssen angemeldet sein, um auf die Einstellungen zuzugreifen zu können", + "settings": "Sie müssen angemeldet sein, um auf die Einstellungen zugreifen zu können", "deploy": "Sie müssen angemeldet sein, um Änderungen übernehmen (deploy) zu können", "notAuthorized": "Sie müssen angemeldet sein, um diese Aktion ausführen zu können" } @@ -141,7 +172,7 @@ "warnings": { "undeployedChanges": "Node hat nicht übernommene (deploy) Änderungen", "nodeActionDisabled": "Node-Aktionen deaktiviert", - "nodeActionDisabledSubflow": "Node-Aktionen deaktiviert im Subflow", + "nodeActionDisabledSubflow": "Node-Aktionen innerhalb des Subflows deaktiviert", "missing-types": "

        Flows gestoppt aufgrund fehlender Node-Typen

        ", "missing-modules": "

        Flows angehalten aufgrund fehlender Module

        ", "safe-mode": "

        Flows sind im abgesicherten Modus gestoppt.

        Flows können bearbeitet und übernommen (deploy) werden, um sie neu zu starten.

        ", @@ -157,21 +188,21 @@ "error": "Fehler: __message__", "errors": { "lostConnection": "Verbindung zum Server verloren. Verbindung wird erneut hergestellt ...", - "lostConnectionReconnect": "Verbindung zum Server verloren. Verbindung wird in __time__ s versucht wieder herzustellen.", - "lostConnectionTry": "Jetzt testen", + "lostConnectionReconnect": "Verbindung zum Server verloren. Wiederherstellung der Verbindung in __time__s.", + "lostConnectionTry": "Jetzt versuchen", "cannotAddSubflowToItself": "Subflow kann nicht zu sich selbst hinzugefügt werden", "cannotAddCircularReference": "Subflow kann nicht hinzugefügt werden, da ein zirkulärer Bezug erkannt wurde", - "unsupportedVersion": "

        Nicht unterstützte Version von Node.js erkannt.

        Es muss ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

        ", + "unsupportedVersion": "

        Nicht unterstützte Version von Node.js erkannt.

        Es sollte ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

        ", "failedToAppendNode": "

        Fehler beim Laden von '__module__'.

        __error__

        " }, "project": { - "change-branch": "Wechsel in den Branch '__project__'", - "merge-abort": "Merge abgebrochen", + "change-branch": "Wechsel in den lokalen Branch '__project__'", + "merge-abort": "Git-Merge abgebrochen", "loaded": "Projekt '__project__' geladen", "updated": "Projekt '__project__' aktualisiert", "pull": "Projekt '__project__' erneut geladen", "revert": "Änderungen im Projekt '__project__' rückgängig gemacht", - "merge-complete": "Merge abgeschlossen", + "merge-complete": "Git-Merge abgeschlossen", "setupCredentials": "Berechtigungen einrichten", "setupProjectFiles": "Projektdateien einrichten", "no": "Nein, Danke", @@ -186,7 +217,7 @@ "no-thanks": "Nein, Danke", "create-default-project": "Standardprojektdateien erstellen", "show-merge-conflicts": "Merge-Konflikte anzeigen", - "unknownNodesButton": "Finden Sie unbekannte nodes" + "unknownNodesButton": "Nach unbekannten Nodes suchen" } }, "clipboard": { @@ -204,17 +235,17 @@ "subflow_plural": "__count__ Subflows", "replacedNodes": "__count__ Node ersetzt", "replacedNodes_plural": "__count__ Nodes ersetzt", - "pasteNodes": "Flow-JSON hier einfügen oder", + "pasteNodes": "Flow-JSON einfügen oder", "selectFile": "Datei für Import auswählen", - "importNodes": "Import", - "exportNodes": "Export", + "importNodes": "Importiere Nodes", + "exportNodes": "Exportiere Nodes", "download": "Download", - "importUnrecognised": "Importierter Typ nicht erkannt:", - "importUnrecognised_plural": "Importierte Typen nicht erkannt:", - "importDuplicate": "Importiertes doppeltes Node:", - "importDuplicate_plural": "Importierte doppelte Nodes:", - "nodesExported": "Nodes in der Zwischenablage abgelegt", - "nodesImported": "Eingefügt:", + "importUnrecognised": "Nicht erkannter Typ importiert:", + "importUnrecognised_plural": "Nicht erkannte Typen importiert:", + "importDuplicate": "Doppelte Node importiert:", + "importDuplicate_plural": "Doppelte Nodes importiert:", + "nodesExported": "Nodes in die Zwischenablage exportiert", + "nodesImported": "Importiert:", "nodeCopied": "__count__ Node kopiert", "nodeCopied_plural": "__count__ Nodes kopiert", "groupCopied": "__count__ Gruppe kopiert", @@ -230,11 +261,11 @@ "all": "Alle Flows", "compact": "Kompakt", "formatted": "Formatiert", - "copy": "In Zwischenablage exportieren", + "copy": "In Zwischenablage kopieren", "export": "In Bibliothek exportieren", "exportAs": "Exportiere als", "overwrite": "Ersetzen", - "exists": "

        '__file__' existiert bereits.

        Soll sie ersetzt werden?

        " + "exists": "

        \"__file__\" existiert bereits.

        Soll sie ersetzt werden?

        " }, "import": { "import": "Importiere in", @@ -270,9 +301,9 @@ "successfulRestart": "Flows erfolgreich neugestartet", "deployFailed": "Übernahme (deploy) fehlgeschlagen: __message__", "unusedConfigNodes": "Einige Konfigurations-Nodes werden nicht verwendet.", - "unusedConfigNodesButton":"Finden Sie ungenutzte konfig nodes", - "unknownNodesButton":"Finden Sie unbekannte nodes", - "invalidNodesButton":"Finden Sie ungültige nodes", + "unusedConfigNodesButton": "Suche nach unbenutzten Konfigurations-Nodes", + "unknownNodesButton": "Suche nach unbekannten Nodes", + "invalidNodesButton": "Suche nach ungültigen Nodes", "errors": { "noResponse": "Keine Antwort vom Server" }, @@ -355,10 +386,10 @@ "keys": "Schlüsselwörter", "keysPlaceholder": "Komma-getrennte Schlüsselwörter", "author": "Author", - "authorPlaceholder": "Dein Name ", + "authorPlaceholder": "Ihr Name ", "desc": "Beschreibung", "env": { - "restore": "Stelle auf Subflow-Standard zurück", + "restore": "Subflow-Standard wiederherstellen", "remove": "Entferne Umgebungsvariable" }, "errors": { @@ -367,9 +398,9 @@ } }, "group": { - "editGroup": "Editiere Gruppe: __name__", + "editGroup": "Bearbeite Gruppe: __name__", "errors": { - "cannotCreateDiffGroups": "Kann keine Gruppe erzeugen mit Nodes von verschiedenen Gruppen", + "cannotCreateDiffGroups": "Kann keine Gruppe mit Nodes von anderen Gruppen erstellen", "cannotAddSubflowPorts": "Kann keine Subflow-Anschlüsse zu einer Gruppe hinzufügen" } }, @@ -383,7 +414,7 @@ "addNewConfig": "Neuen Konfigurations-Node '__type__' hinzufügen", "editNode": "Node '__type__' bearbeiten", "editConfig": "Konfigurations-Node '__type__' bearbeiten", - "addNewType": "Neuen Typ '__type__' hinzufügen", + "addNewType": "Neuen Typ '__type__' hinzufügen ...", "nodeProperties": "Node-Eigenschaften", "label": "Name", "color": "Farbe", @@ -406,7 +437,7 @@ "loadCredentials": "Lade Node-Berechtigungen", "inputs": { "input": "Eingang", - "select": "Wähle", + "select": "Auswahl", "checkbox": "Checkbox", "spinner": "Spinner", "none": "Kein", @@ -449,23 +480,27 @@ "shortcut": "Tastenkürzel", "scope": "Geltungsbereich", "unassigned": "Nicht zugeordnet", - "global": "global", + "global": "Global", "workspace": "Arbeitsbereich", - "selectAll": "Alle Nodes auswählen", + "selectAll": "Alles auswählen", + "selectNone": "Alles abwählen", "selectAllConnected": "Alle verbundenen Nodes auswählen", "addRemoveNode": "Node aus Auswahl hinzufügen/entfernen", "editSelected": "Ausgewählten Node bearbeiten", "deleteSelected": "Ausgewählte Nodes oder Links löschen", - "importNode": "Node importieren", - "exportNode": "Node exportieren", + "importNode": "Nodes importieren", + "exportNode": "Nodes exportieren", "nudgeNode": "Ausgewählte Nodes verschieben (1px)", "moveNode": "Ausgewählte Nodes verschieben (20px)", "toggleSidebar": "Seitenleiste ein-/ausblenden", "togglePalette": "Palette ein-/ausblenden", "copyNode": "Ausgewählte Nodes kopieren", "cutNode": "Ausgewählte Nodes ausschneiden", - "pasteNode": "Node einfügen", + "pasteNode": "Nodes einfügen", + "copyGroupStyle": "Gruppenstil kopieren", + "pasteGroupStyle": "Gruppenstil einfügen", "undoChange": "Letzte Änderung rückgängig machen", + "redoChange": "Letzte Änderung wiederholen", "searchBox": "Suchfeld öffnen", "managePalette": "Palette verwalten", "actionList": "Aktionsliste" @@ -491,7 +526,7 @@ "palette": { "noInfo": "Keine Informationen verfügbar", "filter": "Nodes filtern", - "search": "Modules durchsuchen", + "search": "Module durchsuchen", "addCategory": "Neu hinzufügen ...", "label": { "subflows": "Subflows", @@ -508,8 +543,8 @@ "advanced": "Fortgeschritten" }, "actions": { - "collapse-all": "Kategorien einklappen", - "expand-all": "Kategorien ausklappen" + "collapse-all": "Alle Kategorien einklappen", + "expand-all": "Alle Kategorien ausklappen" }, "event": { "nodeAdded": "Node zur Palette hinzugefügt:", @@ -520,7 +555,8 @@ "nodeEnabled_plural": "Nodes aktiviert:", "nodeDisabled": "Node deaktiviert:", "nodeDisabled_plural": "Nodes deaktiviert:", - "nodeUpgraded": "Upgrade von Node-Modul __module__ auf Version __version__ durchgeführt" + "nodeUpgraded": "Upgrade von Node-Modul __module__ auf Version __version__ durchgeführt", + "unknownNodeRegistered": "Fehler beim Laden des Nodes:
        • __type__
          __error__
        " }, "editor": { "title": "Palette verwalten", @@ -636,7 +672,7 @@ "outline": "Entwurf", "empty": "leer", "globalConfig": "Globale Konfigurations-Nodes", - "triggerAction": "Auslösen", + "triggerAction": "Aktion auslösen", "find": "Suche im Arbeitsbereich" }, "help": { @@ -739,7 +775,7 @@ "userName": "Benutzername", "email": "E-Mail", "workflow": "Arbeitsablauf", - "workfowTip": "Wähle deinen bevorzugten Git-Arbeitsablauf", + "workfowTip": "Wählen Sie Ihren bevorzugten Git-Arbeitsablauf (Workflow)", "workflowManual": "Manuell", "workflowManualTip": "Alle Änderungen müssen manuell übertragen werden (commit) über die Seitenleiste 'Projekthistorie'", "workflowAuto": "Automatisch", @@ -858,6 +894,8 @@ "addTitle": "Element hinzufügen" }, "search": { + "history": "Suchhistorie", + "clear": "Leeren", "empty": "Keine Übereinstimmungen gefunden", "addNode": "Node hinzufügen ...", "options": { @@ -865,7 +903,10 @@ "unusedConfigNodes": "Unbenutzte Konfigurations-Nodes", "invalidNodes": "Ungültige Nodes", "uknownNodes": "Unbekannte Nodes", - "unusedSubflows": "Unbenutzte Subflows" + "unusedSubflows": "Unbenutzte Subflows", + "hiddenFlows": "Versteckte Flows", + "modifiedNodes": "Geänderte Nodes", + "thisFlow": "Aktueller Flow" } }, "expressionEditor": { @@ -887,6 +928,9 @@ "eval": "Fehler beim Auswerten des Ausdrucks\n__message__" } }, + "monaco": { + "setTheme": "Thema auswählen" + }, "jsEditor": { "title": "JavaScript-Editor" }, @@ -896,8 +940,10 @@ "jsonEditor": { "title": "JSON-Editor", "format": "JSON formatieren", - "rawMode": "JSON-Editor", + "rawMode": "Bearbeite JSON", "uiMode": "Visueller Editor", + "rawMode-readonly": "JSON", + "uiMode-readonly": "Visuell", "insertAbove": "Oberhalb einfügen", "insertBelow": "Unterhalb einfügen", "addItem": "Element hinzufügen", @@ -968,7 +1014,7 @@ "clone": "Projekt klonen", "desc0": "Wenn Sie bereits über ein Git-Repository verfügen, das ein Projekt enthält, können Sie es klonen, um damit zu arbeiten.", "already-exists": "Das Projekt ist bereits vorhanden", - "must-contain": "Darf nur A-Z 0-9 _ enthalten", + "must-contain": "Darf nur A-Z 0-9 _ - enthalten", "project-name": "Projektname", "no-info-in-url": "Geben Sie Benutzername & Passwort nicht innerhalb der URL vor", "git-url": "Git-Repository-URL", @@ -1026,7 +1072,7 @@ "desc2": "Im Tab 'Commit-Historie' in der Seitenleiste werden alle Dateien angezeigt, die sich in Ihrem Projekt geändert haben, und um sie ins lokale Repository zu übertragen (commit). Es zeigt Ihnen eine vollständige Historie Ihrer Commits an und ermöglicht es Ihnen, Ihre Commits in ein (remote) Server-Repository zu schieben (push)." }, "create": { - "projects": "Projekt", + "projects": "Projekte", "already-exists": "Das Projekt ist bereits vorhanden", "must-contain": "Darf nur A-Z 0-9 _ enthalten", "no-info-in-url": "Geben Sie Benutzername & Passwort nicht innerhalb der URL vor", @@ -1059,7 +1105,8 @@ "not-git": "Kein Git-Repository", "no-resource": "Repository nicht gefunden", "cant-get-ssh-key-path": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden.", - "unexpected_error": "unerwarteter_Fehler" + "unexpected_error": "unerwarteter_Fehler", + "clearContext": "Kontextdaten löscshen beim Projektwechsel" }, "delete": { "confirm": "Sind Sie sicher, dass dieses Projekt gelöscht werden soll?" @@ -1106,13 +1153,37 @@ "preview": "Vorschau", "defaultValue": "Standardwert" }, + "tourGuide": { + "takeATour": "Tour starten", + "start": "Start", + "next": "Nächste" + }, + "diagnostics": { + "title": "System-Informationen" + }, "languages": { - "de": "German", - "en-US": "English", - "ja": "Japanese", - "ko": "Korean", - "ru": "Russian", - "zh-CN": "Chinese(Simplified)", - "zh-TW": "Chinese(Traditional)" + "de": "Deutsch", + "en-US": "Englisch", + "ja": "Japanisch", + "ko": "Koreanisch", + "ru": "Russisch", + "zh-CN": "Chinesisch (Vereinfacht)", + "zh-TW": "Chinesisch (Traditionell)" + }, + "validator": { + "errors": { + "invalid-json": "Ungültige JSON-Daten: __error__", + "invalid-json-prop": "__prop__: ungültige JSON-Daten: __error__", + "invalid-prop": "Ungültiger Eigenschaftsausdruck", + "invalid-prop-prop": "__prop__: ungültiger Eigenschaftsausdruck", + "invalid-num": "Ungültige Nummer", + "invalid-num-prop": "__prop__: ungültige Nummer", + "invalid-regexp": "Ungültiges Eingabemuster", + "invalid-regex-prop": "__prop__: ungültiges Eingabemuster", + "missing-required-prop": "__prop__: Eigenschaftswert fehlt", + "invalid-config": "__prop__: ungültige Konfigurations-Node", + "missing-config": "__prop__: Konfigurations-Node fehlt", + "validation-error": "__prop__: Validierungsfehler: __node__, __id__: __error__" + } } } From 3a2fa4073a4275f5b18a134c685ec82bf7e2423a Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 18 Jun 2022 16:05:25 -0400 Subject: [PATCH 101/195] Move all colours to CSS variables --- .../@node-red/editor-client/src/sass/ace.scss | 34 +-- .../editor-client/src/sass/base.scss | 44 ++-- .../editor-client/src/sass/colors.scss | 4 + .../editor-client/src/sass/debug.scss | 68 +++--- .../editor-client/src/sass/diff.scss | 188 +++++++-------- .../editor-client/src/sass/dragdrop.scss | 4 +- .../editor-client/src/sass/dropdownMenu.scss | 46 ++-- .../editor-client/src/sass/editor.scss | 178 +++++++------- .../editor-client/src/sass/flow.scss | 136 +++++------ .../editor-client/src/sass/forms.scss | 50 ++-- .../editor-client/src/sass/header.scss | 68 +++--- .../editor-client/src/sass/jquery.scss | 62 ++--- .../editor-client/src/sass/keyboard.scss | 22 +- .../editor-client/src/sass/library.scss | 36 +-- .../editor-client/src/sass/mixins.scss | 56 ++--- .../editor-client/src/sass/notifications.scss | 14 +- .../src/sass/palette-editor.scss | 54 ++--- .../editor-client/src/sass/palette.scss | 46 ++-- .../editor-client/src/sass/panels.scss | 18 +- .../editor-client/src/sass/popover.scss | 24 +- .../editor-client/src/sass/projects.scss | 206 ++++++++-------- .../editor-client/src/sass/radialMenu.scss | 20 +- .../editor-client/src/sass/search.scss | 64 ++--- .../editor-client/src/sass/sidebar.scss | 20 +- .../src/sass/style-custom-theme.scss | 18 ++ .../editor-client/src/sass/tab-config.scss | 32 +-- .../editor-client/src/sass/tab-context.scss | 4 +- .../editor-client/src/sass/tab-help.scss | 2 +- .../editor-client/src/sass/tab-info.scss | 88 +++---- .../editor-client/src/sass/tabs.scss | 96 ++++---- .../editor-client/src/sass/tourGuide.scss | 2 +- .../src/sass/ui/common/checkboxSet.scss | 4 +- .../src/sass/ui/common/editableList.scss | 16 +- .../src/sass/ui/common/nodeList.scss | 10 +- .../src/sass/ui/common/searchBox.scss | 14 +- .../src/sass/ui/common/stack.scss | 4 +- .../src/sass/ui/common/treeList.scss | 30 +-- .../src/sass/ui/common/typedInput.scss | 50 ++-- .../editor-client/src/sass/userSettings.scss | 4 +- .../editor-client/src/sass/variables.scss | 221 +++++++++++++++++- .../editor-client/src/sass/workspace.scss | 28 +-- .../src/sass/workspaceToolbar.scss | 16 +- scripts/build-custom-theme.js | 2 +- 43 files changed, 1162 insertions(+), 941 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss index 4e913942e..5aad96b4d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss @@ -2,48 +2,48 @@ .ace_read-only { .ace_scroller { - background: $text-editor-background-disabled; - color: $text-editor-color-disabled; + background: var(--red-ui-text-editor-background-disabled); + color: var(--red-ui-text-editor-color-disabled); } .ace_cursor { color: transparent !important; } } .ace_gutter { - background: $text-editor-gutter-background; + background: var(--red-ui-text-editor-gutter-background); border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .ace_scroller { - background: $text-editor-background; + background: var(--red-ui-text-editor-background); border-top-right-radius: 4px; border-bottom-right-radius: 4px; - color: $text-editor-color; + color: var(--red-ui-text-editor-color); } .ace_marker-layer .ace_active-line { - background: $text-editor-active-line-background; + background: var(--red-ui-text-editor-active-line-background); } .ace_marker-layer .ace_selection { - background: $text-editor-selection-background; + background: var(--red-ui-text-editor-selection-background); border-radius: 1px; } .ace_gutter-cell { - color: $text-editor-gutter-color; + color: var(--red-ui-text-editor-gutter-color); } .ace_gutter-active-line { - background: $text-editor-gutter-active-line-background; + background: var(--red-ui-text-editor-gutter-active-line-background); } .ace_tooltip { - font-family: $primary-font; + font-family: var(--red-ui-primary-font); line-height: 1.4em; max-width: 400px; white-space: normal; background-image: none; - background: $popover-background; - color: $popover-color; + background: var(--red-ui-popover-background); + color: var(--red-ui-popover-color); border-radius: 4px; @include component-shadow; - border-color: $popover-background; + border-color: var(--red-ui-popover-background); } .ace_content { line-height: 1; @@ -55,14 +55,14 @@ #red-ui-event-log-editor { .ace_scroller { - background: $event-log-background; - color: $event-log-color; + background: var(--red-ui-event-log-background); + color: var(--red-ui-event-log-color); } .ace_marker-layer .ace_active-line { - background: $event-log-active-line-background; + background: var(--red-ui-event-log-active-line-background); } .ace_marker-layer .ace_selection { - background: $event-log-selection-background; + background: var(--red-ui-event-log-selection-background); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/base.scss b/packages/node_modules/@node-red/editor-client/src/sass/base.scss index a08e752d4..92a98913f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/base.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/base.scss @@ -20,12 +20,12 @@ body { } .red-ui-editor { - font-size: $primary-font-size; - font-family: $primary-font; + font-size: var(--red-ui-primary-font-size); + font-family: var(--red-ui-primary-font); padding: 0; margin: 0; - background: $primary-background; - color: $primary-text-color; + background: var(--red-ui-primary-background); + color: var(--red-ui-primary-text-color); line-height: 20px; } @@ -63,15 +63,15 @@ body { .red-ui-icon-picker { a { text-decoration: none; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } a:hover, a:focus { text-decoration: none; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } a:focus { - outline: 1px solid $form-input-focus-color; + outline: 1px solid var(--red-ui-form-input-focus-color); } p { @@ -130,7 +130,7 @@ body { hr { margin: 20px 0; border: 0; - border-top: 1px solid $tertiary-border-color; + border-top: 1px solid var(--red-ui-tertiary-border-color); } @@ -150,22 +150,22 @@ body { mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $spinner-color; + background-color: var(--red-ui-spinner-color); } .red-ui-font-code { - font-family: $monospace-font; - font-size: $primary-font-size; - color: $text-color-code; + font-family: var(--red-ui-monospace-font); + font-size: var(--red-ui-primary-font-size); + color: var(--red-ui-text-color-code); white-space: nowrap; } code { - font-family: $monospace-font; - font-size: $primary-font-size; + font-family: var(--red-ui-monospace-font); + font-size: var(--red-ui-primary-font-size); padding: 0px; margin: 1px; - color: $text-color-code; + color: var(--red-ui-text-color-code); white-space: nowrap; } @@ -177,8 +177,8 @@ body { word-break: break-all; word-wrap: break-word; white-space: pre-wrap; - background-color:$tertiary-background; - border: 1px solid $tertiary-border-color; + background-color:var(--red-ui-tertiary-background); + border: 1px solid var(--red-ui-tertiary-border-color); border-radius: 2px; } @@ -217,8 +217,8 @@ body { blockquote { padding: 0 0 0 15px; margin: 0 0 20px; - border-left: 4px solid $secondary-border-color; - color: $secondary-text-color; + border-left: 4px solid var(--red-ui-secondary-border-color); + color: var(--red-ui-secondary-text-color); p { font-size: 14px; @@ -244,7 +244,7 @@ body { right: 1px; text-align: center; padding: 40px; - background: $secondary-background; + background: var(--red-ui-secondary-background); &:before { content: ''; display: inline-block; @@ -258,14 +258,14 @@ body { width: 80px; } &.red-ui-component-spinner-sidebar { - background: $secondary-background; + background: var(--red-ui-secondary-background); padding:0; img { width: 40px; } } &.projects-version-control-spinner-sidebar { - background: $secondary-background; + background: var(--red-ui-secondary-background); padding:0; img { width: 20px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index d8b4fb175..66b3bd48e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -112,9 +112,13 @@ $tab-text-color-disabled-inactive: $secondary-text-color-disabled-inactive; $tab-badge-color: $tertiary-text-color; $tab-background: $secondary-background; $tab-background-active: $secondary-background; +$tab-background-active-alpha: rgba($secondary-background, 0.001); $tab-background-selected: $secondary-background-selected; +$tab-background-selected-alpha: rgba($secondary-background-selected, 0.001); $tab-background-inactive: $secondary-background-inactive; +$tab-background-inactive-alpha: rgba($secondary-background-inactive, 0.001); $tab-background-hover: $secondary-background-hover; +$tab-background-hover-alpha: rgba($secondary-background-hover, 0.001); $palette-header-background: $primary-background; $palette-header-color: $header-text-color; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index 0c810c03f..58099877f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -17,7 +17,7 @@ .red-ui-debug-window { padding:0; margin:0; - background: $secondary-background; + background: var(--red-ui-secondary-background); line-height: 20px; .red-ui-debug-msg-payload { font-size: 14px; @@ -38,15 +38,15 @@ left: 0px; right: 0px; z-index: 20; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 10px; - border-bottom: 1px solid $secondary-border-color; - box-shadow: 0 2px 6px $shadow; + border-bottom: 1px solid var(--red-ui-secondary-border-color); + box-shadow: 0 2px 6px var(--red-ui-shadow); } #red-ui-sidebar-debug-filter-node-list-row { .red-ui-treeList-label.disabled { font-style: italic; - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } .red-ui-treeList-label { @@ -57,22 +57,22 @@ background: inherit; } &.focus, &.focus .red-ui-treeList-sublabel-text { - background: $list-item-background-hover !important; + background: var(--red-ui-list-item-background-hover) !important; } } } .red-ui-debug-msg { position: relative; - border-bottom: 1px solid $debug-message-border; - border-left: 8px solid $debug-message-border; - border-right: 8px solid $debug-message-border; + border-bottom: 1px solid var(--red-ui-debug-message-border); + border-left: 8px solid var(--red-ui-debug-message-border); + border-right: 8px solid var(--red-ui-debug-message-border); padding: 2px; &>.red-ui-debug-msg-meta .red-ui-debug-msg-tools { display: none; } &.red-ui-debug-msg-hover { - border-right-color: $debug-message-border-hover; + border-right-color: var(--red-ui-debug-message-border-hover); &>.red-ui-debug-msg-meta .red-ui-debug-msg-tools { display: inline-block; } @@ -86,7 +86,7 @@ display: inline-block; } &:hover { - background: $debug-message-background-hover; + background: var(--red-ui-debug-message-background-hover); &>.red-ui-debug-msg-tools { .red-ui-debug-msg-tools-copy { display: inline-block; @@ -120,9 +120,9 @@ } .red-ui-debug-msg-meta { - background: $debug-message-background; + background: var(--red-ui-debug-message-background); font-size: 11px; - color: $secondary-text-color-inactive; + color: var(--red-ui-secondary-text-color-inactive); overflow-wrap: anywhere; } .red-ui-debug-msg-date { @@ -131,11 +131,11 @@ } .red-ui-debug-msg-topic { display: block; - color: $debug-message-text-color-meta; + color: var(--red-ui-debug-message-text-color-meta); } .red-ui-debug-msg-name { padding: 1px 0px; - color: $secondary-text-color-inactive; + color: var(--red-ui-secondary-text-color-inactive); white-space: nowrap; } .red-ui-debug-msg-tools { @@ -152,39 +152,39 @@ .red-ui-debug-msg-payload { display: block; padding: 2px; - background: $debug-message-background; - font-family: $monospace-font; + background: var(--red-ui-debug-message-background); + font-family: var(--red-ui-monospace-font); font-size: 13px !important; } .red-ui-debug-msg-level-log { - border-left-color: $debug-message-border; - border-right-color: $debug-message-border; + border-left-color: var(--red-ui-debug-message-border); + border-right-color: var(--red-ui-debug-message-border); } .red-ui-debug-msg-level-30 { - border-left-color: $debug-message-border-warning; - border-right-color: $debug-message-border-warning; + border-left-color: var(--red-ui-debug-message-border-warning); + border-right-color: var(--red-ui-debug-message-border-warning); } .red-ui-debug-msg-level-20 { - border-left-color: $debug-message-border-error; - border-right-color: $debug-message-border-error; + border-left-color: var(--red-ui-debug-message-border-error); + border-right-color: var(--red-ui-debug-message-border-error); } .red-ui-debug-msg-object-entry { position: relative; padding-left: 15px; } .red-ui-debug-msg-element { - color: $debug-message-text-color; + color: var(--red-ui-debug-message-text-color); line-height: 1.3em; overflow-wrap: break-word; } .red-ui-debug-msg-object-key { - color: $debug-message-text-color-object-key; + color: var(--red-ui-debug-message-text-color-object-key); } .red-ui-debug-msg-object-value { } .red-ui-debug-msg-object-handle { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 1em; width: 1em; text-align: center; @@ -219,17 +219,17 @@ display:none; } .red-ui-debug-msg-object-entry pre { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); font-size: 13px; line-height: 1.2em; margin: 0 0 0 -1em; } -.red-ui-debug-msg-type-other { color: $debug-message-text-color-msg-type-other; } -.red-ui-debug-msg-type-string { color: $debug-message-text-color-msg-type-string; } -.red-ui-debug-msg-type-null { color: $debug-message-text-color-msg-type-null; font-style: italic;} -.red-ui-debug-msg-type-meta { color: $debug-message-text-color-msg-type-meta; font-style: italic;} -.red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; }; +.red-ui-debug-msg-type-other { color: var(--red-ui-debug-message-text-color-msg-type-other); } +.red-ui-debug-msg-type-string { color: var(--red-ui-debug-message-text-color-msg-type-string); } +.red-ui-debug-msg-type-null { color: var(--red-ui-debug-message-text-color-msg-type-null); font-style: italic;} +.red-ui-debug-msg-type-meta { color: var(--red-ui-debug-message-text-color-msg-type-meta); font-style: italic;} +.red-ui-debug-msg-type-number { color: var(--red-ui-debug-message-text-color-msg-type-number); } .red-ui-debug-msg-type-number-toggle { cursor: pointer;} .red-ui-debug-msg-type-string { @@ -241,14 +241,14 @@ padding: 4px 2px 2px; position: relative; &.red-ui-debug-msg-row-pinned { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } .red-ui-debug-msg-expandable { cursor: pointer; } .red-ui-debug-msg-expandable:hover .red-ui-debug-msg-object-handle { - color:$secondary-text-color-hover; + color:var(--red-ui-secondary-text-color-hover); } .red-ui-debug-msg-buffer-opts { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/diff.scss b/packages/node_modules/@node-red/editor-client/src/sass/diff.scss index 38fd36252..a7b3d4dc8 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/diff.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/diff.scss @@ -23,11 +23,11 @@ .red-ui-editableList-container { border-radius:1px; padding:0; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } .red-ui-diff-list { li { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 0px; border: none; min-height: 0; @@ -62,29 +62,29 @@ white-space: nowrap; text-overflow: ellipsis; width: 50%; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); text-align: center; - border-top: 1px solid $secondary-border-color; - border-color:$secondary-border-color; - border-left: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-color:var(--red-ui-secondary-border-color); + border-left: 1px solid var(--red-ui-secondary-border-color); } div:last-child { - border-right: 1px solid $secondary-border-color; + border-right: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-diff-dialog-toolbar { box-sizing: border-box; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); text-align: right; padding: 8px 10px; - background: $primary-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-primary-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; } .red-ui-diff-list-flow { - background: $secondary-background; - border: 1px solid $secondary-border-color; + background: var(--red-ui-secondary-background); + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 1px; overflow: hidden; @@ -114,10 +114,10 @@ font-size: 0.9em; &:first-child { - border-top: 1px solid $tertiary-border-color; + border-top: 1px solid var(--red-ui-tertiary-border-color); } &:not(:last-child) { - border-bottom: 1px solid $tertiary-border-color; + border-bottom: 1px solid var(--red-ui-tertiary-border-color); } &.collapsed { @@ -150,8 +150,8 @@ width: 100%; } td, th { - border-top: 1px solid $secondary-border-color; - border-left: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-left: 1px solid var(--red-ui-secondary-border-color); &:first-child { border-left: none; } @@ -166,7 +166,7 @@ overflow:hidden; } &:hover { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } @@ -212,7 +212,7 @@ cursor: pointer; padding: 0; &:hover { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } .red-ui-diff-list-flow-title-meta { @@ -223,7 +223,7 @@ .red-ui-diff-list-node-header { cursor: pointer; &:hover { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } .red-ui-diff-list-node-icon { @@ -232,9 +232,9 @@ margin: 5px; width: 18px; height: 15px; - background: $form-input-background; + background: var(--red-ui-form-input-background); border-radius: 2px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; background-size: contain; @@ -267,7 +267,7 @@ .red-ui-diff-status-deleted { cursor: default !important; .red-ui-diff-status { - color: $diff-state-deleted; + color: var(--red-ui-diff-state-deleted); } .red-ui-diff-list-node-node { opacity: 0.5; @@ -280,28 +280,28 @@ .red-ui-diff-status-added { cursor: default !important; .red-ui-diff-status { - color: $diff-state-added; + color: var(--red-ui-diff-state-added); } } .red-ui-diff-status-moved { .red-ui-diff-status { - color: $diff-state-moved; + color: var(--red-ui-diff-state-moved); } } .red-ui-diff-status-changed { .red-ui-diff-status { - color: $diff-state-changed; + color: var(--red-ui-diff-state-changed); } } .red-ui-diff-status-unchanged { .red-ui-diff-status { - color: $diff-state-unchanged; + color: var(--red-ui-diff-state-unchanged); } } .red-ui-diff-status-conflict { .red-ui-diff-status { - color: $diff-state-conflict; + color: var(--red-ui-diff-state-conflict); } } .red-ui-diff-list-node-title { @@ -312,7 +312,7 @@ } .red-ui-diff-list-node-properties { margin: 0; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-diff-status { display: inline-block; @@ -329,7 +329,7 @@ } .red-ui-diff-list-node-description { - color: $form-text-color; + color: var(--red-ui-form-text-color); margin-right: 5px; padding-top: 5px; display: inline-block; @@ -340,11 +340,11 @@ } } -.red-ui-diff-state-added { color: $diff-state-added; } -.red-ui-diff-state-deleted { color: $diff-state-deleted; } -.red-ui-diff-state-changed { color: $diff-state-changed; } -.red-ui-diff-state-unchanged { color: $diff-state-unchanged; } -.red-ui-diff-state-conflicted { color: $diff-state-conflicted; } +.red-ui-diff-state-added { color: var(--red-ui-diff-state-added); } +.red-ui-diff-state-deleted { color: var(--red-ui-diff-state-deleted); } +.red-ui-diff-state-changed { color: var(--red-ui-diff-state-changed); } +.red-ui-diff-state-unchanged { color: var(--red-ui-diff-state-unchanged); } +.red-ui-diff-state-conflicted { color: var(--red-ui-diff-state-conflicted); } .red-ui-diff-list-node-cell { @@ -353,19 +353,19 @@ box-sizing: border-box; width: calc( (100% - 20px) / 2); height: 32px; - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); padding-top: 2px; white-space: nowrap; overflow: hidden; position: relative; } .red-ui-diff-empty { - background: $secondary-background-disabled; + background: var(--red-ui-secondary-background-disabled); background: repeating-linear-gradient( 20deg, - $secondary-background, $secondary-background 5px, - $secondary-background-disabled 5px, - $secondary-background-disabled 10px + var(--red-ui-secondary-background), var(--red-ui-secondary-background) 5px, + var(--red-ui-secondary-background-disabled) 5px, + var(--red-ui-secondary-background-disabled) 10px ); } .red-ui-diff-list-node-cell:first-child { @@ -425,10 +425,10 @@ background: none; } &.red-ui-diff-status-changed { - background: $diff-state-changed-background; + background: var(--red-ui-diff-state-changed-background); } &.red-ui-diff-status-conflict { - background: $diff-state-conflict-background; + background: var(--red-ui-diff-state-conflict-background); } } @@ -439,42 +439,42 @@ label.red-ui-diff-selectbox { bottom:0; width: 35px; text-align: center; - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); margin:0; input[type="radio"] { margin-top: 8px; } &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } } .red-ui-diff-list-node-conflict.red-ui-diff-select-remote { .red-ui-diff-list-node-remote { - background: $diff-state-added-background; + background: var(--red-ui-diff-state-added-background); label { - border-left-color: $diff-state-added-border; + border-left-color: var(--red-ui-diff-state-added-border); } } .red-ui-diff-list-node-local { - background: $diff-state-deleted-background; + background: var(--red-ui-diff-state-deleted-background); label { - border-left-color: $diff-state-deleted-border; + border-left-color: var(--red-ui-diff-state-deleted-border); } } } .red-ui-diff-list-node-conflict.red-ui-diff-select-local { .red-ui-diff-list-node-local { - background: $diff-state-added-background; + background: var(--red-ui-diff-state-added-background); label { - border-left-color: $diff-state-added-border; + border-left-color: var(--red-ui-diff-state-added-border); } } .red-ui-diff-list-node-remote { - background: $diff-state-deleted-background; + background: var(--red-ui-diff-state-deleted-background); label { - border-left-color: $diff-state-deleted-border; + border-left-color: var(--red-ui-diff-state-deleted-border); } } } @@ -500,10 +500,10 @@ ul.red-ui-deploy-dialog-confirm-list { width: 30px; margin-right: 10px; &.fa-check { - color: $text-color-success; + color: var(--red-ui-text-color-success); } &.fa-exclamation { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } div { @@ -529,7 +529,7 @@ ul.red-ui-deploy-dialog-confirm-list { table.red-ui-diff-text-content { margin: 10px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 3px; table-layout: fixed; width: calc(100% - 20px); @@ -538,88 +538,88 @@ ul.red-ui-deploy-dialog-confirm-list { word-wrap: break-word; } td.lineno { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); text-align: right; - color: $tertiary-text-color; - background: $tertiary-background; + color: var(--red-ui-tertiary-text-color); + background: var(--red-ui-tertiary-background); padding: 1px 5px; &.added { - background: $diff-state-added-header-background; + background: var(--red-ui-diff-state-added-header-background); } &.removed { - background: $diff-state-deleted-header-background; + background: var(--red-ui-diff-state-deleted-header-background); } } td.lineno:nth-child(3) { - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); } td.linetext { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); white-space: pre-wrap; padding: 1px 5px; - border-left: 1px solid $tertiary-border-color; + border-left: 1px solid var(--red-ui-tertiary-border-color); span.prefix { width: 30px; display: inline-block; text-align: center; - color: $diff-state-prefix-color; + color: var(--red-ui-diff-state-prefix-color); } &.added { - border-left-color: $diff-state-added-header-border; + border-left-color: var(--red-ui-diff-state-added-header-border); } &.removed { - border-left-color: $diff-state-deleted-header-border; + border-left-color: var(--red-ui-diff-state-deleted-header-border); } } td.blank { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } td.added { - background: $diff-state-added-background; - color: $diff-state-color; + background: var(--red-ui-diff-state-added-background); + color: var(--red-ui-diff-state-color); } td.removed { - background: $diff-state-deleted-background; - color: $diff-state-color; + background: var(--red-ui-diff-state-deleted-background); + color: var(--red-ui-diff-state-color); } tr.mergeHeader td { - color: $diff-merge-header-color; - background: $diff-merge-header-background; + color: var(--red-ui-diff-merge-header-color); + background: var(--red-ui-diff-merge-header-background); height: 26px; vertical-align: middle; } tr.mergeHeader-separator td { - color: $diff-merge-header-color; - background: $diff-merge-header-border-color; + color: var(--red-ui-diff-merge-header-color); + background: var(--red-ui-diff-merge-header-border-color); height: 0px; } tr.mergeHeader-ours td { - border-top: 2px solid $diff-merge-header-border-color; + border-top: 2px solid var(--red-ui-diff-merge-header-border-color); } tr.mergeHeader-theirs td { - border-bottom: 2px solid $diff-merge-header-border-color; + border-bottom: 2px solid var(--red-ui-diff-merge-header-border-color); } td.unchanged { - background: $diff-state-unchanged-background; - color: $diff-state-unchanged; + background: var(--red-ui-diff-state-unchanged-background); + color: var(--red-ui-diff-state-unchanged); } tr.unchanged { - background: $diff-state-unchanged-background; + background: var(--red-ui-diff-state-unchanged-background); } tr.start-block { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } tr.end-block { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } tr.red-ui-diff-text-file-header td { .filename { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); } - background: $primary-background; + background: var(--red-ui-primary-background); padding: 5px 10px 5px 0; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); cursor: pointer; i.red-ui-diff-list-chevron { width: 30px; @@ -631,17 +631,17 @@ ul.red-ui-deploy-dialog-confirm-list { } } tr.red-ui-diff-text-commit-header td { - background: $primary-background; + background: var(--red-ui-primary-background); padding: 5px 10px; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); h3 { font-size: 1.4em; margin: 0; } .commit-summary { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); padding-top: 5px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .commit-body { margin-bottom:15px; @@ -651,20 +651,20 @@ ul.red-ui-deploy-dialog-confirm-list { } tr.red-ui-diff-text-header > td:not(.red-ui-diff-flow-diff) { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); padding: 5px 10px; text-align: left; - color: $diff-text-header-color; - background: $diff-text-header-background; + color: var(--red-ui-diff-text-header-color); + background: var(--red-ui-diff-text-header-background); height: 30px; vertical-align: middle; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); } tr.red-ui-diff-text-expand td { cursor: pointer; &:hover { - background: $diff-text-header-background; + background: var(--red-ui-diff-text-header-background); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss b/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss index 1476cf890..78646e0e7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss @@ -18,7 +18,7 @@ position: absolute; top: 0; bottom: 0; left: 0; right: 0; - background: $dnd-background; + background: var(--red-ui-dnd-background); display:table; width: 100%; height: 100%; @@ -30,7 +30,7 @@ vertical-align: middle; text-align: center; font-size: 40px; - color: $dnd-color; + color: var(--red-ui-dnd-color); i { pointer-events: none; font-size: 80px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 4cfc1884e..4323e9342 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -15,8 +15,8 @@ **/ .red-ui-menu-dropdown { - font-family: $primary-font; - font-size: $primary-font-size; + font-family: var(--red-ui-primary-font); + font-size: var(--red-ui-primary-font-size); position: absolute; top: 100%; width: 230px; @@ -28,9 +28,9 @@ margin-left: 0px !important; padding: 5px 0; list-style: none; - background: $menuBackground; - border: 1px solid $secondary-border-color; - box-shadow: 0 5px 10px $shadow; + background: var(--red-ui-menuBackground); + border: 1px solid var(--red-ui-secondary-border-color); + box-shadow: 0 5px 10px var(--red-ui-shadow); &.pull-right { right: 0; @@ -41,7 +41,7 @@ height: 1px; margin: 9px 1px; overflow: hidden; - background-color: $menuDivider; + background-color: var(--red-ui-menuDivider); } & > li > a, & > li > a:focus { @@ -50,7 +50,7 @@ clear: both; font-weight: normal; line-height: 20px; - color: $menuColor; + color: var(--red-ui-menuColor); white-space: normal !important; outline: none; } @@ -73,19 +73,19 @@ & > .active > a, & > .active > a:hover, & > .active > a:focus { - color: $menuActiveColor; + color: var(--red-ui-menuActiveColor); text-decoration: none; - background-color: $menuActiveBackground; + background-color: var(--red-ui-menuActiveBackground); outline: 0; } & > .disabled > a, & > .disabled > a:hover, & > .disabled > a:focus { - color: $menuDisabledColor; + color: var(--red-ui-menuDisabledColor); .red-ui-popover-key { - color: $menuDisabledColor; - border-color: $menuDisabledColor; + color: var(--red-ui-menuDisabledColor); + border-color: var(--red-ui-menuDisabledColor); } } @@ -133,8 +133,8 @@ padding: 0; font-size: 13px; // float: right; - color: $menuColor; - border-color: $menuColor; + color: var(--red-ui-menuColor); + border-color: var(--red-ui-menuColor); } } @@ -151,9 +151,9 @@ .red-ui-menu-dropdown > li > a:focus, .red-ui-menu-dropdown-submenu:hover > a, .red-ui-menu-dropdown-submenu:focus > a { - color: $menuHoverColor; + color: var(--red-ui-menuHoverColor); text-decoration: none; - background-color: $menuHoverBackground; + background-color: var(--red-ui-menuHoverBackground); } .red-ui-menu-dropdown-submenu { @@ -176,7 +176,7 @@ margin-top: 5px; margin-right: -10px; border-color: transparent; - border-left-color: $menuCaret; + border-left-color: var(--red-ui-menuCaret); border-style: solid; border-width: 5px 0 5px 5px; content: " "; @@ -202,7 +202,7 @@ margin-left: -30px; /* Caret Arrow */ border-color: transparent; - border-right-color: $menuCaret; + border-right-color: var(--red-ui-menuCaret); border-style: solid; border-width: 5px 5px 5px 0; content: " "; @@ -227,13 +227,13 @@ } } .red-ui-menu-dropdown-submenu.disabled > a:before { - border-right-color: $menuCaret; + border-right-color: var(--red-ui-menuCaret); } // Menu NG ul.red-ui-menu:not(.red-ui-menu-dropdown) { - font-family: $primary-font; + font-family: var(--red-ui-primary-font); font-size: 12px; list-style-type: none; padding: 0; @@ -244,14 +244,14 @@ ul.red-ui-menu:not(.red-ui-menu-dropdown) { clear: both; font-weight: normal; line-height: 20px; - color: $menuColor; + color: var(--red-ui-menuColor); white-space: nowrap; text-decoration: none; &:hover,&:focus { - color: $menuHoverColor; + color: var(--red-ui-menuHoverColor); text-decoration: none; - background-color: $menuHoverBackground; + background-color: var(--red-ui-menuHoverBackground); border: none; outline: none; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 75133df8a..1730b9e35 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -32,9 +32,9 @@ width: auto; right: -1000px; bottom: 0; - background: $secondary-background; - border-left: 1px solid $secondary-border-color; - border-bottom: 1px solid $primary-border-color; + background: var(--red-ui-secondary-background); + border-left: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-primary-border-color); box-sizing: content-box; } .red-ui-tray.open { @@ -67,8 +67,8 @@ position: relative; box-sizing: border-box; font-weight: bold; - border-bottom: 1px solid $secondary-border-color; - background: $palette-header-background; + border-bottom: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-palette-header-background); &:after { content: ""; display: table; @@ -88,8 +88,8 @@ height: 26px; line-height: 26px; &.toggle:not(.selected) { - color: $workspace-button-color-selected !important; - background: $workspace-button-background-active; + color: var(--red-ui-workspace-button-color-selected) !important; + background: var(--red-ui-workspace-button-background-active); } } @@ -116,8 +116,8 @@ } .red-ui-tray-titlebar { - color: $header-text-color; - border-bottom: 1px solid $secondary-border-color; + color: var(--red-ui-header-text-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); padding: 8px; } .red-ui-editor ul.red-ui-tray-breadcrumbs { @@ -131,7 +131,7 @@ margin:0; &:not(:last-child) { - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); font-weight: normal; &:after { @@ -149,10 +149,10 @@ bottom: 0px; width: 7px; left: -9px; - background-color: $primary-background; + background-color: var(--red-ui-primary-background); cursor: col-resize; - border-left: 1px solid $primary-border-color; - box-shadow: -1px 0 6px $shadow; + border-left: 1px solid var(--red-ui-primary-border-color); + box-shadow: -1px 0 6px var(--red-ui-shadow); &:before { content: ''; @@ -167,11 +167,11 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } &.red-ui-tray-resize-maximised { - background: $primary-background; + background: var(--red-ui-primary-background); cursor: default; } } @@ -182,10 +182,10 @@ button.red-ui-tray-resize-button { height: 37px; line-height: 35px; border: none; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); margin: 0; - background: $primary-background; - color: $workspace-button-color; + background: var(--red-ui-primary-background); + color: var(--red-ui-workspace-button-color); } .red-ui-editor .red-ui-tray { @@ -203,16 +203,16 @@ button.red-ui-tray-resize-button { } .input-error { - border-color: $form-input-border-error-color !important; + border-color: var(--red-ui-form-input-border-error-color) !important; } .input-updated { - border-color: $node-selected-color !important; + border-color: var(--red-ui-node-selected-color) !important; } .form-row { clear: both; - color: $form-text-color; + color: var(--red-ui-form-text-color); margin-bottom:12px; } .form-row label { @@ -223,10 +223,10 @@ button.red-ui-tray-resize-button { width:70%; } .form-tips { - background: $form-tips-background; + background: var(--red-ui-form-tips-background); padding: 8px; border-radius: 2px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); max-width: 450px; } .form-tips code { @@ -238,7 +238,7 @@ button.red-ui-tray-resize-button { } .form-warning { - border-color: $text-color-error; + border-color: var(--red-ui-text-color-error); } } @@ -255,11 +255,11 @@ button.red-ui-tray-resize-button { } } .red-ui-editor-text-container { - border:1px solid $tertiary-border-color; + border:1px solid var(--red-ui-tertiary-border-color); border-radius:5px; overflow: hidden; - font-size: $primary-font-size !important; - font-family: $monospace-font !important; + font-size: var(--red-ui-primary-font-size !important); + font-family: var(--red-ui-monospace-font !important); height: 100%; &.red-ui-editor-text-container-toolbar { @@ -302,7 +302,7 @@ button.red-ui-button-small #red-ui-editor-config-scope-warning { display: inline-block; margin-right: 5px; - color: $text-color-warning; + color: var(--red-ui-text-color-warning); vertical-align: middle; } #red-ui-editor-config-scope { @@ -358,18 +358,18 @@ button.red-ui-button-small padding: 20px 20px 10px; &:last-child { padding-top: 60px; - background: $primary-background; + background: var(--red-ui-primary-background); } } } .red-ui-editor-type-markdown-panel-preview { padding: 10px; - border:1px solid $secondary-border-color; + border:1px solid var(--red-ui-secondary-border-color); border-radius:5px; height: calc(100% - 21px); overflow-y: scroll; - background: $secondary-background; + background: var(--red-ui-secondary-background); } #red-ui-clipboard-hidden { @@ -402,7 +402,7 @@ button.red-ui-button-small span { padding-left: 50px; width: 100px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } @@ -427,14 +427,14 @@ button.red-ui-button.red-ui-editor-node-appearance-button { .red-ui-group-layout-picker { padding: 5px; - background: $secondary-background; + background: var(--red-ui-secondary-background); } .red-ui-group-layout-picker-cell-text { position: absolute; width: 14px; height: 2px; - border-top: 2px solid $secondary-text-color; - border-bottom: 2px solid $secondary-text-color; + border-top: 2px solid var(--red-ui-secondary-text-color); + border-bottom: 2px solid var(--red-ui-secondary-text-color); margin: 2px; &.red-ui-group-layout-text-pos-nw { top: 0; left: 0; } @@ -451,7 +451,7 @@ button.red-ui-button.red-ui-editor-node-appearance-button { background-color: #FFF; background-size: 100% 100%; background-position: 0 0, 50% 50%; - background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent); + background-image: linear-gradient(45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent); border: none; } } @@ -475,17 +475,17 @@ button.red-ui-group-layout-picker-none { width: 100%; margin-bottom: 0; border: none; - border-bottom: 1px solid $form-input-border-color; + border-bottom: 1px solid var(--red-ui-form-input-border-color); } small { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); margin-left: 5px; margin-right: 4px; display: inline-block; min-width: 35px; text-align: right; } - background: $primary-background; + background: var(--red-ui-primary-background); } .red-ui-editor-node-appearance-button { .red-ui-search-result-node { @@ -496,7 +496,7 @@ button.red-ui-group-layout-picker-none { padding: 0; border-style: solid; border-width: 1px; - border-color: $secondary-border-color; + border-color: var(--red-ui-secondary-border-color); } .red-ui-color-picker-swatch { position: absolute; @@ -509,7 +509,7 @@ button.red-ui-group-layout-picker-none { background-color: #FFF; background-size: 100% 100%; background-position: 0 0, 50% 50%; - background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent) + background-image: linear-gradient(45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent) } .red-ui-search-result-node .red-ui-color-picker-cell-none { border-radius: 4px; @@ -536,7 +536,7 @@ button.red-ui-group-layout-picker-none { top:0;right:0;left:0;bottom:0; background-image:linear-gradient(90deg, transparent 0%, #f00 100%); background-size: 100% 100%; - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); } div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { @@ -547,9 +547,9 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { width: 10px; height: 22px; padding: 0; - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); border-radius: 1px; - background: $secondary-background; + background: var(--red-ui-secondary-background); box-sizing: border-box; } .red-ui-icon-picker { @@ -567,10 +567,10 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { position: relative; &.red-ui-icon-list-dark { .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-palette-icon-container { - background: $secondary-background; + background: var(--red-ui-secondary-background); border-radius: 4px; } } @@ -583,10 +583,10 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { border-radius: 4px; &:hover { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } &.selected { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); .red-ui-search-result-node { // border-color: white; } @@ -597,22 +597,22 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { } } .red-ui-icon-list-module { - background: $palette-header-background; + background: var(--red-ui-palette-header-background); font-size: 0.9em; padding: 3px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); clear: both; i { margin-right: 5px; } } .red-ui-icon-meta { - border-top: 1px solid $secondary-border-color; - background: $tertiary-background; + border-top: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-tertiary-background); height: 24px; span { padding: 4px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.9em; line-height: 24px; } @@ -630,7 +630,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { .red-ui-editor-type-json-editor { height: calc(100% - 10px); .red-ui-treeList-container { - background: $secondary-background; + background: var(--red-ui-secondary-background); } .red-ui-treeList-label { padding-top: 0; @@ -647,7 +647,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { vertical-align: middle; } &:hover, &:hover .red-ui-treeList-sublabel-text { - background: $secondary-background-disabled; + background: var(--red-ui-secondary-background-disabled); .red-ui-editor-type-json-editor-item-gutter { > span, > button { display: inline-block; @@ -656,11 +656,11 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { } &.selected { .red-ui-editor-type-json-editor-item-gutter { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } &:hover { .red-ui-editor-type-json-editor-item-gutter { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } } @@ -698,7 +698,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { border: 2px solid rgba(0,0,0,0); border-radius: 3px; &:not(.red-ui-editor-type-json-editor-label-array-key):hover { - border-color: $list-item-background-hover; + border-color: var(--red-ui-list-item-background-hover); border-style: dashed; } &.readonly { @@ -712,8 +712,8 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { height: 100%; line-height: 35px; - color: $tertiary-text-color; - background: $secondary-background-disabled; + color: var(--red-ui-tertiary-text-color); + background: var(--red-ui-secondary-background-disabled); > span { display: inline-block; height: 35px; @@ -755,7 +755,7 @@ button.red-ui-toggleButton.toggle { } >div:first-child { font-size: 0.9em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); margin: 3px 0 -4px; >div { padding-left: 3px; @@ -767,15 +767,15 @@ button.red-ui-toggleButton.toggle { line-height: 30px; display: inline-block; box-sizing: border-box; - // border-left: 2px dashed $secondary-border-color; - // border-bottom: 2px dashed $secondary-border-color; - // border: 1px dashed $secondary-border-color; + // border-left: 2px dashed var(--red-ui-secondary-border-color); + // border-bottom: 2px dashed var(--red-ui-secondary-border-color); + // border: 1px dashed var(--red-ui-secondary-border-color); border-right: none; &:not(:first-child) { padding: 3px; } // &:last-child { - // border-right: 1px dashed $secondary-border-color; + // border-right: 1px dashed var(--red-ui-secondary-border-color); // } .placeholder-input { position: relative; @@ -800,8 +800,8 @@ button.red-ui-toggleButton.toggle { height: 100%; width: 20px; text-align:center; - border-right: 1px solid $secondary-border-color; - background: $tertiary-background; + border-right: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-tertiary-background); } } input[type="checkbox"] { @@ -817,13 +817,13 @@ button.red-ui-toggleButton.toggle { .red-ui-editableList-item-handle { position:relative; top: 0px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } } >div:nth-child(2) { margin: 4px; height: 32px; - border: 1px dashed $secondary-border-color; + border: 1px dashed var(--red-ui-secondary-border-color); text-align: center; a { display: block; @@ -831,7 +831,7 @@ button.red-ui-toggleButton.toggle { height: 100%; line-height: 32px; &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } i { height: 100%; @@ -851,7 +851,7 @@ button.red-ui-toggleButton.toggle { span.red-ui-editor-subflow-env-lang-icon { position: absolute; display: inline-block; - background: $secondary-background; + background: var(--red-ui-secondary-background); opacity: 0.8; width: 20px; line-height: 32px; @@ -864,12 +864,12 @@ span.red-ui-editor-subflow-env-lang-icon { } .red-ui-editor-subflow-env-input-type { - background: $secondary-background; + background: var(--red-ui-secondary-background); height: 100%; box-sizing: border-box; } .red-ui-editor-subflow-env-input-type-placeholder { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); padding-left: 4px; } @@ -886,7 +886,7 @@ span.red-ui-editor-subflow-env-lang-icon { // border-top: none; // } // &.ui-sortable-helper { -// border: 2px dashed $secondary-border-color; +// border: 2px dashed var(--red-ui-secondary-border-color); // .red-ui-editableList-item-content { // >div { // border: none; @@ -901,15 +901,15 @@ span.red-ui-editor-subflow-env-lang-icon { // >div>div { // display: inline-block; // box-sizing: border-box; -// border-left: 1px dashed $secondary-border-color; -// border-bottom: 1px dashed $secondary-border-color; +// border-left: 1px dashed var(--red-ui-secondary-border-color); +// border-bottom: 1px dashed var(--red-ui-secondary-border-color); // } // >div:first-child { // font-size: 0.9em; // display: grid; // grid-template-columns: 25px auto 20px; // >div { -// border-top: 1px dashed $secondary-border-color; +// border-top: 1px dashed var(--red-ui-secondary-border-color); // padding: 1px; // } // >div:nth-child(3) { @@ -929,9 +929,9 @@ span.red-ui-editor-subflow-env-lang-icon { // // line-height: 30px; // // box-sizing: border-box; // // -// // border-left: 2px dashed $secondary-border-color; +// // border-left: 2px dashed var(--red-ui-secondary-border-color); // border-top: none; -// // border-bottom: 2px dashed $secondary-border-color; +// // border-bottom: 2px dashed var(--red-ui-secondary-border-color); // &:not(:first-child) { // padding: 6px 3px; // } @@ -963,7 +963,7 @@ span.red-ui-editor-subflow-env-lang-icon { // height: 100%; // line-height: 45px; // &:hover { -// background: $secondary-background-hover; +// background: var(--red-ui-secondary-background-hover); // } // } // } @@ -993,11 +993,11 @@ span.red-ui-editor-subflow-env-lang-icon { // } .red-ui-editor-subflow-ui-edit-panel { padding-bottom: 3px; - background: $primary-background; + background: var(--red-ui-primary-background); .red-ui-editableList-border { border: none; border-radius: 0; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-editableList-container { } @@ -1005,10 +1005,10 @@ span.red-ui-editor-subflow-env-lang-icon { margin-left: 2px; } .red-ui-editableList-header { - background: $primary-background; + background: var(--red-ui-primary-background); display: grid; grid-template-columns: 50% 50%; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); div:first-child { padding-left: 23px; } @@ -1019,7 +1019,7 @@ span.red-ui-editor-subflow-env-lang-icon { .red-ui-editableList-container { padding: 0 1px; li { - background: $secondary-background; + background: var(--red-ui-secondary-background); // border-bottom: none; padding: 0; .red-ui-editableList-item-content { @@ -1034,14 +1034,14 @@ span.red-ui-editor-subflow-env-lang-icon { margin-bottom: 0; border:none; width: 100%; - border-right: 1px solid $secondary-border-color; + border-right: 1px solid var(--red-ui-secondary-border-color); border-radius: 0; &:focus { - box-shadow: 0 0 0 1px inset $form-input-focus-color; + box-shadow: 0 0 0 1px inset var(--red-ui-form-input-focus-color); } &:first-child { - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); } } button.red-ui-typedInput-type-select, button.red-ui-typedInput-option-expand, button.red-ui-typedInput-option-trigger { @@ -1131,7 +1131,7 @@ span.red-ui-editor-subflow-env-lang-icon { border-top-left-radius: 4px; border-top-right-radius: 4px; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 0; >div { display: grid; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index 2e6de1932..2d4877896 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -16,14 +16,14 @@ .nr-ui-view-lasso { stroke-width: 1px; - stroke: $view-lasso-stroke; - fill: $view-lasso-fill; + stroke: var(--red-ui-view-lasso-stroke); + fill: var(--red-ui-view-lasso-fill); stroke-dasharray: 10 5; } .nr-ui-view-slice { stroke-width: 1px; - stroke: $view-lasso-stroke; + stroke: var(--red-ui-view-lasso-stroke); fill: none; stroke-dasharray: 10 5; } @@ -33,11 +33,11 @@ font-style: italic; } .red-ui-flow-node-label-white { - fill: $view-background !important; + fill: var(--red-ui-view-background) !important; } .red-ui-flow-node-label { stroke-width: 0; - fill: $node-label-color; + fill: var(--red-ui-node-label-color); font-size: 14px; pointer-events: none; -webkit-touch-callout: none; @@ -54,7 +54,7 @@ .red-ui-flow-port-label { stroke-width: 0; - fill: $node-port-label-color; + fill: var(--red-ui-node-port-label-color); font-size: 16px; dominant-baseline: middle; text-anchor: middle; @@ -65,7 +65,7 @@ .red-ui-flow-node { - stroke: $node-border; + stroke: var(--red-ui-node-border); cursor: move; stroke-width: 1; } @@ -80,7 +80,7 @@ opacity: 0.9; .red-ui-flow-node { stroke-width: 2; - stroke: $node-selected-color !important; + stroke: var(--red-ui-node-selected-color) !important; stroke-dasharray: 10, 4; } } @@ -95,53 +95,53 @@ } &.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) { .red-ui-flow-group-outline-select { - stroke: $link-link-color; + stroke: var(--red-ui-link-link-color); } } } .red-ui-flow-group-outline { fill: none; - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); stroke-opacity: 0; stroke-width: 12; pointer-events: stroke; } .red-ui-flow-group-outline-select { fill: none; - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); pointer-events: stroke; stroke-opacity: 0; stroke-width: 3; &.red-ui-flow-group-outline-select-background { - stroke: $view-background; + stroke: var(--red-ui-view-background); stroke-width: 6; } } .red-ui-flow-group-body { pointer-events: none; - fill: $group-default-fill; - fill-opacity: $group-default-fill-opacity; + fill: var(--red-ui-group-default-fill); + fill-opacity: var(--red-ui-group-default-fill-opacity); stroke-width: 2; - stroke: $group-default-stroke; - stroke-opacity: $group-default-stroke-opacity; + stroke: var(--red-ui-group-default-stroke); + stroke-opacity: var(--red-ui-group-default-stroke-opacity); } .red-ui-flow-group-label { @include disable-selection; - fill: $group-default-label-color; + fill: var(--red-ui-group-default-label-color); } .red-ui-flow-node-unknown { stroke-dasharray:10,4; - stroke: $node-border-unknown; + stroke: var(--red-ui-node-border-unknown); } .red-ui-flow-node-placeholder { stroke-dasharray:10,4; - stroke: $node-border-placeholder; - fill: $node-background-placeholder; + stroke: var(--red-ui-node-border-placeholder); + fill: var(--red-ui-node-background-placeholder); opacity: 0.5; stroke-width: 2; } @@ -152,19 +152,19 @@ .fa-lg { @include disable-selection; stroke: none; - fill: $node-icon-color; + fill: var(--red-ui-node-icon-color); text-anchor: middle; font-family: FontAwesome; } } .red-ui-flow-node-icon-shade { stroke: none; - fill: $node-icon-background-color-fill; - fill-opacity: $node-icon-background-color-opacity; + fill: var(--red-ui-node-icon-background-color-fill); + fill-opacity: var(--red-ui-node-icon-background-color-opacity); } .red-ui-flow-node-icon-shade-border { - stroke-opacity: $node-icon-border-color-opacity; - stroke: $node-icon-border-color; + stroke-opacity: var(--red-ui-node-icon-border-color-opacity); + stroke: var(--red-ui-node-icon-border-color); stroke-width: 1; } @@ -181,27 +181,27 @@ cursor: pointer; } .red-ui-flow-node-button-background { - fill: $node-background-placeholder; + fill: var(--red-ui-node-background-placeholder); } .red-ui-flow-port { - stroke: $node-border; + stroke: var(--red-ui-node-border); stroke-width: 1; - fill: $node-port-background; + fill: var(--red-ui-node-port-background); cursor: crosshair; } .red-ui-flow-node-error { - fill: $node-status-error-background; - stroke: $node-status-error-border; + fill: var(--red-ui-node-status-error-background); + stroke: var(--red-ui-node-status-error-border); stroke-width:1px; cursor: default; stroke-linejoin: round; stroke-linecap: round; } .red-ui-flow-node-changed { - fill: $node-status-changed-background; - stroke: $node-status-changed-border; + fill: var(--red-ui-node-status-changed-background); + stroke: var(--red-ui-node-status-changed-border); cursor: default; stroke-width:1px; stroke-linejoin: round; @@ -214,13 +214,13 @@ g.red-ui-flow-node-selected { } .red-ui-flow-node,.red-ui-flow-subflow-port { stroke-width: 2; - stroke: $node-selected-color !important; + stroke: var(--red-ui-node-selected-color) !important; } } .red-ui-flow-node-highlighted { - border-color: $node-selected-color !important; + border-color: var(--red-ui-node-selected-color) !important; border-style: dashed !important; - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); stroke-width: 3; stroke-dasharray: 8, 4; } @@ -236,7 +236,7 @@ g.red-ui-flow-node-selected { .red-ui-flow-link-line { stroke-dasharray: 10,8 !important; stroke-width: 2 !important; - stroke: $link-disabled-color; + stroke: var(--red-ui-link-disabled-color); } .red-ui-flow-port { fill-opacity: 1; @@ -254,7 +254,7 @@ g.red-ui-flow-node-selected { &.red-ui-flow-link-line { stroke-dasharray: 10,8 !important; stroke-width: 2 !important; - stroke: $link-disabled-color; + stroke: var(--red-ui-link-disabled-color); } .red-ui-flow-port { fill-opacity: 1; @@ -263,49 +263,49 @@ g.red-ui-flow-node-selected { } @each $current-color in red green yellow blue grey gray { .red-ui-flow-node-status-dot-#{""+$current-color} { - fill: map-get($node-status-colors,$current-color); - stroke: map-get($node-status-colors,$current-color); + fill: var(--red-ui-node-status-colors-#{"" + $current-color}); + stroke: var(--red-ui-node-status-colors-#{"" + $current-color}); } .red-ui-flow-node-status-ring-#{""+$current-color} { - fill: $view-background; - stroke: map-get($node-status-colors,$current-color); + fill: var(--red-ui-view-background); + stroke: var(--red-ui-node-status-colors-#{"" + $current-color}); } } .red-ui-flow-node-status-label { @include disable-selection; stroke-width: 0; - fill: $secondary-text-color; + fill: var(--red-ui-secondary-text-color); font-size:9pt; text-anchor:start; } .red-ui-flow-port-hovered { - stroke: $port-selected-color; - fill: $port-selected-color; + stroke: var(--red-ui-port-selected-color); + fill: var(--red-ui-port-selected-color); } .red-ui-flow-subflow-port { - fill: $node-background-placeholder; - stroke: $node-border; + fill: var(--red-ui-node-background-placeholder); + stroke: var(--red-ui-node-border); } .red-ui-flow-drag-line { - stroke: $node-selected-color !important; + stroke: var(--red-ui-node-selected-color) !important; stroke-width: 3; fill: none; pointer-events: none; } .red-ui-flow-link-line { - stroke: $link-color; + stroke: var(--red-ui-link-color); stroke-width: 3; fill: none; pointer-events: none; } .red-ui-flow-link-link { stroke-width: 2; - stroke: $link-link-color; + stroke: var(--red-ui-link-link-color); fill: none; stroke-dasharray: 25,4; } @@ -314,19 +314,19 @@ g.red-ui-flow-node-selected { } .red-ui-flow-link-port { - fill: $node-link-port-background; - stroke: $link-link-color; + fill: var(--red-ui-node-link-port-background); + stroke: var(--red-ui-link-link-color); stroke-width: 1; } .red-ui-flow-link-group-active .red-ui-flow-link-port { - stroke: $link-link-active-color; + stroke: var(--red-ui-link-link-active-color); } .red-ui-flow-link-group:hover { cursor: pointer; } .red-ui-flow-link-outline { - stroke: $view-background; + stroke: var(--red-ui-view-background); stroke-opacity: 0.4; stroke-width: 5; cursor: crosshair; @@ -334,7 +334,7 @@ g.red-ui-flow-node-selected { pointer-events: none; } .red-ui-flow-link-background { - stroke: $view-background; + stroke: var(--red-ui-view-background); opacity: 0; stroke-width: 20; cursor: crosshair; @@ -345,10 +345,10 @@ g.red-ui-flow-node-selected { } g.red-ui-flow-link-selected path.red-ui-flow-link-line { - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); } g.red-ui-flow-link-unknown path.red-ui-flow-link-line { - stroke: $link-unknown-color; + stroke: var(--red-ui-link-unknown-color); stroke-width: 2; stroke-dasharray: 10, 4; } @@ -364,15 +364,15 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { pointer-events: none; path:first-child { - fill: $popover-background; - stroke: $popover-background; + fill: var(--red-ui-popover-background); + stroke: var(--red-ui-popover-background); stroke-width: 1; } } .red-ui-flow-port-tooltip-label { stroke-width: 0; - fill: $popover-color; - font-family: $primary-font; + fill: var(--red-ui-popover-color); + font-family: var(--red-ui-primary-font); font-size: 12px; pointer-events: none; -webkit-touch-callout: none; @@ -401,18 +401,18 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { } } .red-ui-flow-junction-port { - stroke: $node-border; + stroke: var(--red-ui-node-border); stroke-width: 1; - fill: $node-port-background; + fill: var(--red-ui-node-port-background); cursor: crosshair; transition: transform 0.1s; opacity: 0; pointer-events: none; } .red-ui-flow-junction-background { - stroke: $node-border; + stroke: var(--red-ui-node-border); stroke-width: 1; - fill: $node-port-background; + fill: var(--red-ui-node-port-background); cursor: crosshair; transform: scale(1); transition: transform 0.1s; @@ -421,10 +421,10 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { } } .red-ui-flow-junction-hovered { - stroke: $port-selected-color; - fill: $port-selected-color; + stroke: var(--red-ui-port-selected-color); + fill: var(--red-ui-port-selected-color); } .red-ui-flow-junction.selected .red-ui-flow-junction-background { - stroke: $port-selected-color; - // fill: $port-selected-color; + stroke: var(--red-ui-port-selected-color); + // fill: var(--red-ui-port-selected-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/forms.scss b/packages/node_modules/@node-red/editor-client/src/sass/forms.scss index 022579b27..a281b9265 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/forms.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/forms.scss @@ -99,13 +99,13 @@ margin-bottom: 20px; font-size: 21px; line-height: 40px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); border: 0; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } legend small { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } @@ -125,7 +125,7 @@ button, select, textarea { - font-family: $primary-font; + font-family: var(--red-ui-primary-font); } label { @@ -159,7 +159,7 @@ margin-bottom: 10px; font-size: 14px; line-height: 20px; - color: $form-text-color; + color: var(--red-ui-form-text-color); vertical-align: middle; border-radius: 4px; } @@ -193,8 +193,8 @@ div[contenteditable="true"], .uneditable-input, .placeholder-input { - background-color: $form-input-background; - border: 1px solid $form-input-border-color; + background-color: var(--red-ui-form-input-background); + border: 1px solid var(--red-ui-form-input-border-color); } textarea:focus, @@ -214,7 +214,7 @@ input[type="color"]:focus, div[contenteditable="true"]:focus, .uneditable-input:focus { - border-color: $form-input-focus-color; + border-color: var(--red-ui-form-input-focus-color); outline: 0; outline: thin dotted \9; } @@ -245,8 +245,8 @@ select { width: 220px; - background-color: $form-input-background; - border: 1px solid $form-input-border-color; + background-color: var(--red-ui-form-input-background); + border: 1px solid var(--red-ui-form-input-border-color); } select[multiple], @@ -258,16 +258,16 @@ input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { - outline: 2px auto $form-input-focus-color; + outline: 2px auto var(--red-ui-form-input-focus-color); outline-offset: -3px; } .uneditable-input, .uneditable-textarea { - color: $form-text-color-disabled; + color: var(--red-ui-form-text-color-disabled); cursor: not-allowed; - background-color: $form-input-background-disabled; - border-color: $form-input-border-color; + background-color: var(--red-ui-form-input-background-disabled); + border-color: var(--red-ui-form-input-border-color); } .uneditable-input { @@ -282,19 +282,19 @@ input:-moz-placeholder, textarea:-moz-placeholder { - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } input:-ms-input-placeholder, div[contenteditable="true"]:-ms-input-placeholder, textarea:-ms-input-placeholder { - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } input::-webkit-input-placeholder, div[contenteditable="true"]::-webkit-input-placeholder, textarea::-webkit-input-placeholder { - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } .radio, @@ -384,7 +384,7 @@ } label.disabled { - color: $form-text-color-disabled; + color: var(--red-ui-form-text-color-disabled); cursor: default; } @@ -395,8 +395,8 @@ select[readonly], textarea[readonly] { cursor: not-allowed; - color: $form-text-color-disabled; - background-color: $form-input-background-disabled; + color: var(--red-ui-form-text-color-disabled); + background-color: var(--red-ui-form-input-background-disabled); } input[type="radio"][disabled], @@ -410,21 +410,21 @@ div[contenteditable="true"]:invalid, textarea:invalid, select:invalid { - border-color: $form-input-border-error-color; + border-color: var(--red-ui-form-input-border-error-color); } input:focus:invalid, div[contenteditable="true"]:focus:invalid, textarea:focus:invalid, select:focus:invalid { - border-color: $form-input-border-error-color; + border-color: var(--red-ui-form-input-border-error-color); } input:focus:invalid:focus, div[contenteditable="true"]:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:focus { - border-color: $form-input-border-error-color; + border-color: var(--red-ui-form-input-border-error-color); } .input-append, @@ -488,8 +488,8 @@ font-weight: normal; line-height: 20px; text-align: center; - background-color: $form-button-background; - border: 1px solid $form-input-border-color; + background-color: var(--red-ui-form-button-background); + border: 1px solid var(--red-ui-form-input-border-color); } .input-append .add-on, diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index 697a90729..19c15b015 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -24,10 +24,10 @@ left: 0; width: 100%; height: 40px; - background: $header-background; + background: var(--red-ui-header-background); box-sizing: border-box; padding: 0px 0px 0px 20px; - color: $header-menu-color; + color: var(--red-ui-header-menu-color); font-size: 14px; span.red-ui-header-logo { @@ -81,17 +81,17 @@ font-size: 20px; padding: 0px 12px; text-decoration: none; - color: $header-menu-color; + color: var(--red-ui-header-menu-color); margin: auto 5px; vertical-align: middle; - border-left: 2px solid $header-background; - border-right: 2px solid $header-background; + border-left: 2px solid var(--red-ui-header-background); + border-right: 2px solid var(--red-ui-header-background); &:hover { - border-color: $header-menu-item-hover; + border-color: var(--red-ui-header-menu-item-hover); } &:active, &.active { - background: $header-button-background-active; + background: var(--red-ui-header-button-background-active); } &:focus { outline: none; @@ -116,18 +116,18 @@ } .red-ui-deploy-button { - background: $deploy-button-background; - color: $deploy-button-color; + background: var(--red-ui-deploy-button-background); + color: var(--red-ui-deploy-button-color); &:hover { - background: $deploy-button-background-hover; + background: var(--red-ui-deploy-button-background-hover); } &:focus { outline: none; } &:active { - background: $deploy-button-background-active; - color: $deploy-button-color-active; + background: var(--red-ui-deploy-button-background-active); + color: var(--red-ui-deploy-button-color-active); } } @@ -149,21 +149,21 @@ padding: 4px 12px; &.disabled { cursor: default; - background: $deploy-button-background-disabled; - color: $deploy-button-color-disabled; + background: var(--red-ui-deploy-button-background-disabled); + color: var(--red-ui-deploy-button-color-disabled); .red-ui-deploy-button-content>img { opacity: 0.3; } &+ #red-ui-header-button-deploy-options { - background: $deploy-button-background-disabled; - color: $deploy-button-color-active; + background: var(--red-ui-deploy-button-background-disabled); + color: var(--red-ui-deploy-button-color-active); } &+ #red-ui-header-button-deploy-options:hover { - background: $deploy-button-background-disabled-hover; + background: var(--red-ui-deploy-button-background-disabled-hover); } &+ #red-ui-header-button-deploy-options:active { - background: $deploy-button-background-disabled; + background: var(--red-ui-deploy-button-background-disabled); } } @@ -174,23 +174,23 @@ .red-ui-deploy-button-group.open { #red-ui-header-button-deploy-options { - background: $header-button-background-active !important; + background: var(--red-ui-header-button-background-active) !important; } } li.open .button { - background: $header-button-background-active; - border-color: $header-button-background-active; + background: var(--red-ui-header-button-background-active); + border-color: var(--red-ui-header-button-background-active); } ul.red-ui-menu-dropdown { - background: $header-menu-background; - border: 1px solid $header-menu-background; + background: var(--red-ui-header-menu-background); + border: 1px solid var(--red-ui-header-menu-background); width: 260px !important; margin-top: 0; li a { - color: $header-menu-color; + color: var(--red-ui-header-menu-color); padding: 3px 10px 3px 40px; img { max-width: 100%; @@ -199,11 +199,11 @@ border: 3px solid transparent; } .red-ui-popover-key { - color: $header-menu-color-disabled !important; - border-color: $header-menu-color-disabled !important; + color: var(--red-ui-header-menu-color-disabled) !important; + border-color: var(--red-ui-header-menu-color-disabled) !important; } &.active img { - border: 3px solid $header-menu-item-border-active; + border: 3px solid var(--red-ui-header-menu-item-border-active); } span.red-ui-menu-label-container { @@ -217,7 +217,7 @@ text-indent: 0px; } span.red-ui-menu-sublabel { - color: $header-menu-sublabel-color; + color: var(--red-ui-header-menu-sublabel-color); font-size: 13px; display: inline-block; text-indent: 0px; @@ -228,13 +228,13 @@ > li > a:focus, > li:hover > a, > li:focus > a { - background: $header-menu-item-hover !important; + background: var(--red-ui-header-menu-item-hover) !important; } li.red-ui-menu-divider { - background: $headerMenuItemDivider; + background: var(--red-ui-headerMenuItemDivider); } li.disabled a { - color: $header-menu-color-disabled; + color: var(--red-ui-header-menu-color-disabled); } > li.disabled > a:hover, > li.disabled > a:focus { @@ -242,7 +242,7 @@ } } .red-ui-menu-dropdown-submenu>a:before { - border-right-color: $headerMenuCaret; + border-right-color: var(--red-ui-headerMenuCaret); } /* Deploy menu customisations */ @@ -250,7 +250,7 @@ width: 300px !important; li a { padding: 10px 30px; - color: $header-menu-heading-color; + color: var(--red-ui-header-menu-heading-color); span.red-ui-menu-label { font-size: 16px; display: inline-block; @@ -263,7 +263,7 @@ } /* User menu customisations */ #usermenu-item-username > .red-ui-menu-label { - color: $header-menu-heading-color; + color: var(--red-ui-header-menu-heading-color); } #red-ui-header-button-user .user-profile { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss index b7278b332..27661c459 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss @@ -16,25 +16,25 @@ .ui-widget { font-size: 14px !important; - font-family: $primary-font; + font-family: var(--red-ui-primary-font); } .ui-widget input, .ui-widget div[contenteditable="true"], .ui-widget select, .ui-widget textarea, .ui-widget button { font-size: 14px !important; - font-family: $primary-font; + font-family: var(--red-ui-primary-font); } .ui-widget input, .ui-widget div[contenteditable="true"] { box-shadow: none; } .ui-widget.ui-widget-content { - border: 1px solid $tertiary-border-color; + border: 1px solid var(--red-ui-tertiary-border-color); } .ui-widget-content { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); } .ui-widget-header { - color: $header-text-color; + color: var(--red-ui-header-text-color); } /* jQuery Theme overrides */ @@ -50,7 +50,7 @@ .ui-dialog { border-radius: 1px; - background: $secondary-background; + background: var(--red-ui-secondary-background); padding: 0; @include component-shadow; } @@ -62,20 +62,20 @@ } .ui-dialog .ui-dialog-titlebar { padding: 10px; - background: $primary-background; + background: var(--red-ui-primary-background); border: none; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); border-radius: 0; } .ui-dialog .ui-dialog-buttonpane.ui-widget-content { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } .ui-corner-all { border-radius: 1px; } .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { - background: $primary-background; + background: var(--red-ui-primary-background); } .ui-dialog-no-close .ui-dialog-titlebar-close { display: none; @@ -95,8 +95,8 @@ padding: 6px 14px; margin-right: 8px; border-radius: 2px; - color: $workspace-button-color; - background: $workspace-button-background; + color: var(--red-ui-workspace-button-color); + background: var(--red-ui-workspace-button-background); &.leftButton { float: left; @@ -107,18 +107,18 @@ } &.primary { - border-color: $workspace-button-background-primary; - color: $workspace-button-color-primary !important; - background: $workspace-button-background-primary; + border-color: var(--red-ui-workspace-button-background-primary); + color: var(--red-ui-workspace-button-color-primary) !important; + background: var(--red-ui-workspace-button-background-primary); &:not(.disabled):hover { - border-color: $workspace-button-background-primary-hover; - background: $workspace-button-background-primary-hover; - color: $workspace-button-color-primary !important; + border-color: var(--red-ui-workspace-button-background-primary-hover); + background: var(--red-ui-workspace-button-background-primary-hover); + color: var(--red-ui-workspace-button-color-primary) !important; } &.disabled { - border-color: $form-input-border-color; - color: $workspace-button-color-disabled !important; - background: $workspace-button-background; + border-color: var(--red-ui-form-input-border-color); + color: var(--red-ui-workspace-button-color-disabled) !important; + background: var(--red-ui-workspace-button-background); } } &.disabled { @@ -142,10 +142,10 @@ .ui-spinner { border-radius: 4px; padding: 0; - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); } .ui-spinner input { - background: $form-input-background; + background: var(--red-ui-form-input-background); margin: 0 17px 0 0; padding: 6px; border: none; @@ -169,8 +169,8 @@ .ui-button, html .ui-button.ui-state-disabled:hover, html .ui-button.ui-state-disabled:active { - border: 1px solid $secondary-border-color; - background: $form-button-background; + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-form-button-background); } .ui-state-hover, @@ -180,9 +180,9 @@ html .ui-button.ui-state-disabled:active { .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus, .ui-button:hover, .ui-button:focus { - border: 1px solid $secondary-border-color; - background: $workspace-button-background-hover; - color: $workspace-button-color-hover; + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-workspace-button-background-hover); + color: var(--red-ui-workspace-button-color-hover); } .ui-state-active, @@ -191,10 +191,10 @@ html .ui-button.ui-state-disabled:active { a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover { - border: 1px solid $secondary-border-color; - background: $workspace-button-background-active; + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-workspace-button-background-active); font-weight: normal; - color: $workspace-button-color-active; + color: var(--red-ui-workspace-button-color-active); } .ui-state-active .ui-icon, .ui-button:active .ui-icon { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss b/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss index 8c4e5a3a8..c11aa0592 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss @@ -23,9 +23,9 @@ } .keyboard-shortcut-list-header { padding:0 5px 0 5px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); div { - color: $header-text-color !important; + color: var(--red-ui-header-text-color) !important; } .red-ui-searchBox-container { width: calc(100% - 20px); @@ -49,7 +49,7 @@ } } li:hover { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } } .keyboard-shortcut-entry { @@ -78,13 +78,13 @@ width: calc(100% - 160px - 100px - 10px); overflow: hidden; i { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); margin-right: 5px; } } .keyboard-shortcut-entry-scope { width:100px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); vertical-align: middle; text-align: right; } @@ -94,13 +94,13 @@ } } .keyboard-shortcut-entry-unassigned { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); .keyboard-shortcut-entry-key { font-style: italic; } } .keyboard-shortcut-entry-expanded { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); .keyboard-shortcut-entry-key { width: 150px; } @@ -115,12 +115,12 @@ } } .help-key { - border: 1px solid $tertiary-border-color; + border: 1px solid var(--red-ui-tertiary-border-color); padding: 4px; border-radius: 3px; - background: $tertiary-background; - font-family: $monospace-font; - box-shadow: $shade-color 1px 1px 1px; + background: var(--red-ui-tertiary-background); + font-family: var(--red-ui-monospace-font); + box-shadow: var(--red-ui-shade-color 1px 1px 1px); } .help-key-block { white-space: nowrap; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/library.scss b/packages/node_modules/@node-red/editor-client/src/sass/library.scss index 0d284ffce..bb651e4ea 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/library.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/library.scss @@ -18,13 +18,13 @@ pre { margin: 10px 0; border: none; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); span { padding: 5px 0; } span.error { padding: 5px; - border: 1px solid $form-input-border-error-color; + border: 1px solid var(--red-ui-form-input-border-error-color); margin: 0 1px; } } @@ -52,16 +52,16 @@ .red-ui-clipboard-dialog-tab-clipboard { textarea { - color: $secondary-text-color-active !important; + color: var(--red-ui-secondary-text-color-active) !important; resize: none; width: 100%; border-radius: 4px; - font-family: $monospace-font !important; + font-family: var(--red-ui-monospace-font !important); font-size: 13px !important; height: 100%; line-height: 1.3em; padding: 6px 10px; - background: $clipboard-textarea-background; + background: var(--red-ui-clipboard-textarea-background); } } @@ -80,7 +80,7 @@ right: 0; bottom: 0; padding: 0; - background: $form-input-background; + background: var(--red-ui-form-input-background); &>div { height: 100%; box-sizing: border-box; @@ -89,7 +89,7 @@ .red-ui-clipboard-dialog-box { height: 400px; position:relative; - border:1px solid $primary-border-color; + border:1px solid var(--red-ui-primary-border-color); } #red-ui-clipboard-dialog-export-tab-library-filename { @@ -111,7 +111,7 @@ .red-ui-clipboard-dialog-tabs-content>div.red-ui-clipboard-dialog-export-tab-library-browser { height: calc(100% - 60px); margin-bottom: 13px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); box-sizing: border-box; } #red-ui-clipboard-dialog-import-tab-library-browser { @@ -124,7 +124,7 @@ position: relative; height: 100%; .red-ui-treeList-container { - background: $secondary-background; + background: var(--red-ui-secondary-background); border: none; border-radius: 0; li { @@ -149,14 +149,14 @@ #red-ui-library-dialog-save-browser { height: calc(100% - 60px); - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); margin-bottom: 10px; } #red-ui-library-dialog-load-browser { - // border: 1px solid $primary-border-color; + // border: 1px solid var(--red-ui-primary-border-color); } #red-ui-library-dialog-load-panes { - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); } @@ -180,15 +180,15 @@ position: relative; li:not(:first-child) .red-ui-clipboard-dialog-import-conflicts-item-header { - // border-top: 1px solid $secondary-border-color; + // border-top: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-clipboard-dialog-import-conflicts-item-header { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); & > span:first-child { - color: $header-text-color; + color: var(--red-ui-header-text-color); padding-left: 4px; font-size: 12px; } @@ -199,7 +199,7 @@ bottom: 0; right: 0px; text-align: center; - color: $form-text-color; + color: var(--red-ui-form-text-color); .form-row & label { padding: 2px 0; line-height: 23px; @@ -210,7 +210,7 @@ height: 100%; width: 80px; text-align: center; - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); } input[type="checkbox"] { display: inline-block; @@ -265,7 +265,7 @@ span:nth-child(3), span:nth-child(4) { flex-grow: 0; padding-right: 5px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.9em; } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss b/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss index 9214ea37b..6262597a1 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss @@ -31,33 +31,33 @@ } @mixin component-border { - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); box-sizing: border-box; } @mixin reset-a-style { - color: $workspace-button-color !important; - background: $workspace-button-background; + color: var(--red-ui-workspace-button-color) !important; + background: var(--red-ui-workspace-button-background); text-decoration: none; &.disabled, &:disabled { cursor: default; - color: $workspace-button-color-disabled !important; + color: var(--red-ui-workspace-button-color-disabled) !important; } &:hover, &:focus { text-decoration: none; } - &:not(.disabled):not(:disabled):hover, { - color: $workspace-button-color-hover !important; - background: $workspace-button-background-hover; + &:not(.disabled):not(:disabled):hover { + color: var(--red-ui-workspace-button-color-hover) !important; + background: var(--red-ui-workspace-button-background-hover); } &:not(.disabled):not(:disabled):focus { - color: $workspace-button-color-focus !important; + color: var(--red-ui-workspace-button-color-focus) !important; } &:not(.disabled):not(:disabled):active { - color: $workspace-button-color-active !important; - background: $workspace-button-background-active; + color: var(--red-ui-workspace-button-color-active) !important; + background: var(--red-ui-workspace-button-background-active); text-decoration: none; } } @@ -68,14 +68,14 @@ box-sizing: border-box; display: inline-block; - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); text-align: center; margin:0; cursor:pointer; &.selected:not(.disabled):not(:disabled) { - color: $workspace-button-color-selected !important; - background: $workspace-button-background-active; + color: var(--red-ui-workspace-button-color-selected) !important; + background: var(--red-ui-workspace-button-background-active); } .button-group &:not(:first-child) { border-left: none; @@ -108,23 +108,23 @@ } &:focus { - outline: 1px solid $workspace-button-color-focus-outline; + outline: 1px solid var(--red-ui-workspace-button-color-focus-outline); outline-offset: 1px; } &.primary { - border-color: $workspace-button-background-primary; - color: $workspace-button-color-primary !important; - background: $workspace-button-background-primary; + border-color: var(--red-ui-workspace-button-background-primary); + color: var(--red-ui-workspace-button-color-primary) !important; + background: var(--red-ui-workspace-button-background-primary); &.disabled, &.ui-state-disabled { background: none; - color: $workspace-button-color !important; - border-color: $form-input-border-color; + color: var(--red-ui-workspace-button-color) !important; + border-color: var(--red-ui-form-input-border-color); } &:not(.disabled):not(.ui-button-disabled):hover { - border-color: $workspace-button-background-primary-hover; - background: $workspace-button-background-primary-hover; - color: $workspace-button-color-primary !important; + border-color: var(--red-ui-workspace-button-background-primary-hover); + background: var(--red-ui-workspace-button-background-primary-hover); + color: var(--red-ui-workspace-button-color-primary) !important; } } &.secondary { @@ -151,7 +151,7 @@ margin-bottom: 1px; &.selected:not(.disabled):not(:disabled) { border-bottom-width: 2px; - border-bottom-color: $form-input-border-selected-color; + border-bottom-color: var(--red-ui-form-input-border-selected-color); margin-bottom: 0; cursor: default; } @@ -166,7 +166,7 @@ padding: 6px 14px; margin-right: 8px; &:not(.disabled):hover { - //color: $workspace-button-color; + //color: var(--red-ui-workspace-button-color); } &.disabled { background: none; @@ -187,8 +187,8 @@ } @mixin component-footer { - border-top: 1px solid $primary-border-color; - background: $primary-background; + border-top: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-primary-background); text-align: right; position: absolute; bottom: 0; @@ -231,7 +231,7 @@ } @mixin component-shadow { - box-shadow: 1px 1px 4px $shadow; + box-shadow: 1px 1px 4px var(--red-ui-shadow); } @@ -241,7 +241,7 @@ left: 0; bottom: 0; right: 0; - background: $shade-color; + background: var(--red-ui-shade-color); z-index: 5; } .red-ui-shade { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss index 52e8509b3..efae432b2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss @@ -27,10 +27,10 @@ position: relative; padding: 8px 18px 0px; margin-bottom: 4px; - box-shadow: 0 1px 1px 1px $shadow; - background-color: $secondary-background; - color: $primary-text-color; - border: 1px solid $notification-border-default; + box-shadow: 0 1px 1px 1px var(--red-ui-shadow); + background-color: var(--red-ui-secondary-background); + color: var(--red-ui-primary-text-color); + border: 1px solid var(--red-ui-notification-border-default); border-left-width: 16px; overflow: hidden; .ui-dialog-buttonset { @@ -50,13 +50,13 @@ } .red-ui-notification-success { - border-color: $notification-border-success; + border-color: var(--red-ui-notification-border-success); } .red-ui-notification-warning { - border-color: $notification-border-warning; + border-color: var(--red-ui-notification-border-warning); } .red-ui-notification-error { - border-color: $notification-border-error; + border-color: var(--red-ui-notification-border-error); } .red-ui-notification-compact { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss index 34fbd3e07..ca387782b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss @@ -27,7 +27,7 @@ left:0; padding: 0; box-sizing:border-box; - background: $secondary-background; + background: var(--red-ui-secondary-background); .red-ui-editableList-container { border: none; @@ -37,27 +37,27 @@ li { // border: none; - // border-top: 1px solid $primary-border-color; + // border-top: 1px solid var(--red-ui-primary-border-color); padding: 0px; .red-ui-button { min-width: 60px; } .disabled { - // background: $secondary-background-inactive;//f3f3f3; + // background: var(--red-ui-secondary-background-inactive;//f3f3f3); .red-ui-palette-module-name { font-style: italic; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-palette-module-version { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-palette-module-errors .fa-warning { opacity: 0.5; } ul.red-ui-palette-module-error-list li { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } @@ -66,7 +66,7 @@ padding: 12px 16px; } &:last-child { - // border-bottom: 1px solid $primary-border-color; + // border-bottom: 1px solid var(--red-ui-primary-border-color); } } @@ -79,14 +79,14 @@ bottom:0 } .red-ui-palette-editor-toolbar { - background: $primary-background; + background: var(--red-ui-primary-background); box-sizing: border-box; padding: 8px 10px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); text-align: right; } .red-ui-palette-module-shade-status { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-palette-module-updated { margin-left: 10px; @@ -98,7 +98,7 @@ .red-ui-palette-module-description { margin-left: 20px; font-size: 0.9em; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-palette-module-link { } @@ -120,7 +120,7 @@ } } .red-ui-palette-module-set { - border:1px solid $secondary-border-color; + border:1px solid var(--red-ui-secondary-border-color); border-radius: 0; padding: 5px; position: relative; @@ -138,7 +138,7 @@ } .red-ui-palette-module-type { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); padding-left: 5px; font-size: 0.9em; @include enable-selection; @@ -150,8 +150,8 @@ border-radius: 3px; vertical-align: middle; margin-right: 5px; - background: $primary-background; - border: 1px dashed $secondary-border-color; + background: var(--red-ui-primary-background); + border: 1px dashed var(--red-ui-secondary-border-color); } .red-ui-palette-module-set-button-group { position: absolute; @@ -160,35 +160,35 @@ } .red-ui-palette-module-set-disabled { - background: $list-item-background-disabled; + background: var(--red-ui-list-item-background-disabled); .red-ui-palette-module-type { - color: $secondary-text-color-disabled-active; + color: var(--red-ui-secondary-text-color-disabled-active); } } .red-ui-palette-module-more { padding: 0 !important; margin-top: 10px; margin-bottom: 10px; - background: $tab-background-inactive; + background: var(--red-ui-tab-background-inactive); a { display: block; text-align: center; padding: 12px 8px; - color: $text-color-code; + color: var(--red-ui-text-color-code); &:hover { text-decoration: none; - background: $tab-background-hover; + background: var(--red-ui-tab-background-hover); } } } } .red-ui-palette-module-meta { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); position: relative; &.disabled { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } .fa { @@ -198,7 +198,7 @@ } } .red-ui-palette-module-name { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); white-space: nowrap; @include enable-selection; } @@ -216,7 +216,7 @@ } } .red-ui-palette-module-meta .fa-warning { - color: $text-color-warning; + color: var(--red-ui-text-color-warning); } ul.red-ui-palette-module-error-list { display: inline-block; @@ -264,9 +264,9 @@ button.red-ui-palette-editor-upload-button { right: 0; top: 44px; padding: 20px; - background: $secondary-background; - border-bottom: 1px $secondary-border-color solid; - box-shadow: 1px 1px 4px $shadow; + background: var(--red-ui-secondary-background); + border-bottom: 1px var(--red-ui-secondary-border-color solid); + box-shadow: 1px 1px 4px var(--red-ui-shadow); .placeholder-input { width: calc(100% - 180px); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 800f34079..0d123918a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -20,7 +20,7 @@ top: 0px; bottom: 0px; left:0px; - background: $primary-background; + background: var(--red-ui-primary-background); width: 180px; text-align: center; @include disable-selection; @@ -55,26 +55,26 @@ .red-ui-palette-search { position: relative; overflow: hidden; - background: $secondary-background; + background: var(--red-ui-secondary-background); text-align: center; height: 35px; padding: 3px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); box-sizing:border-box; } .red-ui-palette-category { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-palette-content { - background: $palette-content-background; + background: var(--red-ui-palette-content-background); padding: 3px; } .red-ui-palette-header { position: relative; - background: $palette-header-background; - color: $palette-header-color; + background: var(--red-ui-palette-header-background); + color: var(--red-ui-palette-header-color); cursor: pointer; text-align: left; padding: 9px; @@ -83,7 +83,7 @@ overflow: hidden; user-select: none; &:hover { - background: $palette-header-background !important; + background: var(--red-ui-palette-header-background) !important; } } .red-ui-palette-header > i { @@ -106,7 +106,7 @@ clear: both; } .red-ui-palette-label { - color: $node-label-color; + color: var(--red-ui-node-label-color); font-size: 13px; margin: 4px 0 4px 32px; line-height: 20px; @@ -121,11 +121,11 @@ .red-ui-palette-node { // display: inline-block; cursor: move; - background: $secondary-background; + background: var(--red-ui-secondary-background); margin: 10px auto; height: 25px; border-radius: 5px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; width: 120px; @@ -141,7 +141,7 @@ } .red-ui-palette-node:hover { border-color: transparent; - box-shadow: 0 0 0 2px $node-selected-color; + box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); } .red-ui-palette-port { position: absolute; @@ -149,11 +149,11 @@ left: -5px; box-sizing: border-box; -moz-box-sizing: border-box; - background: $node-port-background; + background: var(--red-ui-node-port-background); border-radius: 3px; width: 10px; height: 10px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); } .red-ui-palette-port-output { left:auto; @@ -161,7 +161,7 @@ } .red-ui-palette-node:hover .red-ui-palette-port { - background-color: $node-port-background-hover; + background-color: var(--red-ui-node-port-background-hover); } .red-ui-palette-icon-container { position: absolute; @@ -170,14 +170,14 @@ bottom:0; left:0; width: 30px; - border-right: 1px solid $node-icon-background-color; - background-color: $node-icon-background-color; + border-right: 1px solid var(--red-ui-node-icon-background-color); + background-color: var(--red-ui-node-icon-background-color); } .red-ui-palette-icon-container-right { left: auto; right: 0; border-right: none; - border-left: 1px solid $node-icon-background-color; + border-left: 1px solid var(--red-ui-node-icon-background-color); } .red-ui-palette-icon { display: inline-block; @@ -198,7 +198,7 @@ background: none; } .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 18px; } } @@ -249,12 +249,12 @@ // width: 30px; // height: 25px; border-radius: 3px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; background-size: contain; position: relative; - background-color: $node-icon-background-color; + background-color: var(--red-ui-node-icon-background-color); text-align: center; .red-ui-palette-icon { @@ -278,7 +278,7 @@ background: none; } .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 16px; } } @@ -318,5 +318,5 @@ .red-ui-node-label { white-space: nowrap; margin-left: 4px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss index e820992b4..c782b341b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss @@ -35,12 +35,12 @@ .red-ui-panels-separator { flex: 0 0 auto; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); height: 7px; box-sizing: border-box; cursor: ns-resize; - background-color: $primary-background; + background-color: var(--red-ui-primary-background); &:before { content: ''; @@ -55,7 +55,7 @@ mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } } @@ -80,14 +80,14 @@ vertical-align: top; border-top: none; border-bottom: none; - border-left: 1px solid $secondary-border-color; - border-right: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); + border-right: 1px solid var(--red-ui-secondary-border-color); height: 100%; width: 7px; display: inline-block; cursor: ew-resize; - background-color: $primary-background; - + background-color: var(--red-ui-primary-background); + &:before { content: ''; display: block; @@ -101,7 +101,7 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss index 2eb167ef4..7e504b59b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss @@ -25,7 +25,7 @@ color: var(--red-ui-popover-color); border-radius: 4px; z-index: 1000; - font-family: $primary-font; + font-family: var(--red-ui-primary-font); font-size: 14px; line-height: 1.4em; @include component-shadow; @@ -146,7 +146,7 @@ .red-ui-popover-key { font-size: 11px; - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); margin-left: 3px; border: 1px solid var(--red-ui-popover-color); border-radius:3px; @@ -163,42 +163,42 @@ color: var(--red-ui-popover-color) !important; } a:focus { - outline: 1px solid $form-input-focus-color; + outline: 1px solid var(--red-ui-form-input-focus-color); } } .red-ui-popover a.red-ui-button, .red-ui-popover button.red-ui-button { &:not(.primary) { - border-color: $popover-button-border-color; + border-color: var(--red-ui-popover-button-border-color); background: var(--red-ui-popover-background); color: var(--red-ui-popover-color) !important; } &:not(.primary):not(.disabled):not(.ui-button-disabled):hover { - border-color: $popover-button-border-color-hover; + border-color: var(--red-ui-popover-button-border-color-hover); } &.primary { - border-color: $popover-button-border-color; + border-color: var(--red-ui-popover-button-border-color); } &.primary:not(.disabled):not(.ui-button-disabled):hover { - border-color: $popover-button-border-color-hover; + border-color: var(--red-ui-popover-button-border-color-hover); } } .red-ui-popover code { border: none; background: none; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-popover-panel { @include component-shadow; - font-family: $primary-font; - font-size: $primary-font-size; + font-family: var(--red-ui-primary-font); + font-size: var(--red-ui-primary-font-size); position: absolute; box-sizing: border-box; - border: 1px solid $primary-border-color; - background: $secondary-background; + border: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-secondary-background); z-index: 2000; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index 681e7b3f9..0019ba516 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -29,7 +29,7 @@ overflow-y: scroll; } .red-ui-sidebar-vc-shade { - background: $primary-background; + background: var(--red-ui-primary-background); } .red-ui-projects-edit-form form { @@ -37,7 +37,7 @@ .form-row { margin-bottom: 15px; label { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); width: 100%; display: block; &.red-ui-projects-edit-form-inline-label { @@ -57,7 +57,7 @@ } .red-ui-projects-edit-form-sublabel { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); text-align: right; margin-bottom: -15px; font-weight: normal; @@ -76,7 +76,7 @@ font-size: 1.4em; padding: 10px; min-height: 40px; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-projects-dialog-screen-start-body { min-height: 300px; @@ -132,21 +132,21 @@ margin-left: -1px; padding: 15px; margin-top: -15px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 3px; } .red-ui-projects-dialog-credentials-box-left { width: 220px; > div { padding: 7px 8px 3px 8px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 4px; border-top-right-radius: 0; border-bottom-right-radius: 0; - border-right-color: $form-background; + border-right-color: var(--red-ui-form-background); &.disabled { - border-color: $form-background; - border-right-color:$secondary-border-color; + border-color: var(--red-ui-form-background); + border-right-color:var(--red-ui-secondary-border-color); } i { font-size: 1.4em; @@ -173,7 +173,7 @@ } .red-ui-projects-dialog-project-list-container { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 2px; display: flex; flex-direction: column; @@ -197,43 +197,43 @@ .red-ui-projects-dialog-project-list-entry { padding: 12px 0; - color: $list-item-color; - background: $list-item-background; - border-left: 3px solid $list-item-background; - border-right: 3px solid $list-item-background; + color: var(--red-ui-list-item-color); + background: var(--red-ui-list-item-background); + border-left: 3px solid var(--red-ui-list-item-background); + border-right: 3px solid var(--red-ui-list-item-background); &.projects-list-entry-current { &:not(.selectable) { - color: $form-text-color; - background: $list-item-background-selected; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-selected); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } i { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } &.selectable { cursor: pointer; &:hover:not(.selected) { - color: $form-text-color; - background: $list-item-background-hover; - border-left-color:$list-item-background-hover; - border-right-color:$list-item-background-hover; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-background-hover); + border-right-color:var(--red-ui-list-item-background-hover); } } .red-ui-projects-dialog-project-list-entry-icon { i { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); font-size: 2em; } } &.selected { - color: $form-text-color; - background: $list-item-background-selected; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-selected); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } span { display: inline-block; @@ -249,7 +249,7 @@ float: right; margin-right: 20px; font-size: 0.9em; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); padding-top: 4px; } .red-ui-projects-dialog-project-list-entry-tools { @@ -257,7 +257,7 @@ top: 16px; right: 30px; display: none; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } &:hover { .red-ui-projects-dialog-project-list-entry-tools { @@ -274,7 +274,7 @@ width: 1000px; overflow: hidden; padding: 5px 20px; - background: $secondary-background; + background: var(--red-ui-secondary-background); transition: left 0.4s; white-space: nowrap; > span { @@ -289,7 +289,7 @@ position: relative; } .red-ui-projects-dialog-screen-create-type.red-ui-button.toggle.selected:not(.disabled):not(:disabled) { - color: $secondary-text-color-active !important; + color: var(--red-ui-secondary-text-color-active) !important; } .red-ui-projects-dialog-screen-input-status { text-align: right; @@ -298,7 +298,7 @@ right: 8px; width: 70px; height: 30px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-sidebar-vc { @@ -338,17 +338,17 @@ } .red-ui-palette-module-unused { & > * { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } .red-ui-palette-module-unknown { - border: 1px dashed $secondary-border-color; - background: $secondary-background-inactive; + border: 1px dashed var(--red-ui-secondary-border-color); + background: var(--red-ui-secondary-background-inactive); } .red-ui-palette-module-not-installed { - border: 1px dashed $text-color-warning; + border: 1px dashed var(--red-ui-text-color-warning); i.fa-warning { - color: $text-color-warning; + color: var(--red-ui-text-color-warning); } } } @@ -365,11 +365,11 @@ } .red-ui-sidebar-vc { .red-ui-editableList-container { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 0; li { padding:0; - background: $secondary-background; + background: var(--red-ui-secondary-background); } } .red-ui-editableList-border { @@ -384,7 +384,7 @@ box-sizing: border-box; transition: height 0.2s ease-in-out; &:first-child { - // border-bottom: 1px solid $primary-border-color; + // border-bottom: 1px solid var(--red-ui-primary-border-color); } } .red-ui-sidebar-vc-merging { @@ -399,7 +399,7 @@ right:0; height:0; transition: height 0.2s ease-in-out; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); box-sizing: border-box; overflow: hidden; &.red-ui-sidebar-vc-slide-box-top { @@ -408,20 +408,20 @@ left: auto; width: 100%; max-width: 280px; - border-left: 1px solid $primary-border-color; - border-right: 1px solid $primary-border-color; - border-bottom: 1px solid $primary-border-color; - box-shadow: 1px 1px 4px $shadow; + border-left: 1px solid var(--red-ui-primary-border-color); + border-right: 1px solid var(--red-ui-primary-border-color); + border-bottom: 1px solid var(--red-ui-primary-border-color); + box-shadow: 1px 1px 4px var(--red-ui-shadow); - color: $primary-text-color; - background: $tertiary-background; + color: var(--red-ui-primary-text-color); + background: var(--red-ui-tertiary-background); padding: 10px; box-sizing: border-box; } &.red-ui-sidebar-vc-slide-box-bottom { bottom: 0px; - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } textarea { @@ -437,15 +437,15 @@ .red-ui-projects-branch-list { position: relative; .red-ui-searchBox-container { - border-top: 1px solid $secondary-border-color; - border-left: 1px solid $secondary-border-color; - border-right: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-left: 1px solid var(--red-ui-secondary-border-color); + border-right: 1px solid var(--red-ui-secondary-border-color); border-top-left-radius: 2px; border-top-right-radius: 2px; overflow: hidden; } .red-ui-editableList { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; & > .red-ui-editableList-border { @@ -456,7 +456,7 @@ padding: 0; li { padding: 0; - background: $secondary-background; + background: var(--red-ui-secondary-background); } } } @@ -483,23 +483,23 @@ .red-ui-sidebar-vc-branch-list-entry { padding: 5px 8px; margin: 0 1px; - color: $list-item-color; - background: $list-item-background; - border-left: 2px solid $list-item-background; - border-right: 2px solid $list-item-background; + color: var(--red-ui-list-item-color); + background: var(--red-ui-list-item-background); + border-left: 2px solid var(--red-ui-list-item-background); + border-right: 2px solid var(--red-ui-list-item-background); cursor: pointer; &.selected { - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } i { width: 16px; text-align: center} &.input-error { cursor: default; } &:not(.input-error):hover { - background: $list-item-background-hover; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } span { margin-left: 5px; @@ -507,7 +507,7 @@ span.current { float: right; font-size: 0.8em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } } @@ -542,7 +542,7 @@ } &.red-ui-help-info-node { text-align: center; - background: $list-item-background; + background: var(--red-ui-list-item-background); white-space: normal; height: auto; } @@ -556,63 +556,63 @@ overflow: hidden; cursor: pointer; &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } } .red-ui-sidebar-vc-commit-more { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); text-align: center; padding: 10px; font-style: italic; } .red-ui-sidebar-vc-commit-sha { float: right; - font-family: $monospace-font; - color: $vcCommitShaColor; + font-family: var(--red-ui-monospace-font); + color: var(--red-ui-vcCommitShaColor); display: inline-block; font-size: 0.85em; margin-left: 5px; } .red-ui-sidebar-vc-commit-subject { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-sidebar-vc-commit-refs { min-height: 22px; } .red-ui-sidebar-vc-commit-ref { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); font-size: 0.7em; - border: 1px solid $tertiary-border-color; + border: 1px solid var(--red-ui-tertiary-border-color); border-radius: 10px; padding: 2px 5px; margin-right: 5px; } .red-ui-sidebar-vc-commit-date { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.85em; } .red-ui-sidebar-vc-commit-user { float: right; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.85em; } .red-ui-sidebar-vc-commit-head { } .red-ui-sidebar-vc-change-header { - color: $primary-text-color; - background: $tertiary-background; + color: var(--red-ui-primary-text-color); + background: var(--red-ui-tertiary-background); padding: 4px 10px; height: 30px; box-sizing: border-box; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); i { transition: all 0.2s ease-in-out; } } .red-ui-sidebar-vc-repo-toolbar { - color: $primary-text-color; - background: $tertiary-background; + color: var(--red-ui-primary-text-color); + background: var(--red-ui-tertiary-background); padding: 10px; box-sizing: border-box; } @@ -637,7 +637,7 @@ .red-ui-projects-file-listing-container > .red-ui-editableList > .red-ui-editableList-border { border-radius: 0; border: none; - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } .red-ui-editableList-container .red-ui-projects-dialog-file-list { @@ -654,39 +654,39 @@ } .red-ui-projects-dialog-file-list-entry { padding: 3px 0; - border-left: 2px solid $list-item-background; - border-right: 2px solid $list-item-background; - background: $list-item-background; + border-left: 2px solid var(--red-ui-list-item-background); + border-right: 2px solid var(--red-ui-list-item-background); + background: var(--red-ui-list-item-background); &.projects-list-entry-current { &:not(.selectable) { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); } i { - color: $secondary-text-color-selected; + color: var(--red-ui-secondary-text-color-selected); } } &.selectable { cursor: pointer; &:hover { - background: $list-item-background-hover; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } } &.unselectable { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } i { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); width: 16px; text-align: center; } &.selected { - background: $list-item-background-selected; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + background: var(--red-ui-list-item-background-selected); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } span { display: inline-block; @@ -696,7 +696,7 @@ margin: 0 10px 0 0px; .fa-angle-right { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); transition: all 0.2s ease-in-out; } @@ -747,7 +747,7 @@ div.red-ui-projects-dialog-ssh-public-key { padding: 10px 5px; cursor: pointer; &:hover { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } } } @@ -756,7 +756,7 @@ div.red-ui-projects-dialog-ssh-public-key { position: relative; .red-ui-editableList-container { padding: 1px; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); li:last-child { border-bottom: none; } @@ -775,7 +775,7 @@ div.red-ui-projects-dialog-ssh-public-key { text-align: center; min-width: 30px; vertical-align: top; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .entry-name { min-width: 250px; @@ -784,7 +784,7 @@ div.red-ui-projects-dialog-ssh-public-key { font-weight: bold; } .entry-detail { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); font-size: 0.9em; } @@ -802,9 +802,9 @@ div.red-ui-projects-dialog-ssh-public-key { position: relative; margin-top: 10px; margin-bottom: 20px; - background: $secondary-background; + background: var(--red-ui-secondary-background); border-radius: 4px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); .red-ui-projects-edit-form-sublabel { margin-top: -8px !important; margin-right: 50px; @@ -819,7 +819,7 @@ div.red-ui-projects-dialog-ssh-public-key { .red-ui-projects-dialog-list-dialog-header { font-weight: bold; - background: $primary-background; + background: var(--red-ui-primary-background); margin-top: 0 !important; padding: 5px 10px; margin-bottom: 10px; @@ -830,5 +830,5 @@ div.red-ui-projects-dialog-ssh-public-key { padding: 8px 20px 20px; } .red-ui-settings-section-description { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss index deeabc4fc..3348e945a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss @@ -15,8 +15,8 @@ **/ .red-ui-editor-radial-menu { - font-size: $primary-font-size; - font-family: $primary-font; + font-size: var(--red-ui-primary-font-size); + font-family: var(--red-ui-primary-font); position: absolute; top: 0; left:0; @@ -29,8 +29,8 @@ border-radius: 80px; width: 160px; height: 160px; - background: $shadow; - border: 1px solid $primary-border-color; + background: var(--red-ui-shadow); + border: 1px solid var(--red-ui-primary-border-color); } } @@ -39,20 +39,20 @@ border-radius: 20px; width: 50px; height: 50px; - background: $secondary-background; - border: 2px solid $primary-border-color; + background: var(--red-ui-secondary-background); + border: 2px solid var(--red-ui-primary-border-color); text-align: center; line-height:50px; &.selected { - background: $workspace-button-background-hover; + background: var(--red-ui-workspace-button-background-hover); } } .red-ui-editor-radial-menu-opt-disabled { - border-color: $tertiary-border-color; - color: $tertiary-border-color; + border-color: var(--red-ui-tertiary-border-color); + color: var(--red-ui-tertiary-border-color); } .red-ui-editor-radial-menu-opt-active { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index cce1e69e4..ed4eee8cb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -22,9 +22,9 @@ left: 50%; margin-left: -250px; top: 0px; - border: 1px solid $primary-border-color; - box-shadow: 0 0 10px $shadow; - background: $secondary-background; + border: 1px solid var(--red-ui-primary-border-color); + box-shadow: 0 0 10px var(--red-ui-shadow); + background: var(--red-ui-secondary-background); .red-ui-searchBox-container { display: inline-block; @@ -47,7 +47,7 @@ .red-ui-search-container { border-top-left-radius: 5px; border-top-right-radius: 5px; - border: 1px dashed $primary-border-color; + border: 1px dashed var(--red-ui-primary-border-color); border-bottom: none; padding: 0; width: 100%; @@ -56,8 +56,8 @@ display: none; height: 150px; .red-ui-editableList-container { - border: 1px dashed $primary-border-color; - border-top: 1px solid $secondary-border-color; + border: 1px dashed var(--red-ui-primary-border-color); + border-top: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-search-result { @@ -73,7 +73,7 @@ } } .red-ui-search-result-separator { - border-bottom: 3px solid $secondary-border-color; + border-bottom: 3px solid var(--red-ui-secondary-border-color); } .red-ui-search-result-node { position: relative; @@ -89,7 +89,7 @@ height: 7px; top:4px; left:-4px; - background: $node-port-background; + background: var(--red-ui-node-port-background); box-sizing: border-box; } .red-ui-search-result-node-output{ @@ -107,26 +107,26 @@ margin-left:8px; } .red-ui-search-result-node-label { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } .red-ui-search-container { padding: 3px; - background: $form-input-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-form-input-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-search-results-container { position:relative; height: 300px; padding: 5px; - background: $primary-background; + background: var(--red-ui-primary-background); .red-ui-search-results-list { } .red-ui-editableList-container { padding: 0; - background: $primary-background; + background: var(--red-ui-primary-background); li { padding: 0; } @@ -137,21 +137,21 @@ display: flex; align-items: start; cursor: pointer; - color: $list-item-color; - background: $list-item-background; - border-left: 3px solid $list-item-background; - border-right: 3px solid $list-item-background; + color: var(--red-ui-list-item-color); + background: var(--red-ui-list-item-background); + border-left: 3px solid var(--red-ui-list-item-background); + border-right: 3px solid var(--red-ui-list-item-background); li.selected & { - background: $list-item-background-selected; - border-left-color: $list-item-border-selected; - border-right-color: $list-item-border-selected; + background: var(--red-ui-list-item-background-selected); + border-left-color: var(--red-ui-list-item-border-selected); + border-right-color: var(--red-ui-list-item-border-selected); } &:hover { text-decoration: none; - color: $form-text-color; - background: $list-item-background-hover; - border-left-color:$list-item-background-hover; - border-right-color:$list-item-background-hover; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-background-hover); + border-right-color:var(--red-ui-list-item-background-hover); } &:after { content: ""; @@ -165,7 +165,7 @@ float:left; height: 25px; border-radius: 3px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; background-size: contain; @@ -182,28 +182,28 @@ flex-grow: 1; } .red-ui-search-result-node-label { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-search-result-node-type { font-style: italic; font-size: 0.9em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-search-result-node-flow { float:right; font-size: 0.8em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-search-result-node-id { display:none; font-size: 0.8em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-search-empty { padding: 10px; text-align: center; font-style: italic; - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } .red-ui-search-history { button { @@ -229,12 +229,12 @@ } .red-ui-search-result-action { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-search-result-action-key { position: absolute; top: 9px; right: 0; margin-right: 10px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 18b186bad..21a57d29d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -20,7 +20,7 @@ right: 0px; bottom: 0px; width: 315px; - background: $primary-background; + background: var(--red-ui-primary-background); box-sizing: border-box; z-index: 10; @include component-border; @@ -32,7 +32,7 @@ #red-ui-sidebar-content { position: absolute; - background: $secondary-background; + background: var(--red-ui-secondary-background); top: 35px; right: 0; bottom: 25px; @@ -47,7 +47,7 @@ bottom:10px; width: 7px; // z-index: 11; - background-color: $primary-background; + background-color: var(--red-ui-primary-background); cursor: col-resize; &:before { @@ -63,7 +63,7 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } } @@ -82,11 +82,11 @@ .sidebar-header, /* Deprecated -> red-ui-sidebar-header */ .red-ui-sidebar-header { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); text-align: right; padding: 8px 10px; - background: $primary-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-primary-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; } @@ -138,9 +138,9 @@ button.red-ui-sidebar-header-button-toggle { top: calc(50% - 26px); padding:15px 8px; - border:1px solid $primary-border-color; - background:$primary-background; - color: $secondary-text-color; + border:1px solid var(--red-ui-primary-border-color); + background:var(--red-ui-primary-background); + color: var(--red-ui-secondary-text-color); text-align: center; cursor: pointer; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss b/packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss new file mode 100644 index 000000000..1202d9fb7 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss @@ -0,0 +1,18 @@ +/** +* 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. +**/ + +@import "colors"; +@import "variables"; \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 5c8d0ba94..c8e44e26e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -16,7 +16,7 @@ .red-ui-sidebar-node-config { position: relative; - background: $secondary-background; + background: var(--red-ui-secondary-background); height: 100%; overflow-y:auto; @include disable-selection; @@ -40,11 +40,11 @@ ul.red-ui-sidebar-node-config-list { &.selected { border-color: transparent; - box-shadow: 0 0 0 2px $node-selected-color; + box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); } &.highlighted { border-color: transparent; - outline: dashed $node-selected-color 4px; + outline: dashed var(--red-ui-node-selected-color 4px); } } .red-ui-palette-label { @@ -58,7 +58,7 @@ ul.red-ui-sidebar-node-config-list { .red-ui-palette-icon-container { font-size: 12px; line-height: 30px; - background-color: $node-icon-background-color; + background-color: var(--red-ui-node-icon-background-color); border-top-right-radius: 4px; border-bottom-right-radius: 4px; a { @@ -67,10 +67,10 @@ ul.red-ui-sidebar-node-config-list { bottom: 0; left: 0; right: 0; - color: $node-port-label-color; + color: var(--red-ui-node-port-label-color); &:hover { text-decoration: none; - background: $node-port-background-hover; + background: var(--red-ui-node-port-background-hover); } } } @@ -78,12 +78,12 @@ ul.red-ui-sidebar-node-config-list { .red-ui-palette-node-config { width: 160px; height: 30px; - background: $node-config-background; - color: $primary-text-color; + background: var(--red-ui-node-config-background); + color: var(--red-ui-primary-text-color); cursor: pointer; } ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); text-align: right; padding-right: 3px; &:not(:first-child) { @@ -91,21 +91,21 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { } } .red-ui-palette-node-config-none { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); text-align:right; padding-right: 3px; } .red-ui-palette-node-config-unused,.red-ui-palette-node-config-disabled { - border-color: $primary-border-color; - background: $secondary-background-inactive; + border-color: var(--red-ui-primary-border-color); + background: var(--red-ui-secondary-background-inactive); border-style: dashed; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-palette-node-config-disabled { opacity: 0.6; font-style: italic; i { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); margin-right: 5px; } } @@ -116,8 +116,8 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { height: 38px; line-height: 38px; padding: 0 8px; - background: $palette-header-background; + background: var(--red-ui-palette-header-background); font-size: 0.8em; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-weight: normal; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss index b98d452c1..fc4c78afb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss @@ -63,12 +63,12 @@ .red-ui-sidebar-context-updated { text-align: right; font-size: 11px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); padding: 1px 3px; } .red-ui-sidebar-context-property-storename { display: block; font-size: 0.8em; font-style: italic; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss index fe4f9fb84..f82b97116 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss @@ -2,7 +2,7 @@ // height: calc(100% - 39px); } .red-ui-help-search { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-sidebar-help-toc { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss index 9c63f2119..57dc7d6e3 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss @@ -32,7 +32,7 @@ display: inline-block; margin-left: 5px; } - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } table.red-ui-info-table { font-size: 14px; @@ -40,8 +40,8 @@ table.red-ui-info-table { width: 100%; } table.red-ui-info-table tr:not(.blank) { - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-help-property-expand { font-size: 0.8em; @@ -57,7 +57,7 @@ table.red-ui-info-table tr.blank { th { text-align: left; font-weight: 500; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); padding: 6px 3px 3px; } >* { @@ -69,9 +69,9 @@ table.red-ui-info-table tr.blank { a { display: block; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); &:hover,&:focus { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); text-decoration: none; } &:not(.expanded) { @@ -103,36 +103,36 @@ table.red-ui-info-table tr.blank { } .red-ui-help-info-none { font-style: italic; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } table.red-ui-info-table tr:not(.blank) td:first-child{ - color: $header-text-color; + color: var(--red-ui-header-text-color); vertical-align: top; width: 90px; padding: 3px 3px 3px 6px; - background:$tertiary-background; - border-right: 1px solid $secondary-border-color; + background:var(--red-ui-tertiary-background); + border-right: 1px solid var(--red-ui-secondary-border-color); } table.red-ui-info-table tr:not(.blank) td:last-child{ padding: 3px 3px 3px 6px; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); overflow-y: hidden; } div.red-ui-info-table { margin: 5px; } .red-ui-help { - font-size: $primary-font-size; + font-size: var(--red-ui-primary-font-size); line-height: 1.5em; a { - color: $text-color-link; + color: var(--red-ui-text-color-link); text-decoration: none; } a:hover, a:focus { - color: $text-color-link; + color: var(--red-ui-text-color-link); text-decoration: underline; } @@ -143,7 +143,7 @@ div.red-ui-info-table { line-height: 1.3em; margin: 8px auto; &.red-ui-help-title { - border-bottom: 1px solid $tertiary-border-color; + border-bottom: 1px solid var(--red-ui-tertiary-border-color); } } h2 { @@ -168,24 +168,24 @@ div.red-ui-info-table { & > span > p:first-child { } dl.message-properties { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 2px; margin: 5px auto 10px; &>dt { padding: 0px 3px 2px 3px; - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); font-weight: normal; margin: 5px 3px 1px; - color: $text-color-code; + color: var(--red-ui-text-color-code); white-space: nowrap; &.optional { font-style: italic; } .property-type { - font-family: $primary-font; - color: $primary-text-color; + font-family: var(--red-ui-primary-font); + color: var(--red-ui-primary-text-color); font-style: italic; font-size: 11px; float: right; @@ -204,7 +204,7 @@ div.red-ui-info-table { ol.node-ports { margin: 0; li { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 2px; list-style-position: inside; padding: 3px; @@ -224,7 +224,7 @@ div.red-ui-info-table { transition: transform 0.2s ease-in-out; margin-right: 4px; } - color: $header-text-color; + color: var(--red-ui-header-text-color); &:hover, &:focus { text-decoration: none; } @@ -242,7 +242,7 @@ div.red-ui-info-table { overflow : hidden; } table thead tr { - background-color: var(--red-ui-primary-background); //$primary-text-color; + background-color: var(--red-ui-primary-background); //var(--red-ui-primary-text-color); border-bottom: 1px solid var(--red-ui-secondary-border-color); color: var(--red-ui-header-text-color); text-align: left; @@ -252,7 +252,7 @@ div.red-ui-info-table { padding: 6px 8px; } table tbody tr:nth-of-type(even) { - background-color: var(--red-ui-tertiary-background); //$primary-background; + background-color: var(--red-ui-tertiary-background); //var(--red-ui-primary-background); } } .red-ui-sidebar-info-stack { @@ -273,10 +273,10 @@ div.red-ui-info-table { height: 0; transition: height 0.2s, padding 0.2s; box-sizing: border-box; - border-top: 1px solid $secondary-border-color; - background-color: $secondary-background; + border-top: 1px solid var(--red-ui-secondary-border-color); + background-color: var(--red-ui-secondary-background); padding: 0; - box-shadow: 0 5px 20px 0px $shadow; + box-shadow: 0 5px 20px 0px var(--red-ui-shadow); overflow-y: auto; } .red-ui-sidebar-info.show-tips { @@ -305,7 +305,7 @@ div.red-ui-info-table { font-size: 16px; text-align: center; line-height: 1.9em; - color : $tertiary-text-color; + color : var(--red-ui-tertiary-text-color); @include disable-selection; cursor: default; } @@ -314,14 +314,14 @@ div.red-ui-info-table { top: 4px; right: 6px; a { - color: $secondary-text-color; - border-color: $secondary-border-color !important; + color: var(--red-ui-secondary-text-color); + border-color: var(--red-ui-secondary-border-color) !important; margin-left: 4px; } } .node-info-property-config-node { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 4px; padding: 2px 4px 2px; } @@ -346,7 +346,7 @@ div.red-ui-info-table { } .red-ui-info-outline-project { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-info-outline, @@ -380,13 +380,13 @@ div.red-ui-info-table { background: none; } .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 18px; } } &.red-ui-info-outline-item-empty { font-style: italic; - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } } @@ -414,7 +414,7 @@ div.red-ui-info-table { white-space: nowrap; } .red-ui-search-result-node-label { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } @@ -439,16 +439,16 @@ div.red-ui-info-table { right: 1px; padding: 1px 2px 0 1px; text-align: right; - background: $list-item-background; + background: var(--red-ui-list-item-background); .red-ui-treeList-label:hover & { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } .red-ui-treeList-label.focus & { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } .red-ui-treeList-label.selected & { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); } @@ -510,7 +510,7 @@ div.red-ui-info-table { } .red-ui-info-outline-item-label { font-style: italic; - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } .red-ui-icons-flow { opacity: 0.4; @@ -538,7 +538,7 @@ div.red-ui-info-table { -webkit-mask-size: contain; mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat; - background-color: $icons-flow-color; + background-color: var(--red-ui-icons-flow-color); // filter: brightness(2.5); } @@ -549,8 +549,8 @@ div.red-ui-info-table { text-align: left; // padding-left: 9px; // box-sizing: border-box; - // background: $palette-header-background; - // border-bottom: 1px solid $secondary-border-color; + // background: var(--red-ui-palette-header-background); + // border-bottom: 1px solid var(--red-ui-secondary-border-color); .red-ui-searchBox-container { position: absolute; @@ -558,7 +558,7 @@ div.red-ui-info-table { right: 8px; width: calc(100% - 130px); max-width: 250px; - background: $palette-header-background; + background: var(--red-ui-palette-header-background); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index a5d3003ae..595423888 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -16,7 +16,7 @@ .red-ui-tabs { position: relative; - background: $tab-background; + background: var(--red-ui-tab-background); overflow: hidden; height: 35px; box-sizing: border-box; @@ -39,18 +39,18 @@ display: block; height: 35px; box-sizing:border-box; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); white-space: nowrap; @include disable-selection; li { box-sizing: border-box; display: inline-block; - border-left: 1px solid $primary-border-color; - border-top: 1px solid $primary-border-color; - border-right: 1px solid $primary-border-color; - border-bottom: 1px solid $primary-border-color; - background: $tab-background-inactive; + border-left: 1px solid var(--red-ui-primary-border-color); + border-top: 1px solid var(--red-ui-primary-border-color); + border-right: 1px solid var(--red-ui-primary-border-color); + border-bottom: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-tab-background-inactive); margin: 3px 3px 0 3px; height: 32px; line-height: 29px; @@ -73,7 +73,7 @@ padding-left: 12px; width: 100%; height: 100%; - color: $tab-text-color-inactive; + color: var(--red-ui-tab-text-color-inactive); } a:hover { text-decoration: none; @@ -83,27 +83,27 @@ } &:not(.active) a:hover+a.red-ui-tab-close { - background: $tab-background-hover; + background: var(--red-ui-tab-background-hover); } &.highlighted { - box-shadow: 0px 0px 4px 2px $node-selected-color; - border: dashed 1px $node-selected-color; + box-shadow: 0px 0px 4px 2px var(--red-ui-node-selected-color); + border: dashed 1px var(--red-ui-node-selected-color); } &.active { - background: $tab-background-active; + background: var(--red-ui-tab-background-active); font-weight: bold; - border-bottom: 1px solid $tab-background-active; + border-bottom: 1px solid var(--red-ui-tab-background-active); z-index: 2; a { - color: $tab-text-color-active; + color: var(--red-ui-tab-text-color-active); } a.red-ui-tab-close { - color: $workspace-button-color; - background: $tab-background-active; + color: var(--red-ui-workspace-button-color); + background: var(--red-ui-tab-background-active); &:hover { - background: $workspace-button-background-hover !important; - color: $workspace-button-color-hover; + background: var(--red-ui-workspace-button-background-hover) !important; + color: var(--red-ui-workspace-button-color-hover); } } img.red-ui-tab-icon { @@ -111,24 +111,24 @@ } .red-ui-tabs-fade { - background-image: linear-gradient(to right, change-color($tab-background-active, $alpha: 0.001), $tab-background-active); + background-image: linear-gradient(to right, var(--red-ui-tab-background-active-alpha), var(--red-ui-tab-background-active)); } } &.selected { &:not(.active) { - background: $tab-background-selected; + background: var(--red-ui-tab-background-selected); .red-ui-tabs-fade { - background-image: linear-gradient(to right, change-color($tab-background-selected, $alpha: 0.001), $tab-background-selected); + background-image: linear-gradient(to right, var(--red-ui-tab-background-selected-alpha), var(--red-ui-tab-background-selected)); } .red-ui-tabs-badge-selected { - background: $tab-background-selected; + background: var(--red-ui-tab-background-selected); } } font-weight: bold; .red-ui-tabs-badge-selected { display: inline; - background: $tab-background; + background: var(--red-ui-tab-background); } .red-ui-tabs-badge-changed { display: none; @@ -136,10 +136,10 @@ } &:not(.active) a:hover { - color: $workspace-button-color-hover; - background: $tab-background-hover; + color: var(--red-ui-workspace-button-color-hover); + background: var(--red-ui-tab-background-hover); &+.red-ui-tabs-fade { - background-image: linear-gradient(to right, change-color($tab-background-hover, $alpha: 0.001), $tab-background-hover); + background-image: linear-gradient(to right, var(--red-ui-tab-background-hover-alpha), var(--red-ui-tab-background-hover)); } } } @@ -182,9 +182,9 @@ &.red-ui-tabs-vertical { box-sizing: border-box; height: 100%; - border-right: 1px solid $primary-border-color; + border-right: 1px solid var(--red-ui-primary-border-color); margin: 0; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); overflow: visible; .red-ui-tabs-scroll-container { @@ -203,13 +203,13 @@ display: block; margin: 0; border: none; - border-right: 1px solid $primary-border-color; + border-right: 1px solid var(--red-ui-primary-border-color); height: auto; &:not(:first-child) { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } &:last-child { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } a.red-ui-tab-label { @@ -217,7 +217,7 @@ } &.active { - border-right: 1px solid $tab-background-active; + border-right: 1px solid var(--red-ui-tab-background-active); } } } @@ -238,8 +238,8 @@ top: 0; right: 0; height: 35px; - background: $tab-background; - border-bottom: 1px solid $primary-border-color; + background: var(--red-ui-tab-background); + border-bottom: 1px solid var(--red-ui-primary-border-color); z-index: 2; a { @@ -261,8 +261,8 @@ top: 0; right: 0; height: 35px; - background: $tab-background; - border-bottom: 1px solid $primary-border-color; + background: var(--red-ui-tab-background); + border-bottom: 1px solid var(--red-ui-primary-border-color); z-index: 2; a { @include workspace-button-toggle; @@ -272,7 +272,7 @@ margin: 4px 3px 3px; z-index: 2; &.red-ui-tab-link-button-menu { - border-color: $tab-background; + border-color: var(--red-ui-tab-background); } &:not(.single):not(.selected) { margin-top: 4px; @@ -286,27 +286,27 @@ height: 35px; width: 21px; display: block; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); font-size: 22px; text-align: center; margin:0; border-left: none; border-right: none; border-top: none; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); line-height: 34px; } } .red-ui-tab-scroll-left { left:0; a { - border-right: 1px solid $primary-border-color; + border-right: 1px solid var(--red-ui-primary-border-color); } } .red-ui-tab-scroll-right { right: 0px; a { - border-left: 1px solid $primary-border-color; + border-left: 1px solid var(--red-ui-primary-border-color); } } @@ -341,7 +341,7 @@ top: 0; right: 0; width: 15px; - background-image: linear-gradient(to right, change-color($tab-background-inactive, $alpha: 0.001), $tab-background-inactive); + background-image: linear-gradient(to right, var(--red-ui-tab-background-inactive-alpha), var(--red-ui-tab-background-inactive)); pointer-events: none; } @@ -365,7 +365,7 @@ i.red-ui-tab-icon { mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $tab-icon-color; + background-color: var(--red-ui-tab-icon-color); } } .red-ui-tabs-badges { @@ -379,7 +379,7 @@ i.red-ui-tab-icon { line-height: 28px; text-align: center; padding:0px; - color: $tab-badge-color; + color: var(--red-ui-tab-badge-color); } .red-ui-tabs-badges i { @@ -415,7 +415,7 @@ i.red-ui-tab-icon { } .red-ui-tab-close { display: none; - background: $tab-background-inactive; + background: var(--red-ui-tab-background-inactive); opacity: 0.8; position: absolute; right: 0px; @@ -425,10 +425,10 @@ i.red-ui-tab-icon { line-height: 28px; text-align: center; padding: 0px; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); &:hover { - background: $workspace-button-background-hover !important; - color: $workspace-button-color-hover; + background: var(--red-ui-workspace-button-background-hover) !important; + color: var(--red-ui-workspace-button-color-hover); opacity: 1; } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss b/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss index f8d175d44..a22c07f7f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss @@ -97,7 +97,7 @@ color: var(--red-ui-primary-text-color) !important; } &:not(.primary):not(.disabled):not(.ui-button-disabled):hover { - border-color: $popover-button-border-color-hover; + border-color: var(--red-ui-popover-button-border-color-hover); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss index a606bba8c..47929378b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss @@ -16,7 +16,7 @@ .red-ui-checkboxSet { width: 15px; display: inline-block; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); cursor: pointer; input { display:none !important; @@ -24,6 +24,6 @@ &.disabled { pointer-events: none; - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss index a5873f684..00b79b54a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss @@ -14,10 +14,10 @@ * limitations under the License. **/ .red-ui-editableList-border { - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); border-radius: 4px; .red-ui-editableList-header { - border-bottom: 1px solid $form-input-border-color; + border-bottom: 1px solid var(--red-ui-form-input-border-color); padding: 2px 16px 2px 4px; font-size: 0.9em; } @@ -32,22 +32,22 @@ margin: 0; } .red-ui-editabelList-item-placeholder { - border: 2px dashed $secondary-border-color !important; + border: 2px dashed var(--red-ui-secondary-border-color) !important; } li { box-sizing: border-box; position: relative; - background: $secondary-background; + background: var(--red-ui-secondary-background); margin:0; padding:8px 0px; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); min-height: 20px; .red-ui-editableList-item-handle { position: absolute; top: 50%; left: 2px; margin-top: -7px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); cursor: move; } .red-ui-editableList-item-remove { @@ -57,7 +57,7 @@ margin-top: -9px; } &.ui-sortable-helper { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } //.red-ui-editableList-item-content { outline: 1px solid red} @@ -68,7 +68,7 @@ margin-right: 28px; } &.red-ui-editableList-item-deleting { - background: $secondary-background-inactive; + background: var(--red-ui-secondary-background-inactive); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss index 01a7a4802..44745e87c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss @@ -21,9 +21,9 @@ margin: 0; white-space: nowrap; border: none; - background: $secondary-background; + background: var(--red-ui-secondary-background); &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } i.fa-angle-right { @@ -44,12 +44,12 @@ } } .red-ui-editableList-item-content.disabled { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } &.red-ui-editableList-section-header { - background: $primary-background; + background: var(--red-ui-primary-background); .red-ui-editableList-item-content.disabled { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss index b925e5212..8788ed6a7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss @@ -21,7 +21,7 @@ position: absolute; top: 9px; font-size: 10px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } i.fa-search { pointer-events: none; @@ -41,8 +41,8 @@ margin: 0; } a.red-ui-searchBox-opts:hover { - color: $workspace-button-color-hover; - background: $workspace-button-background-hover; + color: var(--red-ui-workspace-button-color-hover); + background: var(--red-ui-workspace-button-background-hover); } input.red-ui-searchBox-input { border-radius: 0; @@ -76,8 +76,8 @@ position: absolute; right: 18px; top: 4px; - background: $primary-background; - color: $secondary-text-color; + background: var(--red-ui-primary-background); + color: var(--red-ui-secondary-text-color); padding: 1px 8px; font-size: 9px; border-radius: 4px; @@ -97,12 +97,12 @@ .red-ui-searchBox-compact { input:focus.red-ui-searchBox-input { - outline: 1px solid $form-input-focus-color; + outline: 1px solid var(--red-ui-form-input-focus-color); } input.red-ui-searchBox-input,input:focus.red-ui-searchBox-input { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 3px; font-size: 12px; height: 26px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss index a32bfad70..82d697b33 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss @@ -15,9 +15,9 @@ **/ .red-ui-stack { - background: $secondary-background; + background: var(--red-ui-secondary-background); .red-ui-palette-category { - background: $secondary-background; + background: var(--red-ui-secondary-background); &:last-child { border-bottom: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss index 5f1d27037..f076a533e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss @@ -24,9 +24,9 @@ width: 100%; height: 100%; position: relative; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); border-radius: 4px; box-sizing: border-box; @@ -71,7 +71,7 @@ padding: 6px 0; display: flex; align-items: center; - color: $list-item-color; + color: var(--red-ui-list-item-color); text-decoration: none; cursor: pointer; vertical-align: middle; @@ -79,26 +79,26 @@ position: relative; &:hover, &:hover .red-ui-treeList-sublabel-text { - background: $list-item-background-hover; - color: $list-item-color; + background: var(--red-ui-list-item-background-hover); + color: var(--red-ui-list-item-color); text-decoration: none; } &:focus, &:focus .red-ui-treeList-sublabel-text { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); outline: none; - color: $list-item-color; + color: var(--red-ui-list-item-color); text-decoration: none; } &.focus, &.focus .red-ui-treeList-sublabel-text { - background: $list-item-background-hover; - outline: 1px solid $form-input-focus-color !important; + background: var(--red-ui-list-item-background-hover); + outline: 1px solid var(--red-ui-form-input-focus-color) !important; outline-offset: -1px; - color: $list-item-color; + color: var(--red-ui-list-item-color); } &.selected, &.selected .red-ui-treeList-sublabel-text { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); outline: none; - color: $list-item-color; + color: var(--red-ui-list-item-color); } input.red-ui-treeList-checkbox, @@ -121,9 +121,9 @@ padding: 0 10px 0 5px; line-height: 32px; font-size: 0.9em; - color: $list-item-secondary-color; + color: var(--red-ui-list-item-secondary-color); position: absolute; - background: $list-item-background; + background: var(--red-ui-list-item-background); } @@ -143,5 +143,5 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $spinner-color; + background-color: var(--red-ui-spinner-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index 76345e980..1a421fac5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -15,7 +15,7 @@ **/ .red-ui-typedInput-container { - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); border-radius: 5px; height: 34px; line-height: 14px; @@ -28,7 +28,7 @@ position: relative; &[disabled] { input, button { - background: $secondary-background-inactive; + background: var(--red-ui-secondary-background-inactive); pointer-events: none; cursor: not-allowed; } @@ -50,7 +50,7 @@ } &.red-ui-typedInput-focus:not(.input-error) { - border-color: $form-input-focus-color !important; + border-color: var(--red-ui-form-input-focus-color) !important; } .red-ui-typedInput-value-label { flex-grow: 1; @@ -61,42 +61,42 @@ overflow: hidden; text-overflow: ellipsis; .red-ui-typedInput-value-label-inactive { - background: $secondary-background-disabled; - color: $secondary-text-color-disabled; + background: var(--red-ui-secondary-background-disabled); + color: var(--red-ui-secondary-text-color-disabled); } } } .red-ui-typedInput-options { @include component-shadow; - font-family: $primary-font; - font-size: $primary-font-size; + font-family: var(--red-ui-primary-font); + font-size: var(--red-ui-primary-font-size); position: absolute; max-height: 350px; overflow-y: auto; - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); box-sizing: border-box; - background: $secondary-background; + background: var(--red-ui-secondary-background); white-space: nowrap; z-index: 2000; a { padding: 6px 18px 6px 6px; display: flex; align-items: center; - border-bottom: 1px solid $secondary-border-color; - color: $form-text-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); + color: var(--red-ui-form-text-color); &:hover { text-decoration: none; - background: $workspace-button-background-hover; + background: var(--red-ui-workspace-button-background-hover); } &:focus { text-decoration: none; - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); outline: none; } &:active { text-decoration: none; - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); } input[type="checkbox"] { margin: 0 6px 0 0; @@ -111,7 +111,7 @@ mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $primary-text-color; + background-color: var(--red-ui-primary-text-color); height: 14px; width: 12px; } @@ -128,11 +128,11 @@ button.red-ui-typedInput-option-trigger border-top-left-radius: 4px; border-bottom-left-radius: 4px; padding: 0 1px 0 5px; - background: $form-button-background; + background: var(--red-ui-form-button-background); height: 32px; line-height: 30px; vertical-align: middle; - color: $form-text-color; + color: var(--red-ui-form-text-color); white-space: nowrap; i.red-ui-typedInput-icon { margin-left: 1px; @@ -142,7 +142,7 @@ button.red-ui-typedInput-option-trigger &.disabled { cursor: default; > i.red-ui-typedInput-icon { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } } .red-ui-typedInput-type-label,.red-ui-typedInput-option-label { @@ -161,21 +161,21 @@ button.red-ui-typedInput-option-trigger mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $primary-text-color; + background-color: var(--red-ui-primary-text-color); } } &:not(.disabled):hover { text-decoration: none; - background: $workspace-button-background-hover; + background: var(--red-ui-workspace-button-background-hover); } &:focus { text-decoration: none; outline: none; - box-shadow: inset 0 0 0 1px $form-input-focus-color; + box-shadow: inset 0 0 0 1px var(--red-ui-form-input-focus-color); } &:not(.disabled):active { - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); text-decoration: none; } &.red-ui-typedInput-full-width { @@ -208,8 +208,8 @@ button.red-ui-typedInput-option-trigger { line-height: 32px; display: inline-flex; .red-ui-typedInput-option-label { - background:$form-button-background; - color: $form-text-color; + background:var(--red-ui-form-button-background); + color: var(--red-ui-form-text-color); flex-grow: 1; padding: 0 0 0 8px; display:inline-block; @@ -231,6 +231,6 @@ button.red-ui-typedInput-option-trigger { box-shadow: none; } &:focus .red-ui-typedInput-option-caret { - box-shadow: inset 0 0 0 1px $form-input-focus-color; + box-shadow: inset 0 0 0 1px var(--red-ui-form-input-focus-color); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss index 5cf0570c2..36ab67e3f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss @@ -20,7 +20,7 @@ left: 0; bottom: 0; width: 120px; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } .red-ui-settings-tabs-content { position: absolute; @@ -30,7 +30,7 @@ bottom: 0; padding: 0; h3:not(:first-child) { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); margin-top: 15px; margin-bottom: 10px; padding-top: 20px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss index aadc2231e..abd31629c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss @@ -1,4 +1,5 @@ :root { + --red-ui-primary-font: #{$primary-font}; --red-ui-primary-font-size: #{$primary-font-size}; --red-ui-monospace-font: #{$monospace-font}; @@ -40,7 +41,6 @@ --red-ui-text-color-link: #{$text-color-link}; - --red-ui-primary-border-color: #{$primary-border-color}; --red-ui-secondary-border-color: #{$secondary-border-color}; --red-ui-tertiary-border-color: #{$tertiary-border-color}; @@ -50,20 +50,38 @@ --red-ui-border-color-success: #{$border-color-success}; --red-ui-form-background: #{$form-background}; - --red-ui-form-placeholder-color: #{$form-placeholder-color}; --red-ui-form-text-color: #{$form-text-color}; --red-ui-form-text-color-disabled: #{$form-text-color-disabled}; + --red-ui-form-input-focus-color: #{$form-input-focus-color}; --red-ui-form-input-border-color: #{$form-input-border-color}; - --red-ui-form-input-border-color-focus: #{$form-input-focus-color}; - --red-ui-form-input-border-color-selected: #{$form-input-border-selected-color}; - --red-ui-form-input-border-color-error: #{$form-input-border-error-color}; + --red-ui-form-input-border-selected-color: #{$form-input-border-selected-color}; + --red-ui-form-input-border-error-color: #{$form-input-border-error-color}; --red-ui-form-input-background: #{$form-input-background}; --red-ui-form-input-background-disabled: #{$form-input-background-disabled}; --red-ui-form-button-background: #{$form-button-background}; --red-ui-form-tips-background: #{$form-tips-background}; + + --red-ui-text-editor-color: #{$text-editor-color}; + --red-ui-text-editor-background: #{$text-editor-background}; + --red-ui-text-editor-color-disabled: #{$text-editor-color-disabled}; + --red-ui-text-editor-background-disabled: #{$text-editor-background-disabled}; + --red-ui-text-editor-gutter-background: #{$text-editor-gutter-background}; + --red-ui-text-editor-gutter-color: #{$text-editor-gutter-color}; + --red-ui-text-editor-gutter-active-line-background: #{$text-editor-gutter-active-line-background}; + --red-ui-text-editor-active-line-background: #{$text-editor-active-line-background}; + --red-ui-text-editor-selection-background: #{$text-editor-selection-background}; + + --red-ui-event-log-background: #{$event-log-background}; + --red-ui-event-log-color: #{$event-log-color}; + --red-ui-event-log-active-line-background: #{$event-log-active-line-background}; + --red-ui-event-log-selection-background: #{$event-log-selection-background}; + + + + --red-ui-list-item-color: #{$list-item-color}; --red-ui-list-item-secondary-color: #{$list-item-secondary-color}; --red-ui-list-item-background: #{$list-item-background}; @@ -72,8 +90,120 @@ --red-ui-list-item-background-selected: #{$list-item-background-selected}; --red-ui-list-item-border-selected: #{$list-item-border-selected}; + --red-ui-tab-text-color-active: #{$tab-text-color-active}; + --red-ui-tab-text-color-inactive: #{$tab-text-color-inactive}; + --red-ui-tab-text-color-disabled-active: #{$tab-text-color-disabled-active}; + --red-ui-tab-text-color-disabled-inactive: #{$tab-text-color-disabled-inactive}; + + --red-ui-tab-badge-color: #{$tab-badge-color}; + --red-ui-tab-background: #{$tab-background}; + --red-ui-tab-background-active: #{$tab-background-active}; + --red-ui-tab-background-active-alpha: #{$tab-background-active-alpha}; + --red-ui-tab-background-selected: #{$tab-background-selected}; + --red-ui-tab-background-selected-alpha: #{$tab-background-selected-alpha}; + --red-ui-tab-background-inactive: #{$tab-background-inactive}; + --red-ui-tab-background-inactive-alpha: #{$tab-background-inactive-alpha}; + --red-ui-tab-background-hover: #{$tab-background-hover}; + --red-ui-tab-background-hover-alpha: #{$tab-background-hover-alpha}; + + --red-ui-palette-header-background: #{$palette-header-background}; + --red-ui-palette-header-color: #{$palette-header-color}; + --red-ui-palette-content-background: #{$palette-content-background}; + + + --red-ui-workspace-button-background: #{$workspace-button-background}; + --red-ui-workspace-button-background-hover: #{$workspace-button-background-hover}; + --red-ui-workspace-button-background-active: #{$workspace-button-background-active}; + + --red-ui-workspace-button-color: #{$workspace-button-color}; + --red-ui-workspace-button-color-disabled: #{$workspace-button-color-disabled}; + --red-ui-workspace-button-color-focus: #{$workspace-button-color-focus}; + --red-ui-workspace-button-color-hover: #{$workspace-button-color-hover}; + --red-ui-workspace-button-color-active: #{$workspace-button-color-active}; + --red-ui-workspace-button-color-selected: #{$workspace-button-color-selected}; + + --red-ui-workspace-button-color-primary: #{$workspace-button-color-primary}; + --red-ui-workspace-button-background-primary: #{$workspace-button-background-primary}; + --red-ui-workspace-button-background-primary-hover: #{$workspace-button-background-primary-hover}; + + --red-ui-workspace-button-color-focus-outline: #{$workspace-button-color-focus-outline}; + --red-ui-shade-color: #{$shade-color}; + --red-ui-popover-background: #{$popover-background}; + --red-ui-popover-border: #{$popover-border}; + --red-ui-popover-color: #{$popover-color}; + --red-ui-popover-button-border-color: #{$popover-button-border-color}; + --red-ui-popover-button-border-color-hover: #{$popover-button-border-color-hover}; + + + + --red-ui-diff-text-header-color: #{$diff-text-header-color}; + --red-ui-diff-text-header-background: #{$diff-text-header-background}; + --red-ui-diff-state-color: #{$diff-state-color}; + --red-ui-diff-state-prefix-color: #{$diff-state-prefix-color}; + --red-ui-diff-state-added: #{$diff-state-added}; + --red-ui-diff-state-deleted: #{$diff-state-deleted}; + --red-ui-diff-state-changed: #{$diff-state-changed}; + --red-ui-diff-state-changed-background: #{$diff-state-changed-background}; + --red-ui-diff-state-unchanged: #{$diff-state-unchanged}; + --red-ui-diff-state-unchanged-background: #{$diff-state-unchanged-background}; + + --red-ui-diff-state-conflicted: #{$diff-state-conflicted}; + --red-ui-diff-state-moved: #{$diff-state-moved}; + --red-ui-diff-state-conflict: #{$diff-state-conflict}; + --red-ui-diff-state-conflict-background: #{$diff-state-conflict-background}; + + --red-ui-diff-state-added-background: #{$diff-state-added-background}; + --red-ui-diff-state-added-border: #{$diff-state-added-border}; + --red-ui-diff-state-added-header-background: #{$diff-state-added-header-background}; + --red-ui-diff-state-added-header-border: #{$diff-state-added-header-border}; + + --red-ui-diff-state-deleted-background: #{$diff-state-deleted-background}; + --red-ui-diff-state-deleted-border: #{$diff-state-deleted-border}; + --red-ui-diff-state-deleted-header-background: #{$diff-state-deleted-header-background}; + --red-ui-diff-state-deleted-header-border: #{$diff-state-deleted-header-border}; + + --red-ui-diff-merge-header-color: #{$diff-merge-header-color}; + --red-ui-diff-merge-header-background: #{$diff-merge-header-background}; + --red-ui-diff-merge-header-border-color: #{$diff-merge-header-border-color}; + + --red-ui-menuBackground: #{$menuBackground}; + --red-ui-menuDivider: #{$menuDivider}; + --red-ui-menuColor: #{$menuColor}; + --red-ui-menuActiveColor: #{$menuActiveColor}; + --red-ui-menuActiveBackground: #{$menuActiveBackground}; + --red-ui-menuDisabledColor: #{$menuDisabledColor}; + --red-ui-menuHoverColor: #{$menuHoverColor}; + --red-ui-menuHoverBackground: #{$menuHoverBackground}; + --red-ui-menuCaret: #{$menuCaret}; + + --red-ui-view-navigator-background: #{$view-navigator-background}; + + --red-ui-view-lasso-stroke: #{$view-lasso-stroke}; + --red-ui-view-lasso-fill: #{$view-lasso-fill}; + + --red-ui-view-background: #{$view-background}; + --red-ui-view-grid-color: #{$view-grid-color}; + + --red-ui-node-label-color: #{$node-label-color}; + --red-ui-node-port-label-color: #{$node-port-label-color}; + --red-ui-node-border: #{$node-border}; + --red-ui-node-border-unknown: #{$node-border-unknown}; + --red-ui-node-border-placeholder: #{$node-border-placeholder}; + --red-ui-node-background-placeholder: #{$node-background-placeholder}; + + --red-ui-node-port-background: #{$node-port-background}; + --red-ui-node-port-background-hover: #{$node-port-background-hover}; + --red-ui-node-icon-color: #{$node-icon-color}; + --red-ui-node-icon-background-color: #{$node-icon-background-color}; + --red-ui-node-icon-background-color-fill: #{$node-icon-background-color-fill}; + --red-ui-node-icon-background-color-opacity: #{$node-icon-background-color-opacity}; + --red-ui-node-icon-border-color: #{$node-icon-border-color}; + --red-ui-node-icon-border-color-opacity: #{$node-icon-border-color-opacity}; + + --red-ui-node-config-background: #{$node-config-background}; + --red-ui-node-link-port-background: #{$node-link-port-background}; --red-ui-node-status-error-border: #{$node-status-error-border}; @@ -81,18 +211,87 @@ --red-ui-node-status-changed-border: #{$node-status-changed-border}; --red-ui-node-status-changed-background: #{$node-status-changed-background}; - --red-ui-node-border: #{$node-border}; - --red-ui-node-port-background:#{$node-port-background}; + @each $current-color in red green yellow blue grey gray { + --red-ui-node-status-colors-#{"" + $current-color}: #{map-get($node-status-colors, $current-color)}; + } + - --red-ui-node-label-color: #{$node-label-color}; --red-ui-node-selected-color: #{$node-selected-color}; --red-ui-port-selected-color: #{$port-selected-color}; - --red-ui-popover-background: #{$popover-background}; - --red-ui-popover-border: #{$popover-border}; - --red-ui-popover-color: #{$popover-color}; + --red-ui-link-color: #{$link-color}; + --red-ui-link-link-color: #{$link-link-color}; + --red-ui-link-disabled-color: #{$link-disabled-color}; + --red-ui-link-link-active-color: #{$link-link-active-color}; + --red-ui-link-unknown-color: #{$link-unknown-color}; + + --red-ui-clipboard-textarea-background: #{$clipboard-textarea-background}; + + + --red-ui-deploy-button-color: #{$deploy-button-color}; + --red-ui-deploy-button-color-active: #{$deploy-button-color-active}; + --red-ui-deploy-button-color-disabled: #{$deploy-button-color-disabled}; + --red-ui-deploy-button-background: #{$deploy-button-background}; + --red-ui-deploy-button-background-hover: #{$deploy-button-background-hover}; + --red-ui-deploy-button-background-active: #{$deploy-button-background-active}; + --red-ui-deploy-button-background-disabled: #{$deploy-button-background-disabled}; + --red-ui-deploy-button-background-disabled-hover: #{$deploy-button-background-disabled-hover}; + + + --red-ui-header-background: #{$header-background}; + --red-ui-header-button-background-active: #{$header-button-background-active}; + --red-ui-header-menu-color: #{$header-menu-color}; + --red-ui-header-menu-color-disabled: #{$header-menu-color-disabled}; + --red-ui-header-menu-heading-color: #{$header-menu-heading-color}; + --red-ui-header-menu-sublabel-color: #{$header-menu-sublabel-color}; + --red-ui-header-menu-background: #{$header-menu-background}; + --red-ui-header-menu-item-hover: #{$header-menu-item-hover}; + --red-ui-header-menu-item-border-active: #{$header-menu-item-border-active}; + --red-ui-headerMenuItemDivider: #{$headerMenuItemDivider}; + --red-ui-headerMenuCaret: #{$headerMenuCaret}; + + --red-ui-vcCommitShaColor: #{$vcCommitShaColor}; + + --red-ui-dnd-background: #{$dnd-background}; + --red-ui-dnd-color: #{$dnd-color}; + + --red-ui-notification-border-default: #{$notification-border-default}; + --red-ui-notification-border-success: #{$notification-border-success}; + --red-ui-notification-border-warning: #{$notification-border-warning}; + --red-ui-notification-border-error: #{$notification-border-error}; + + --red-ui-debug-message-background: #{$debug-message-background}; + --red-ui-debug-message-background-hover: #{$debug-message-background-hover}; + + --red-ui-debug-message-text-color: #{$debug-message-text-color}; + --red-ui-debug-message-text-color-meta: #{$debug-message-text-color-meta}; + --red-ui-debug-message-text-color-object-key: #{$debug-message-text-color-object-key}; + --red-ui-debug-message-text-color-msg-type-other: #{$debug-message-text-color-msg-type-other}; + --red-ui-debug-message-text-color-msg-type-string: #{$debug-message-text-color-msg-type-string}; + --red-ui-debug-message-text-color-msg-type-null: #{$debug-message-text-color-msg-type-null}; + --red-ui-debug-message-text-color-msg-type-meta: #{$debug-message-text-color-msg-type-meta}; + --red-ui-debug-message-text-color-msg-type-number: #{$debug-message-text-color-msg-type-number}; + + --red-ui-debug-message-border: #{$debug-message-border}; + --red-ui-debug-message-border-hover: #{$debug-message-border-hover}; + --red-ui-debug-message-border-warning: #{$debug-message-border-warning}; + --red-ui-debug-message-border-error: #{$debug-message-border-error}; + + --red-ui-group-default-fill: #{$group-default-fill}; + --red-ui-group-default-fill-opacity: #{$group-default-fill-opacity}; + --red-ui-group-default-stroke: #{$group-default-stroke}; + --red-ui-group-default-stroke-opacity: #{$group-default-stroke-opacity}; + --red-ui-group-default-label-color: #{$group-default-label-color}; --red-ui-tourGuide-border: #{$tourGuide-border}; --red-ui-tourGuide-heading-color: #{$tourGuide-heading-color}; + --red-ui-grip-color: #{$grip-color}; + + --red-ui-icons-flow-color: #{$icons-flow-color}; + + --red-ui-spinner-color: #{$spinner-color}; + + --red-ui-tab-icon-color: #{$tab-icon-color}; + } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index d594337de..24e156b1e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -47,12 +47,12 @@ } .red-ui-workspace-chart-background { - fill: $view-background; + fill: var(--red-ui-view-background); } .red-ui-workspace-chart-grid line { fill: none; shape-rendering: crispEdges; - stroke: $view-grid-color; + stroke: var(--red-ui-view-grid-color); stroke-width: 1px; } .red-ui-workspace-select-mode { @@ -94,11 +94,11 @@ a { font-style: italic; - color: $tab-text-color-disabled-inactive !important; + color: var(--red-ui-tab-text-color-disabled-inactive) !important; } &.active a { font-weight: normal; - color: $tab-text-color-disabled-active !important; + color: var(--red-ui-tab-text-color-disabled-active) !important; } .red-ui-workspace-disabled-icon { display: inline; @@ -112,17 +112,17 @@ bottom: 0; right:0; z-index: 101; - border-left: 1px solid $primary-border-color; - border-top: 1px solid $primary-border-color; - background: $view-navigator-background; - box-shadow: -1px 0 3px $shadow; + border-left: 1px solid var(--red-ui-primary-border-color); + border-top: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-view-navigator-background); + box-shadow: -1px 0 3px var(--red-ui-shadow); } .red-ui-navigator-border { stroke-dasharray: 5,5; pointer-events: none; - stroke: $secondary-border-color; + stroke: var(--red-ui-secondary-border-color); stroke-width: 1; - fill: $view-background; + fill: var(--red-ui-view-background); } .red-ui-component-footer { @@ -182,7 +182,7 @@ button.red-ui-footer-button-toggle { #red-ui-loading-progress { position: absolute; - background: $primary-background; + background: var(--red-ui-primary-background); top: 0; bottom: 0; right: 0; @@ -196,7 +196,7 @@ button.red-ui-footer-button-toggle { width: 300px; height:80px; text-align: center; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } @@ -204,13 +204,13 @@ button.red-ui-footer-button-toggle { box-sizing: border-box; width: 300px; height: 30px; - border: 2px solid $primary-border-color; + border: 2px solid var(--red-ui-primary-border-color); border-radius: 4px; > span { display: block; height: 100%; - background: $secondary-border-color; + background: var(--red-ui-secondary-border-color); transition: width 0.2s; width: 10%; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss index 2a734eb43..d0b0370ab 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss @@ -17,7 +17,7 @@ #red-ui-workspace-toolbar { display: none; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); font-size: 12px; line-height: 18px; position: absolute; @@ -27,8 +27,8 @@ padding: 7px; height: 40px; box-sizing: border-box; - background: $secondary-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-secondary-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; transition: right 0.2s ease; overflow: hidden; @@ -61,23 +61,23 @@ } } .button.active { - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); cursor: default; } } .spinner-value { width: 25px; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); padding: 0 5px; display: inline-block; text-align: center; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); margin: 0; height: 24px; font-size: 12px; - background: $form-input-background; + background: var(--red-ui-form-input-background); line-height: 22px; box-sizing: border-box; } diff --git a/scripts/build-custom-theme.js b/scripts/build-custom-theme.js index e16b35e8b..3a904fea8 100644 --- a/scripts/build-custom-theme.js +++ b/scripts/build-custom-theme.js @@ -83,7 +83,7 @@ while((match = ruleRegex.exec(colorsFile)) !== null) { const result = sass.renderSync({ outputStyle: "expanded", - file: path.join(workingDir,"style.scss"), + file: path.join(workingDir,"style-custom-theme.scss"), }); const css = result.css.toString() From ad0a08ea0e7dde0684e97545a0fd52c33c7a6316 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 20 Jun 2022 00:44:20 +0900 Subject: [PATCH 102/195] Add Japanese translations in action list for project feature --- .../@node-red/editor-client/locales/ja/editor.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index ac09a1e87..2849070bf 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1331,6 +1331,10 @@ "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", "show-system-info": "システム情報", - "split-wires-with-junctions": "分岐点によりワイヤーを分割" + "split-wires-with-junctions": "分岐点によりワイヤーを分割", + "new-project": "新しいプロジェクト", + "open-project": "プロジェクトを開く", + "show-project-settings": "プロジェクト設定を表示", + "show-version-control-tab": "バージョンコントロールタブを表示" } } From 3fbbfce17cd9ef011b3dcd37a9376f08821963a9 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Mon, 20 Jun 2022 13:28:10 +0200 Subject: [PATCH 103/195] Fix typo in german translation --- .../node_modules/@node-red/editor-client/locales/de/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index dd931a812..ba3007f51 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -1106,7 +1106,7 @@ "no-resource": "Repository nicht gefunden", "cant-get-ssh-key-path": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden.", "unexpected_error": "unerwarteter_Fehler", - "clearContext": "Kontextdaten löscshen beim Projektwechsel" + "clearContext": "Kontextdaten löschen beim Projektwechsel" }, "delete": { "confirm": "Sind Sie sicher, dass dieses Projekt gelöscht werden soll?" From 9729c89f5dc30181765aef276a6daf151f687482 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 20 Jun 2022 18:25:41 +0100 Subject: [PATCH 104/195] ensure link-call cache is updated when link-in is modified fixes #3694 depends on node-red-node-test-helper@0.3.0 --- package.json | 2 +- .../@node-red/nodes/core/common/60-link.js | 17 +++----- test/nodes/core/common/60-link_spec.js | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 1343af50c..715d09a5b 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "marked": "4.0.17", "minami": "1.2.3", "mocha": "9.2.2", - "node-red-node-test-helper": "^0.2.7", + "node-red-node-test-helper": "^0.3.0", "nodemon": "2.0.16", "proxy": "^1.0.2", "sass": "1.52.3", diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 24572f8b1..08bad7a62 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -109,16 +109,13 @@ module.exports = function(RED) { }, remove(node) { const target = generateTarget(node); - const tn = this.getTarget(target.name, target.flowId); - if (tn) { - const targs = this.getTargets(tn.name); - const idx = getIndex(targs, tn.id); - if (idx > -1) { - targs.splice(idx, 1); - } - if (targs.length === 0) { - delete registry.name[tn.name]; - } + const targs = this.getTargets(target.name); + const idx = getIndex(targs, target.id); + if (idx > -1) { + targs.splice(idx, 1); + } + if (targs.length === 0) { + delete registry.name[tn.name]; } delete registry.id[target.id]; }, diff --git a/test/nodes/core/common/60-link_spec.js b/test/nodes/core/common/60-link_spec.js index 63733455c..be7ffc39f 100644 --- a/test/nodes/core/common/60-link_spec.js +++ b/test/nodes/core/common/60-link_spec.js @@ -17,6 +17,7 @@ var should = require("should"); var linkNode = require("nr-test-utils").require("@node-red/nodes/core/common/60-link.js"); var helper = require("node-red-node-test-helper"); +var clone = require("clone"); describe('link Node', function() { @@ -319,6 +320,48 @@ describe('link Node', function() { linkCall.receive({ payload: "hello", target: "double payload" }); }); }) + it('should not raise error after deploying a name change to a duplicate link-in node', async function () { + this.timeout(400); + const flow = [ + { id: "tab-flow-1", type: "tab", label: "Flow 1" }, + { id: "link-in-1", z: "tab-flow-1", type: "link in", name: "duplicate", wires: [["link-out-1"]] }, + { id: "link-in-2", z: "tab-flow-1", type: "link in", name: "duplicate", wires: [["link-out-1"]] }, //duplicate name + { id: "link-out-1", z: "tab-flow-1", type: "link out", mode: "return" }, + { id: "link-call", z: "tab-flow-1", type: "link call", linkType: "dynamic", links: [], wires: [["n4"]] }, + { id: "n4", z: "tab-flow-1", type: "helper" } + ]; + + await helper.load(linkNode, flow) + + const linkIn2before = helper.getNode("link-in-2"); + linkIn2before.should.have.property("name", "duplicate") // check link-in-2 has been deployed with the duplicate name + + //modify the flow and deploy change + const newConfig = clone(flow); + newConfig[2].name = "add" // change nodes name + await helper.setFlows(newConfig, "nodes") // deploy "nodes" only + + const helperNode = helper.getNode("n4"); + const linkCall2 = helper.getNode("link-call"); + const linkIn2after = helper.getNode("link-in-2"); + linkIn2after.should.have.property("name", "add") // check link-in-2 no longer has a duplicate name + + //poke { payload: "hello", target: "add" } into the link-call node and + //ensure that a message arrives via the link-in node named "add" + await new Promise((resolve, reject) => { + helperNode.on("input", function (msg) { + try { + msg.should.have.property("target", "add"); + msg.should.not.have.property("error"); + resolve() + } catch (err) { + reject(err); + } + }); + linkCall2.receive({ payload: "hello", target: "add" }); + }); + + }) it('should allow nested link-call flows', function(done) { this.timeout(500); var flow = [/** Multiply by 2 link flow **/ From 202102ebf7374e6697d9c807457f9bc0c31099cd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 20 Jun 2022 20:51:31 +0100 Subject: [PATCH 105/195] Fix clicking on node in workspace to hide context menu --- .../editor-client/src/js/ui/contextMenu.js | 3 ++- .../@node-red/editor-client/src/js/ui/view.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 9920789f5..0b388aff3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -170,6 +170,7 @@ RED.contextMenu = (function() { } return { - show: show + show: show, + hide: disposeMenu } })() diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 522b86cbc..b55244d0f 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -988,6 +988,7 @@ RED.view = (function() { if (RED.view.DEBUG) { console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); } + RED.contextMenu.hide(); if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); return; @@ -1779,6 +1780,9 @@ RED.view = (function() { } var i; var historyEvent; + if (d3.event.button === 2) { + return + } if (mouse_mode === RED.state.PANNING) { resetMouseVars(); return @@ -2903,6 +2907,7 @@ RED.view = (function() { function portMouseDown(d,portType,portIndex, evt) { if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); } + RED.contextMenu.hide(); evt = evt || d3.event; if (evt === 1) { return; @@ -3411,6 +3416,7 @@ RED.view = (function() { function nodeMouseDown(d) { if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } focusView(); + RED.contextMenu.hide(); if (d3.event.button === 1) { return; } @@ -3793,6 +3799,7 @@ RED.view = (function() { if (RED.view.DEBUG) { console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); } + RED.contextMenu.hide(); if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); return; @@ -3852,6 +3859,9 @@ RED.view = (function() { } function groupMouseUp(g) { + if (RED.view.DEBUG) { + console.warn("groupMouseUp", { mouse_mode, event: d3.event }); + } if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; RED.editor.editGroup(g); @@ -3867,6 +3877,10 @@ RED.view = (function() { // return // } + if (RED.view.DEBUG) { + console.warn("groupMouseDown", { mouse_mode, point: mouse, event: d3.event }); + } + RED.contextMenu.hide(); focusView(); if (d3.event.button === 1) { return; From 10835968fb3f714d971ea78bc48be41378bc7f9f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 21 Jun 2022 21:47:57 +0900 Subject: [PATCH 106/195] add access to previous tours --- .../editor-client/src/js/ui/tab-help.js | 23 +- .../editor-client/src/js/ui/tour/tourGuide.js | 26 ++ .../editor-client/src/tours/2.1/welcome.js | 229 ++++++++++++++++++ .../src/tours/2.2/images/delete-repair.gif | Bin 0 -> 29584 bytes .../src/tours/2.2/images/detach-repair.gif | Bin 0 -> 71182 bytes .../src/tours/2.2/images/slice.gif | Bin 0 -> 83214 bytes .../src/tours/2.2/images/subflow-labels.png | Bin 0 -> 30101 bytes .../editor-client/src/tours/2.2/welcome.js | 156 ++++++++++++ 8 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/delete-repair.gif create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/detach-repair.gif create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/slice.gif create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/subflow-labels.png create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index 154b64364..c6b33dbc7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -97,7 +97,10 @@ RED.sidebar.help = (function() { var pendingContentLoad; treeList.on('treelistselect', function(e,item) { pendingContentLoad = item; - if (item.nodeType) { + if (item.tour) { + RED.tourGuide.run(item.tour); + } + else if (item.nodeType) { showNodeTypeHelp(item.nodeType); } else if (item.content) { helpSection.empty(); @@ -198,15 +201,27 @@ RED.sidebar.help = (function() { label: RED._("sidebar.help.nodeHelp"), children: [], expanded: true - } + }; + var tours = RED.tourGuide.list().map(function (item) { + return { + icon: "fa fa-repeat", + label: item.label, + tour: item.path, + }; + }); var helpData = [ { id: 'changelog', label: "Node-RED v"+RED.settings.version, content: getChangelog }, - nodeHelp - ] + nodeHelp, + { + id: "tours", + label: "Tours", + children: tours + }, + ]; var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)}); if (subflows.length > 0) { nodeHelp.children.push({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 31612cfeb..49b960402 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -433,9 +433,35 @@ RED.tourGuide = (function() { }) } + function listTour() { + return [ + { + id: "2_3", + label: "3.0.0-beta.3 (latest)", + path: "./tours/welcome.js" + }, + { + id: "2_2", + label: "2.2.0", + path: "./tours/2.2/welcome.js" + }, + { + id: "2_1", + label: "2.1.0", + path: "./tours/2.1/welcome.js" + }, + { + id: "first_flow", + label: "First flow", + path: "./tours/first-flow.js" + }, + ]; + } + return { load: loadTour, run: run, + list: listTour, reset: function() { RED.settings.set("editor.tours.welcome",''); } diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js new file mode 100644 index 000000000..8a4565bab --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js @@ -0,0 +1,229 @@ +export default { + version: "2.1.0", + steps: [ + { + titleIcon: "fa fa-map-o", + title: { + "en-US": "Welcome to Node-RED 2.1!", + "ja": "Node-RED 2.1へようこそ!" + }, + description: { + "en-US": "Let's take a moment to discover the new features in this release.", + "ja": "本リリースの新機能を見つけてみましょう。" + } + }, + { + title: { + "en-US": "A new Tour Guide", + "ja": "新しいツアーガイド" + }, + description: { + "en-US": "

        First, as you've already found, we now have this tour of new features. We'll only show the tour the first time you open the editor for each new version of Node-RED.

        " + + "

        You can choose not to see this tour in the future by disabling it under the View tab of User Settings.

        ", + "ja": "

        最初に、既に見つけている様に、新機能の本ツアーがあります。本ツアーは、新バージョンのNode-REDフローエディタを初めて開いた時のみ表示されます。

        " + + "

        ユーザ設定の表示タブの中で、この機能を無効化することで、本ツアーを表示しないようにすることもできます。

        " + } + }, + { + title: { + "en-US": "New Edit menu", + "ja": "新しい編集メニュー" + }, + prepare() { + $("#red-ui-header-button-sidemenu").trigger("click"); + $("#menu-item-edit-menu").parent().addClass("open"); + }, + complete() { + $("#menu-item-edit-menu").parent().removeClass("open"); + }, + element: "#menu-item-edit-menu-submenu", + interactive: false, + direction: "left", + description: { + "en-US": "

        The main menu has been updated with a new 'Edit' section. This includes all of the familar options, like cut/paste and undo/redo.

        " + + "

        The menu now displays keyboard shortcuts for the options.

        ", + "ja": "

        メインメニューに「編集」セクションが追加されました。本セクションには、切り取り/貼り付けや、変更操作を戻す/やり直しの様な使い慣れたオプションが含まれています。

        " + + "

        本メニューには、オプションのためのキーボードショートカットも表示されるようになりました。

        " + } + }, + { + title: { + "en-US": "Arranging nodes", + "ja": "ノードの配置" + }, + prepare() { + $("#red-ui-header-button-sidemenu").trigger("click"); + $("#menu-item-arrange-menu").parent().addClass("open"); + }, + complete() { + $("#menu-item-arrange-menu").parent().removeClass("open"); + }, + element: "#menu-item-arrange-menu-submenu", + interactive: false, + direction: "left", + description: { + "en-US": "

        The new 'Arrange' section of the menu provides new options to help arrange your nodes. You can align them to a common edge, spread them out evenly or change their order.

        ", + "ja": "

        メニューの新しい「配置」セクションには、ノードの配置を助ける新しいオプションが提供されています。ノードの端を揃えたり、均等に配置したり、表示順序を変更したりできます。

        " + } + }, + { + title: { + "en-US": "Hiding tabs", + "ja": "タブの非表示" + }, + element: "#red-ui-workspace-tabs > li.active", + description: { + "en-US": '

        Tabs can now be hidden by clicking their icon.

        The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.', + "ja": '

        アイコンをクリックすることで、タブを非表示にできます。

        情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。' + }, + interactive: false, + prepare() { + $("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","block"); + }, + complete() { + $("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display",""); + } + }, + { + title: { + "en-US": "Tab menu", + "ja": "タブメニュー" + }, + element: "#red-ui-workspace-tabs-menu", + description: { + "en-US": "

        The new tab menu also provides lots of new options for your tabs.

        ", + "ja": "

        新しいタブメニューには、タブに関する沢山の新しいオプションが提供されています。

        " + }, + interactive: false, + direction: "left", + prepare() { + $("#red-ui-workspace > .red-ui-tabs > .red-ui-tabs-menu a").trigger("click"); + }, + complete() { + $(document).trigger("click"); + } + }, + { + title: { + "en-US": "Flow and Group level environment variables", + "ja": "フローとグループの環境変数" + }, + element: "#red-ui-workspace-tabs > li.active", + interactive: false, + description: { + "en-US": "

        Flows and Groups can now have their own environment variables that can be referenced by nodes inside them.

        ", + "ja": "

        フローとグループには、内部のノードから参照できる環境変数を設定できるようになりました。

        " + } + }, + { + prepare(done) { + RED.editor.editFlow(RED.nodes.workspace(RED.workspaces.active()),"editor-tab-envProperties"); + setTimeout(done,700); + }, + element: "#red-ui-tab-editor-tab-envProperties-link-button", + description: { + "en-US": "

        Their edit dialogs have a new Environment Variables section.

        ", + "ja": "

        編集ダイアログに環境変数セクションが追加されました。

        " + } + }, + { + element: ".node-input-env-container-row", + direction: "left", + description: { + "en-US": '

        The environment variables are listed in this table and new ones can be added by clicking the button.

        ', + "ja": '

        この表に環境変数が一覧表示されており、ボタンをクリックすることで新しい変数を追加できます。

        ' + }, + complete(done) { + $("#node-dialog-cancel").trigger("click"); + setTimeout(done,500); + } + }, + { + title: { + "en-US": "Link Call node added", + "ja": "Link Callノードを追加" + }, + prepare(done) { + this.paletteWasClosed = $("#red-ui-main-container").hasClass("red-ui-palette-closed"); + RED.actions.invoke("core:toggle-palette",true) + $('[data-palette-type="link call"]')[0].scrollIntoView({block:"center"}) + setTimeout(done,100); + }, + element: '[data-palette-type="link call"]', + direction: "right", + description: { + "en-US": "

        The Link Call node lets you call another flow that begins with a Link In node and get the result back when the message reaches a Link Out node.

        ", + "ja": "

        Link Callノードを用いることで、Link Inノードから始まるフローを呼び出し、Link Outノードに到達した時に、結果を取得できます。

        " + } + }, + { + title: { + "en-US": "MQTT nodes support dynamic connections", + "ja": "MQTTノードが動的接続をサポート" + }, + prepare(done) { + $('[data-palette-type="mqtt out"]')[0].scrollIntoView({block:"center"}) + setTimeout(done,100); + }, + element: '[data-palette-type="mqtt out"]', + direction: "right", + description: { + "en-US": '

        The MQTT nodes now support creating their connections and subscriptions dynamically.

        ', + "ja": '

        MQTTノードは、動的な接続や購読ができるようになりました。

        ' + }, + }, + { + title: { + "en-US": "File nodes renamed", + "ja": "ファイルノードの名前変更" + }, + prepare(done) { + $('[data-palette-type="file"]')[0].scrollIntoView({block:"center"}); + setTimeout(done,100); + }, + complete() { + if (this.paletteWasClosed) { + RED.actions.invoke("core:toggle-palette",false) + } + }, + element: '[data-palette-type="file"]', + direction: "right", + description: { + "en-US": "

        The file nodes have been renamed to make it clearer which node does what.

        ", + "ja": "

        fileノードの名前が変更され、どのノードが何を行うかが明確になりました。

        " + } + }, + { + title: { + "en-US": "Deep copy option on Change node", + "ja": "Changeノードのディープコピーオプション" + }, + prepare(done) { + var def = RED.nodes.getType('change'); + RED.editor.edit({id:"test",type:"change",rules:[{t:"set",p:"payload",pt:"msg", tot:"msg",to:"anotherProperty"}],_def:def, _:def._}); + setTimeout(done,700); + }, + complete(done) { + $("#node-dialog-cancel").trigger("click"); + setTimeout(done,500); + }, + element: function() { + return $(".node-input-rule-property-deepCopy").next(); + }, + description: { + "en-US": "

        The Set rule has a new option to create a deep copy of the value. This ensures a complete copy is made, rather than using a reference.

        ", + "ja": "

        値を代入に、値のディープコピーを作成するオプションが追加されました。これによって参照ではなく、完全なコピーが作成されます。

        " + } + }, + { + title: { + "en-US": "And that's not all...", + "ja": "これが全てではありません..." + }, + description: { + "en-US": "

        There are many more smaller changes, including:

        • Auto-complete suggestions in the msg TypedInput.
        • Support for msg.resetTimeout in the Join node.
        • Pushing messages to the front of the queue in the Delay node's rate limiting mode.
        • An optional second output on the Delay node for rate limited messages.
        ", + "ja": "

        以下の様な小さな変更が沢山あります:

        • msg TypedInputの自動補完提案
        • Joinノードでmsg.resetTimeoutのサポート
        • Delayノードの流量制御モードにおいて先頭メッセージをキューに追加
        • Delayノードで流量制限されたメッセージ向けの任意の2つ目の出力
        " + } + } + ] +} diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/delete-repair.gif b/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/delete-repair.gif new file mode 100644 index 0000000000000000000000000000000000000000..c668dfdee52b73e9ae2f21e3d9969986d4b526a5 GIT binary patch literal 29584 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 JgF8Bf{{x*;*Yp4Y literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/detach-repair.gif b/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/detach-repair.gif new file mode 100644 index 0000000000000000000000000000000000000000..14fe1a423b89eb8203701c9000472bcdaf4adbd1 GIT binary patch literal 71182 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�*z7z&RM04@g?#i74f{e851N%b#woRBmTvqjDks(^NRky2L=W40)Xy zdTgtasu0DhlU|w_yz2v$ryI)3RQ|mh_shU~BgQF2UN_QN6<U5`hG}{)FY*Z7KYzHuN5y@0>)q~la z%qXf67KRGJL!hVf%V7}qMfYeZEm6e)Dj+Bi)05_-Zk)JT`3A-@1eA}W`R4-@u%#6r z4GFzHJOA3i2EF^=Hn0+xVfBKr>dDFuhW3=!API|6|8M(Qmw+9tGPql?R%*AqUadB` zn4S9U$B8Ny#jM)0aYEQ?S+dO2g58;TZD|0PbZGv&RLWBLpJ|~lvX?p9PsX=bK7AR5 zKKDo>tDLFRHR!y?yn95VW9RFfMJWI2;=}QLA`}=y=Up{=TgxXr?qJvZf_bB)$922v zx|_}OzrVjvU1R;}`%3cO&rh$Il)i86Z*sd{(tdu8aD&>M+5ddFv^#xe|KN4#L+k2c zEgAv#`%hyWI}@+HvB3~{$MwD4-%dLK7RkPqzzpDH0~#pIdSn{!&d38Jtu0&(-=N5E zT))9q6dO1A`A}{&&YcS;{GkR0urgS+7MEi)@)-z)#D3%zxP(BKx%@gK0|AgRlGCGx zmt6K50{EM@;trjU1t=`WUJ`nOC{uC=d#Kj5&|57tSS&6pbB&l}5i&w5T24#cp$WKv zciJ$mum9n1)sTV?-TGop)_ zNvF7Zh$(rDX2^fah@LYi0smfu{@qo~40wJEeyjK&>(l@3_pSmWfY{$>LT_-3P3JN= z))hadqHew1pMd7&=ki&nHyUwz3#|;$N0{9@2&Yrcm6u5e>0b0|NS?6<+J63cb7b{t1o<6SPK5@#_ejC!+$QR;Q~I*(-8JZe~izE#Cj3g_>QrU9YS)L^ue zT7k;=?Q+HphdI8s%$#3i(ca{m^ZYS0!kG^iH6%O)hA9=baI)vuv-`FB3U&)PRVYh7&&W>lb-lCi-soFkUcl&Z9vy6G)?}eGBQK&2dypJoz z--vFii&2|16yLl*uVFmYws`6NW;^4m?kITU7I;K_T~&X#b1nV-qwX!5Uxxn2Cw}*O zzVzw-(f4y3JQMD|s6XEaMe8Z`0n!ayG>m4?`F>_ze`5cC76e@dgu#jA*+2B{O{86} zOmr11zmrR2sZmryyt_kf5ceJ@;y4}=L5;Iw7@VkaV^up6 zw2Z%yfYXZ}VdhT(cmD^;-yEE21t{>^O9?+d5pR1a01zvV5jZi%ORmXQH-?PZFDeM~ zn9vS0ag>p6n!%wIgwaNvK?$XwSA@&SAmtM&DjJ;8*Z-Q)#PEcCF2J^^;3MxVYC2hl zN8*PE1A0ILrIz2VOo`@zx%b(e&CTaimd-2|s&|mIFAtXClX%}q&S^bw&cfB@UXsZH zPdAh-|FUg)g9VUv>zn}qX}`f>^i6u{vpZgwabCO2O{DoCv)&ES{PDhDbFl4(i)8u+ zEzbAlY>CwQt5M~|oYYUboVFxqNYvwR9B6SFE#ed!v1ou*7r~QKv8;(6`Qqt|neki> z9jjRXthBH=95W>=8Zxy1zOW-vFv~?`3c{tgBe0a=g!q_9wuYlQ=aArFY#i z3GT7;fh95rS6FSLo!6d-9<0jwwnIVl$~vKJPI&Fu3*Lr61PcU)R-$Bl^p6)BRtDwQ z8Z^g$jJl)>P=K_B0FagD)*2}&3+v1|M#U0Vz{QaVPi+hSor5E94JE+c2Fmp8O2|-7 zDW>LF{_6e{0OZ5bjuaTkOFGNz9WBoRl07 zS~C(4z4nr(hWzHsj$*zT8)T$JOAUrGOCo1eWb3qoVd7cv>S9q{zTipaDRWOz;#~d{ z4x_x*paJo;j$u?{XspV(0w0U_IHA0OkRqEG`_WLMX-DjoP$}*LI$dE|EHgv+{*H%~ z#@xKPu?s8Qi_cuX$t>IM^w_jy0)bY3-J|N+Ot$;!XY`wa!!p%H|Mfu%?UU@kAEXvN zM<$wyUDiztciq4miZ#_6Z7gdrN_aChK(KmBJXRGS*{T)95Ow@c^l6l~ocrgAIuG8& zW3RxIg63lY0FUH6z4t`rh3E7L4jzNdVHuHGSYO}W} z7y__=76n5--`)jZfLuP#3ILb1E^qeVy3e)ujER;Dni5PbOGLZTEb*{Y01!9T^NsHs z9?$i!wE$cS@;@pq>kaN+!XDSyym+yUw>!{%;j0>g7RbA8&XvbHXgS}#!q(iYlXIC9 zF1WuGFzqh|g#D%9leto!T_#C)dxL(;?WgZ772;WxNmAbDoLo4(k(M+c-9f1XHw|HH z%K`*Y553HxsPO$EunUGECA|zLPirpDSSi&pI9NCh1|1;&+&j=%t|pfY)~Yke*$IWY z9Vx8xQyXz>8~lmjL=K(#0fPWTtz~n<$y$$bhtMZZ*Z&uJe;N+;|NnpEvzZwT#y(kR ztV3w*gc|#(p%IlzHI_=K#!`f8m|@1grXiIrq0LsPlzmH#C8;EP5}G8b-sOBzecpY4 zpYQ*||2)nM=W(2ei!O0IUeD+Aaev(Jx5wo$0p=6cID9!Y#!M@BNqG9hIrGsOkHn`2@D0qGFn&V&${{dbJSNUM=B+I|-b@NHF2pn-mFra1$|4_a~8v zH_EftcWzw7wl{i376+cHl6pY1%r5pmhGJ@L)I{b;2p84iX)Kw$A_DA>H0&%rOPoJ0 zHamq1<%8*sUM65?o3Cs#4&#?S#juXmY7QN#IXv%Hd8{!ETmauCQ@+20l6*gUyQHm4 zL11#@HMF!n1HJ*Gm~*&2 zL5OL1j{cQpV(_QNX{-h zSY2#!!EWxofrreltZb84xt1?#490ibzPk5;!LKXu82`{CU;+b${QG=%04@nSNOk?9 z`0=&!=#Jb522wRb=j6o|}dE*1}o1&z|cT2ES`z7T!`?BTMTKIIl3(&$>Tzit{ zOb~5mbFnD3x8~2Iy77e<+k(UmSQ7~gIzV{6KyHV+pmzn zmKWAuT=@2&F>3p6D<(Wv!6G|Z!)Lr%$joNnQl2RzI4|4r!M*ucH9_x3o<1->alnuA z4@upbjoNb6x?1WfqTl>1_p{|h|F={AZ-y%U-gNKZDTry-eYyH7&r>LG3#;$Op&om= zhST$Kr?9G@luOn%YhS_R$E&L#IDV84lk&mRIitB&p>p%|s53Lq_PjA)uJ0!iVEoo^ z!oEyZzllW5BF5P&VjKYXmAGf*jM(j?+bou=`q^q312uVJ@^Ld)lThU^Fyz0ZLK0n)3)1Y6Q z98@NMp!wvT6dDl*-&QIUm9qz851%hWHMJqc3c=(|rG>T$T+s|8628n7jR)4L z=`t$J-&@2#igl;ptk2|;PepH_5y5*YL9QBkg5g3jihg8tW!Ewn|8xKbMUB({3%GB=I*Oe7fexfOJv z!}0l~vmsI{e)y^|WU-D^FuGg2K&ABRxh9Stsf;xMCG&$}L@UD2!jyXLYHF0C&SBxe z_NrB3hJ!yiT;`?@V};;we=zrfqn4FPP?v5q8fZVfy>&Yu%+((4(M=-nymLzmUq*xh zno3LNFHObdMm^M>15FlNhGFSjeTMqUFu~udcgvd2((p@I@z_rwwY?A)FEyxxiIa6( zX8hB z40*9FRQxL$Y8qN8UD9wWx)sguL+uuVx~K4o@a3d*XKf10x3T= zCP`4*0o{D@sj&=~P6&N|?+ss1=qZB2;y=^uC5p1#6u!+$$xYbPpz*-xtb=7S9$+C- z0UAk#=swH@Z3FmRNi>-GBVcZI?L#y{j&33@&FOlMp?;uJw@p@0E3&lZnO!YKqo+X$hspS@qDi6AtVbi5b|j0 z)}6Cc#ZaMmAVf*BmgpcsS3-GBSmAQEy+N+nm)7PwZGyy(;EPHpEYTs}`F8uesM6ri zw#V>|AR|1&z=&d}sx>N_KWc?wJ43?V8x(UZ?d3^t6S4I;K4DI)pa2cwOPPQQVBsQi zSOIs?1XK#-4KAV8*PZb~P4!(eGQq*Dea2(o?e$hxw!B6P>^- zvY6VWw?an_dZ11fH(^?_OkG;kw)bD5I>_-vlABA~&~UCu3Izph;^mVmYmoOx#~C~% zO4QZ!iEgfEd`ZQgRiU+pl4wyD5o7Q=bSwTueWaLoB<9A02JHw3n4&}zyROMrHYSCS zy=_UripQspDrd;Mj|s$#Hrw7>VtJswWfiVvEvfP24@>l3?BleWuyyxPDFxDX6h0lR z1}3&SAM)-7eG?rU?dCRtc?t{z!aSaTcVJ*FUA2mURjLoey2#lzO_=*KyF)N4NWFR{ z9v|7e1V}xnqu`cFse?^F+9bk+GBsUw<6f?gTS*qxyO!w$AXR7T7364F(K(}RN`hxe z*k{V|<2MxDVH(xlbvL!{t0l-z<|+t)wG~UN^7@GrViCEmzWd*+2rb)J_qSw(J%6I; z?j99rMtLionZp0jDxvP(flXnrWM4n=+$J$7nG-5k1%F=rR*dist~%!_;%JcXw8!&( z(DuTsBbpvaYnl!D`*w?WQXLYTiM@11Z|Y}uh}!Z>YXa6^kw2uyre;^O^a2<8aN)}# z1vN3YZC9#l)^&%^Q%PCHJF-Z86AWY^bNg+nw%JTzoor z`2DvR+@yO|$A2)kUWX1zZ(v)@Hq4(0fAES|%rfnAPNtm)9JDd4`_Hsb1m5}nns+Yb zLBITmFjn~R(mmscZ*P4#E6!|qq1Im8Q#UX0;_xW)=#~4QQ+bi^y~c#LerXzO;IYHQ z$0e&xsy9}pCVJbvQtUb1E;RlzA!ArcUFj;xmeRGD`M_OakyhJv6!Iyn=i#)``7>QX zBY`=ehNu5-Oa6UhB0ysel}9)I|4`ljzxF#ipeQQ*)ptKx>${Pf?(MYP63g*s`9@W) zYh#qOtfoPW{Wvb@yx6Ra8V%!+C?y^!@kO0Tx0@Guy}7w9Lr+nW=G;u;4v_Xnu$Hr#5PrqbG89)zEKlp|N;^|X0T`cIRF3eTGa zM+*JSs1))8=*lAMl5(I11vM-s0WYpn2IfPyqr3U@mI7l%#7)v-K{o=#I)p=bv_?Qi z;@ok{MG`ikO{HM2Fx9#U z_Of3!*?i}CX1v}@%GC=;HlvHO#U%dnoc+J)qmTfJfM!5Czuxuh4ibxSK5MQo}o+R$wJpBnwb z2Uiy!@}IJ+D!#MPOL*;FzdoAVc6r;~(q}i^Dt8N=;a#uJ&oSAc=X$h@vvCgq>)BX% z>yN0B3DxJf6_Hk{^j^t%r!udy@{G-mq=@ilgOCinX-gP0O}al3K}Rn*C|Z zHnlVxjj0UR-N&XbxE(2(%G`BZmw^P>d#{fJa4Ff4ivfXF=wQ-LZ^bnMC@rv6!$JCN zZbiqH2w)T6RXSXACYLECP^@z3px$&6C+o$AZ0!RL5NTU6t9C5sNVbMz5m7nGq)9Gc zqlnd|%ho9_ES)XAc@MBWm9?&FzQ1{UwgN7&wa{9zz6sQLzNb+aQD%uO24t&uajCMKJ_M!iw|m)LQ!X3*&Px-L6~i_7X8qgaxG# zK3BxwdGwExf0+h{y0fA~tJ=J*5%odFzs5b$Y-g>MSJ!Qg&qMuA%Gng9;P&@P4>#4@ zSNnHjuTH-@+2`;;>u&w`#^EwYeZ9$E?NO{BCV2?;)=7dxM_Q1PgqiR#ig6tcTeE1wLS(reSXe8J^qUk1FlUF)-O%|@U z>h#R|^<7742CqRy(?>|)gt8P|Zj>hx$mcXvxM`uR&GeBTr);2sAZ*YbFiXtvk^@G9 z$QZ-+lxS!FvI8F~+H~b~W6{1n5CJzLtPdTDf0!sBVqznqQnm#~+95~sv7a5bj0*IL z;)hY|r6Z_dccStyhzK7*b<_g6FlX-WWCCiAc?HBO0=3* zjl_0;&rdBC20TKkW!HX*38^WQ$OK!5>AhCY_TgC*>-gn4lg zgUDs}n;;wP{!B^;i_4@XlH+%E0GN*1hBZ#-b+D)?-mL z#4#A7*x|4`C`}V|NKRSGNpcOejFJOhXwU<6{E5^AA@|%3b)*@M4YS+_t7*?}hLlg~ zJXvfxRPoGU3b#YIjn>7II-He6*@F}41*l-^Vu|eAYRHWhbBV&`G8lxHe7FOYlv!bo zj8=oyUkMQym0J!~S8pw2c$wVYAYD^$V3V3)fWe~7DcXJq1oyv)xqHtxwpc6HzJ8zZ zko_{RlX7B%!pB^-TkUc;wxx$7g*3;d4sV zQPWJ%w z1)P&TQ%M9}7P+;vT&Pf>MKPzi9*iwRh+}gx`5Yp|hzN$YW98HdptL*>Fuy_VNnlwZ zE+UuTFUM7O1p5ihZ{EU0qMxAq^NllD86!!S$Z~Vp2S4fYj8J8<6AOeKk@rv)2>`Md z8VUEOA!M0KLYt7bn{>t){3c4`5R=*Q(=>!a4prn(jpZh$WvnbM7q%DMYH^YluOi-n zXkj0>3h0f&d3%_~h&JBX>)1Q-xwO7PRM4I4$u64-J8&M;YIzSlXWTi^l;Z;TPuEI3 zmcYPBBjAS2Ucp_(yjb{=hn!Pq`$DuW}a6+LQKmP+?g zGYooe+;M$%wSvz7xapjg{RW#o!9QZe_Fy3tA~J}sn~W7`SPvh4tv>75blzp+JcvU% zp^wl?*{BCSdjxV`TT~E7f%<`vm`cm&T%_zYE%A%Byc^?*y3Lk)h#!%@u!nBp8AWi0 z;t0?K^$5FAX6Ie^=v*E9Atq@E9x-}qqd~N*TlDVGXg5yuK6W&RfG{0`UaDtUBWOS2 z%#FP$G8UplhxiZ0oLGsWt;7%rtYA0R=}=ZEhZR=CiWp*@DTdTk@J|y&&c0=AZVXHD z5b@(d^+8bmRUSJ96Psodn>NGB42|W~u;?o>n%r1TI%_Kr>Xgr4PT0B6iw!A1X;Bl0 zJRShN#9Z;$?BK9Dk{}HM%~zvhCY(Mtu}-FBI8Vhq*d+V`;#*H4Z9murISU| zZu-!pYc6E=UXU-j;PFC~C@Nw7ky1I9dK8i-*Kxr=HqNgklT{##_YnJkq9$m@B9 zx(Gtr7~z2Me&MudeFNAvZ$KI!VEAyZCdfBl^Bu=#ns@+tg(Dcn02Z1eHrUF9@U1n5 zxdK3AcxWps%4j{WUd|Dh*Vp<)o2&j;SE%Aav5NJcUK80tE08v^y~Et?y6X+$P1XJ<^2U^%En*8b6EKid;S69s znX6|L`qu|`%(aFvA<#j_c-Uiy$^xn(|Cj4zZIX*O%vzjGSN_V05eiwc_JBIARJIA9 zVp?_K>l3`EvrxBSlk()bkd}*%R#`<38%$NEF67YyiCX1zWh{=ekj?en4f1KNg5g76 z$aJSPn6sr|Vj5h83=^H^ByACRhdUDyz`|2}_odm%SZD|o7o-4BbwbtpQ(a;0soqV1 z-m2`@7cqa>=#p$YJsp% z=_MD!2XMfG}y)DUlz$BxS&_A5tj$aP0kF=;Gj}NS+RU8@hlynZ@?h{KT z7P&*jfuRQ>(lFZ{u=v#!dv?YaCFCn7s(0p(o0rnjM34&1s|R<{rm)baLaU?Dip7EM zkXfHRgaiEx!C^K^zz!o?h*q0+qa@>Z9o!IaAy*aDp;{clKW$Sn?A^@=o+C61YcGt# zt}6IvP39mzf<>f3Fz$GM0))o0h$yz2v5BC0Bq2a69Y>4KLq;Rbj+pd4DLf^d&lH{t zr6%KkB5Buf%P9q;j_94{N&;3j!Z0AC??wk3EYnkqw(c6LOhm{$BBF5%Ha9=EA~(*Jm!C=iR!faU z_*XP8wg*+1jjW7s?@({^-|^R&)dnO1Vu3dQQ6>J@9cV4_kk_?A(+TN$o4szEh*|}* zI?^R^o)I-^xhTknX&KV!j*g5N(u5!ckS*Np?Tankt%xB@5AL_H^ zx86JTefNUo!xKZbs)q5bzZbx~(HC8v1I$}fbZJ)5UP8aqIIzIL0=B@iN+WYR!ysAyM zAJ#Vc{GrClKz4Op?vifctGD(WMfP(INN?US4sH1PGV8%oRsYc)Bh^~eoVtc;nb4rR z>0Jk(zq?L#OaExZ@%_kepQC?yo^>z^qhp_9_tbO+8{GTU*yHHnF^+4D%~J_!cD;j% z!YMVK=cjnSCqxW(+Tm)~z%kZ5cN`8tuX{ZTA8adc`(@{;*7=6c$6!@Hiit>8Fd)mFeY z$1@R1?UP{$x$Sv&Ds(zOyC8MFPGOoZP!< z#-+}yVef8MdES3lba%S+HTMn3vIK6QJg)0fCLe!K4a*MPBR1O;wAn8-K|rzf5<(RS8s?%G5!z7m`|4_@_vIgK>6&V2ik-UK{p)uF_(=%0>Uh4*d0-i=A3mZ$6R1|C8e6W!YZe)+8FF zRo?PxW}oh;MM(>~*l z%SFX&6t(9szx1!3KmDqh6G(b_^7-*EuS1_F@(XL4ug(t%<0;2K4|(;ht0P`11ka-f zmR|(Fz5HePQ@i%pDb7S)$eSa!7N?#uj~)jG$Qf$`vXX=Z9o z#g1n`Og_H#gm9mC*(sfWO%mC;3P>WY|4JeQB0paCPc$J^Qs7jZq!Y)i_rJV-BuN-J z7C}6^#X<_)1>b~Ftmo4ixc2~NcPHZbD-6KIWt2qcyCFYDqi<8;CPc7+>kL>rA>{Et zdl~0{>R+1ET3KJy4v%=XwAvVQ_Y4QBun9yLLWv~_`zP{=aH%i3)lHuqM40#c`J^L= ze3~?{z}EIgG-@m{$lC^)!;3M>0oHcAS&Lf_K~*Jm?|qk_j7e|DO@d6;H&az(_#l!d z7G#otSYr)CEed$b(DH?wk*z9fIAfuhlW111(#sf+_D^TaLW+hsB5GrHepLaoCUEG;Q#m+)t1>>XO1|e zLQqMej)(+dkWewPTyW4Z6(rFP5fPw8c@IP47bl|(qi~d@0h~Fa zm!HU2DirmtLSZXIGYnw=a>3%q2kPmFmt)AdeFg$ZZZWtKD`Y>U4A7-Z92^8JFGK+= z(t)i}blh*?ZzNdYW8z64BD8Tu1IgGtkRRq{;<(e-`I!yH-aS!f3~z6WQF;*wD-eyV zSA8EGTEE4YC=*QtiL*t!VH7KVB+X{iBW`2>nGS=zXfUkY3l3j(w}hhczZOIxl|_); zcM!;>U%CZomQaIr&DWDG|LI;zlnuYT4sPL*Rb(0tIC`J`+ z*X0gClu%HrjH`*Z98QfLxYk{oGgZL1`P;jr&l)(E36J8VdmO_lUlcrW3rLe z4pmXb%mI{m&8qn4XDgIlW(#^FLO*gfT+17>Zg@)6)Yoc~akf?S|Dd;VElr~cOgl`d zL;;2GDUKDKz47u}xyi3^i`zytfeE;2vHf1(R@Ss(w}k9tm}0RIL$wJ9FemG796BU3=d#o`ZJIxUk*L9c6)w*W7~NM z$ta>Mh#5>ra_9mzbkq=CxSh_aM=U;vUZR}m|Dz7b4Mi%fL}FJW34|yWwz?nZyhWd#?DM9i?^LY}ShErJ2~vKC#Q_zs72GAPBwYX8zM3Q*t$2fKisa4;t6R5)sL_I)%=+-3!1S zD0TyOHcW%Bp`nw0(Ku(-PaL65#O+`H23>xJo zb@3rAqw3sJ<^;hgj})$9h;mo;Np&@texx9b4gwhr$@<@N7l8L~CV{TIw|lN@rsnKv%arIgUtf7O(u2>o{bC>|y=Uc8S{YR%fV=s@ z1+BzLLThx^>Y}bTB=%KR$jT>s&nOGe?bp{Gl2sx|_KVPg_gwQj0OJj~wFI!BBXTSK z;NSVEHNQJA2OEb!&S|oYga(pw5kFxb)|*<^P0m15l2{R(Im9B_qg&UX?f;k z&Z(Q?&0^t4&ZLrx+{N%BBgn|2?9794?{Mxs9^d(dpJ%wGh-2T{Rq}ZoN(63H^$9fH zs`>S3%zlrm?;hQF=YO`wYt;g*3Hs%H|D~n?)xrjxoRPvC^aidy87uP;R2B>2Cw4l z4@MMiaXav7V-#1fdbw({!Z~k11TQTVD569#&f1nR)q6J4FR=NoJxnrZF$r#-yd5-`hiN6)=NHuc`C=!-WAKKCC%|Wkc+5J1G?srv@#A#;f1)J5)mX;{?FxAQrU3xFC!^@9ts#q6+Zha z<|&w~E<=#KJ#8aZ{OGj|-M^WLwHhH5+2F{&iIdfbw3bt-Px)yP|8c{6TcQp%G6UJB z@-S)>+6Yt4kT3&{Ms6)%XFY(dg^u>2#pQJ(>eJyMFy{JX8YsadS-Eb=8}P*!?@ZGy zHo{U@?aQGGddG!eav5-72a&ome* zYM$V=L;lpl^Q*>ybvhP*UTu=AxNVp!qWc<5jM85=5V&?>gQ+W9%nard86deL#*|29 zid}OMt36-Bu#f{doec~$>Av0KYghwrop!kQ|&8tGAdiork#VN(^? zsuE~1jtk=GG;3i~Hy}^J%kVp4F%5VM%RX26U9IJ1#@{ww7`(BgHgU>tN8La7!Z(?J zTT$lK|1>ZvYXG37WfpwzUvbQZ?X}7+7Kw6yonYrcdO+Ij@0!$dDWc?A`2fkDsQbY< zIitC;21b#9QWrqu{1fQGT1OQH@0#QUaBP0nWI9ljm|rzn62~Cy`dyQxwVHI5;MbER z`B>|&8?iax=%;}Ajug$ZGVXH|)@K`%Ol?$&j0tc@`XHOUKx{Kg=2^)tL?$i;FM;56E0*nK`n8V|Odt zkPr1V47|O-#*lHaI_MB!Y2IC7z`mfx84%3oNA&N+tRf{l2N=jPG69|Wv!P0wZ-K&C z27}A1lJB2)!_LKvh3A?7p8@-N__|I1*hB0Zjsyr@@Dc)v${~c*VIa+!(!Pcx>B0jz zl1BMH6(|tjTv|-W_v1xDAdt6il5!pUvyg5F*<~Uj56J0gmvZt2bc%RjM+-6$M1*c% z@p4qABt>mcHv-Nwj!J4_oF)VB(u12vf$M=qES|{*4j1+o5l?3ZqZOxZbZyM$wZL@r z!jOLR)g(^-bz72UP&)`s&~1i+Tt}JW0KTTfm{siQY=3bk9h6*{**O9JnPT5(T|{kc z!R5i8)2GBFUGF!GWwkasVDij6@QQlOv_C4P#QSE+8*?=Klz5qeS={l5Kq4*+6Mxez zW-SqSioA2gZt?`IsTR+qg2k6d!>)<{ba%EC0BX0fc4hj;CX_al4i?{)5*Gb%<4i82 z?)$8ui20{3D3A#y1SF^-n#akKZS7vk7N51|KDGe@kCTPU^U46Te%HL%tvT-HPg@B= z?u%XDwB~xAnw)$nggG^kmuO*v&<^g_m7ed`%`tKD=_R*mCAT`ha`7K-x2Ap;#qR<3e2vT5G$u2d_ntacIC)hDtx52VfJ zT^3X-j79S)T|lt%NxTxGl`Pv?0Z-vI46teie>tFrYk|PZ4a$@gMD1yet!^%9J#x8r zSPTBIhqc;iUNr=VV zl|aWiuMTlX!PR8#B}<@C8rTzASBB z;M*w}O)J$bDjx5{v52csFbG7ZCaM*LdUuu|Gk^9g|+wQoIvOGp}4 z)`T|W-e-t|E2YS_E1SIq6nQi-5maTTr3(rfYGT#$t2ZO0Z-5E3dIfP59r2z%M%wgT z@0y+q2lcOt%F|X^PWdIGc6Jh*uuEJU!Ne#&4ADyj^Woh7Xp2`VxebEaRg|S?ZqlzI z0+j|@cEGe-B!%0(4g-ha-LY5@tVUY1l8Z7?>w9-H{SYfy4&pWnZGnv zTVOU21G4&~v68q~%#H%2pwgh0*c6u4X%YZs7`Im6(~8lXeDcCH9LpakXG&1%NQXG) ztCR#@YtMwdJ`*&(9oH0t-Z)!@hZWc*Da_|F-|1cB=5I7HEcc!kSf{V&&bSACtz=9E1jj5(y}w0Wm` z^Od9h%LnF-jNZREd00?-!(D${_Ld!oo_-6j**$dmvPg!5%NR#jIB45twz=}lskm#b zRlY}eYh7jyr9B_lT|T!4#+a!@W5XZP)Nr%m z-PA)}xM>_i{vJDT>pvxJ3b7KJU&haB9GS^Z7En6eLw;0z@ikj~yhu99{N?k)7}Cg> zK2w5~iE^UqGV9vKGSwH7=@tu!{EU~PSvgsVdmZ_i{zr}}a&#dks#&TAd))rxhWn+lDu=AO;i~2qpAg!H_2ko*7~r*# z3jY(mFKcW6+8(x|l)6AY)SMhN>?!Wc*it{`66$k*T5vBy^OE*@Rw^znF za(X@5@@&VbP0P%{Sk3sOx4%y3gxvlm_)@Lf>k+R{BhDEsa=Hq7-hU%D*^e@$N4`#T zs7gtu$A*;eZ4IyL?^eAyeBz&vt=sz`sDdYldxSM_T9Oo5t|xzdj$lL6cv#29_Rp-U z0HjV4N9oDh+P|M)fn%(jZ`sEah>omO##ty-iFc{i-24D!iU9kAZX{IFK!jjMj9e;4 zY~ej^QsDX65Qs_%9}et@nAE=t>VIw$XYkRl)m)%^TMcu=YPcSzVJmDsczKxh!01=9Zi3OQ=UGyrl?gDak<|i6tD56L=4DA*23C@# zJ_V8pe&PX?wKN~O&Bbn9*vSSGwv`Q$e-w(`LbO)=ITWL8VA)}ipPU%6LWlK%P==Ww z(9WZgrsh-;rxnIIVEgLNhkz6{=OMx6H6UocTlZoD5+AuL<9=L^x1>c1=&=vtJ8@!% zkYfTz0LxA;FRM|9WxEuqP78GLvNKw>6IyfI4qtVKW_h|P- zvqq18jfsqH6tdKA8xL(7$A_gQ4}x!!cY zf3@V}g^f^(Mx+Fu3PN*j_(*PGYlB=!B5-p{jsC%mODN=m_##Hv*Uyo;Zi1-sV({lF zLXt+}L6|b81>dTBPZz$YH4v3$#rC}n&ZTfZhfRYV2nv?5$*bpF_{EBek_#yyH3Kiq zHUeEKgGlEmL?ZZ#K|kGUp(1j0Sa=OU6sPvD`Q>1MUoIu(mt8KGD3uZpXvs>I{*w^t zo1zKNii)Zrrn`Li+(;bEzN=8ybjR5{#JVaoSq9U7RXObYD?FB3v&IWAbdG|~)ypps zJhV=iMV%f7K?d<{j@Ns^aFDgk8=@6hAOH%L7?*v+rNfy4BlMz>y7_B&?8eRDel=g} z_8_B`OYPS;#^JsOtZj@-h!VM#eU}a|bVkAmmNIdUz^XbrAk9X}r~xQ?&H6~3ft5sm zF5K{hOx#Hh9o)zPc;Nx@-jm=}@WcU7e0q7Lph1E3%xkTL*ZU7y``lj&B0dzY#sqNS30dGef<81GNH~!To*p9+ zM5M5BM$f-?pV_@B{HuA)wtvp3JyZz(OekgVr#H)It&9~U%e~v-v`;3EKo{x+CkkPx zoq}b025zHt?;I>RWCgCHK#Zj2?8Qu^cqw?`kiGbqdB z>$6ra-FKX3g^c0C@>s|H88-O|Pht#6h8w|VP$-md6vT(2((Tb-MC<6)If~#&7@Y_~ zuC7Fx1{3MX;mC@{-?B262pRwlk_a-Ch5l?!(pB6?Ew>por8MdnoUcWrEYY`Y3H!-fBs28rT9JCw2o}m5_&T$hRaey~cRxesf1>2S|>AL7JvmhBtBzsAxfsc4(Z zIH0}h{LU1sPZuVwRrA1UUi6WBB*T_fiL?Ce&KyPQQUc$m=R;dRLIdMA$nkx9w)Om{ zOIv@;9#a01p8TkJO-rVTgy^1)pxQ}!3w$`uA3+p){pIXY!?1Ui=PK&AOSnbWsYFVJ zMsBEy6bs!V&xv@bA^^hd?@lNBVNC{u_%|;JWzY;h_9HWxc4!Ql5p1GySb!C@#dO61 z&`AG55Q}o5dah9d%&5U8dh$mh3kk-UKI6xvl@VvBSqgzadM z!46=F8iD`LXZnUE<{nPWKP>)^LR*ypJ|z$zlYkIfLgu5_7pD$+w|8;xQAF`?y~#69!!iP>#Yz=s4oV8{`i&g$3FgLp;w1y_Mhb9op61o5w?@9s}lh|jHIN+Ap z`yYXs@>^gg5TKK8NzJ#N5VxMWrE1cw&KN|Bt#mON4 z6domo??v*ePl`Zoia=}%tRn^8k^FNdMYJ|WcyFo{znG3&@-j9B@*))-Y@lG8wz4bb z8!=6xL_{q%4N{WEqorwOCF}5~>z+wQ56Ji`Hf|ge)8qzfm3vB5D*+m_jFm%%_16uy zu^IMfHvZkT27=6itc3EfH}i|iw|CYoSVF>UDQK%`nC!L)d@8O*2BYsg++JSLmTc(x zpA>X!N;8mxzDV%u%+WZO{`8Vb(ClzWuHHyB}D`ey;FDn zTwUC6Wy^SX594;ZHf-J%{%oME`hMx6Xvj>8=@yTIS()jjRf!FWQT$J>mi)%&8{>d0i0*`97i396mmviC&FGPiaf?(OWM2 zR1aNA2~2&2S?i(^d_d##%N8Uu`lSKKodhUl+ep9Ykv94@Gr2Z6aDwsf zNd$EXP@}xG7TSieo{@!7(QvU}8CeQZ2(C~y!Dc)~lK|o!3`+!U5TJUeGz@t)g&CVr z9Zml$`#OI47n`iFDsnsCMj;Ca@(m0mYxR@G598mE)VpBv1^{OE3>HY=0<6j@6VJ3W z4hRxiiEg}{<24y0AK2I-M#bXPp>+ctM15&@!qY3qFlcygeuPG%5`2gvtRsb@A=KZ` ztx}Uym*>iyRAP_;2$(q2>-wGFd`PH}PuetYaAS?NwH~Rs3oDWBY zi%alYj5njv4f1Mu>|ZW5et5?0Us$ro>cgV3AlpFCb^-NJG)=$R{r~K_yW)&puaoub zMm?ttCeX@`_*FYGhZy*WGJC>~ef`_pxe?X?8wuGD$n)+RLb~DSW-~Oiv+{xNTg9SN z_1HrmTdkZ{0RWPQA;b8Ztf;%1Mc%iT@a`e=u1CC%9$D`5c$ng#20&;8xYe9nSz3YV zjv=p4SD$i_C1Kqy4F_GpyY{&~ICFRjh&zdGYY*D4y5iz5xv=ZSnH?u;cLg3=do!Pe z5B^z|E!_03`1LxJC8bgAYvW-s?Q%J^rHbmm(Sn zvIRc4e`h-Do=0&3gTvbZMHD4%6zt>6u{|In55(iR<4wOl0{{G*v1)>P24~pG8i&_h zkgB5jT89Keo9=C>XmOo3V#PzbFti3bng+YAAM#qn$N0UU_;ws=C zNC9d%994UKbT;-yN=O(dqkRn`|*Y#Iy`+Sh;RsvOYiV7nCp`nmU;m4xU zgjBni_1fWY{Ke@YAQ;yJT<`hs(YT`85h3ov@sk{^nfM482+TjXSo7 zz7SgGq?B!9g#x&!mO^25#P;=?PmDf>A-sa*52>%$ypwFHxn8}|Vh$7p8X1~M1f^E> zx34Qq%>7#co)X`11J$V+DEXA%C7)|4d(ln3K4(X_Mtyn%>=Qlse}GT{off-}Q$ZJs zkR+f9N}Jqs9SF}~d{qGjLq-La<%WAvx)*0?R!V>Kzg8z+2_4huCvU2$lI}!j+lUb} z-gccy%5ViXc@dhIqBa`5Y2W;`w4O3?#h}@3WzM0L>kXj53O`2qy`Q7@(APdzwV0tY zgle?`K;Qw)dT|d52F&6feiAT9e^g3ITeERiI+;SlDwDP*%z;>H)yT>_TKcwRVCrO# zlC)ukUScbU1ZMLq&&G}Onvn-??%XE!)sLZ^YTglxGn>onC>6Pw5NPLn4aZQ z6QK!pta^U=742%>^2>nd$4>*31wb_7b&2VVyrCn6O-8~5Pt=Sufi~tZEhiX60>o6z z`n|LjxCgp2ZZhD9HyE_=u#GVYk&n99y4Kiq3Y%@H`Hq$>tfP=@5jyXt$9;^I*T?#S zP6S3M-+u@rtCpCo|!tVySC1P{wbhN*JF zZ!f6#FE6M;x%XDNsNH#;mto{=p?FVXY8qJl@UwOd}5i8t;zgvy8g-XBeu& zrFL7J*@u3%vm`pOpyBc+RTJnrD8sX_^`lAeLJZVz>Te`Wq2)dtjLfb(KzqCW3O@+s z%5KpB2429F;ni)kf$D-rHTTuW7uL9S(aGxP{jdpj-YO6nHlXWGv|!)byn&g$a1fAC zyUzBfT(k>s(eR`Ty&-ymVZ5p)##S(LG#S0@=3*)J2K9*u8>35)L;D|6>g5NBpTLm1 z-^Y0*OoBHkEWYgO(}P(Kr6$5<{)_bcyTHlowe3T^gaFOaT7-Mg@E0X3WYFyYmy?{V z_jPRXmCEukpiB1hY2rcQE8-~Kx~1{KUxf5;&d?mdbDF=+uE@O)E za)Mgqo11~r8BovycDIw}l-}bn9#H}4QuqdIQ-z_X*sTz^c8BBJ59muNww_~3xRPGu815kTaTigOol+F>=SB*e{!g2)jd?$Ms>KpgK$wQ#iA?b@j= z!KA8uSM&kCN3e$Zho!}79~zKbgFA4f!uOVUtV~2VHNe&V18Pz8fb{r5IL~1x=0E~) zKfVvdstJK!E+RV+FUT4fXX`FGqgN2vGm;Iy3oBBJlJ0#^nW9@|Nmf)0zn2K zH{iorn*sm$`#DD#s}CB92q@K`juXdWLVSkX0}yrzPOS;L%^er@PcFzvxffU9o@5I1 z8wJ08nzhMVLi^lYjc!3}GR{8JT!W%UW^PK>G$^gYI$jdrxVg3_3KlE5UT*V{t;^1~>KZ`VPS2T)b^cBNnQTG4@^Wu5V(CZ~O(d zVLQyB(~X^Sh_2jO@{N_G~os*ueDzu*Hj; zmi?{;d%#bAeI^)NSF@wvt@-5_+J&A2-)_swrXikmNcU2o%XuVz7;TDQRowCD;nzvS+!F}D0{Z^1@GIV^e>--cPdUQ=B{~-$l{_Jw2$PvDsYob|FM>!KpcEBJt0{0o%DH!H>FI;td~UIL9&vhG zB68o}x<6jJvGk;HBcG&RahV-{N}{l9EwKr|m6pA&kw0O=qgN)eT&QbYq5Ku|(2p%b?J_-E5(} ziSU^zb+#K1FJ9^qsj#zJ#^z_Rr6N&@!4!pr#W1GCqEJdN7Fyw-yu-6e(tP0}m8$u= zQ!-ZXZ+1=G&0Tg4f4)Z)gmYlVkCzibxdY5bh6d~LO%JdNow@P_o%pxE zO)fQ#e&x2oJ>xw$pyf>h7{Pr1W(0QvX1*~yR-u%C7{S~BK!S+?Bp91XhAm9tKu}kv zJq_7A6}WM)98yq}#p7w8W@Y{%r zC|J9TnBqbqAjh=$;g7YIK;VeL-fHU8elZ>p0KIM?aTDR{X~u&;pC)`pceLBkw)XbU zM-k@PO|K8Y1Tse6#ka)`1P)!gu3@^-)n@QfW)S3AZ?0~EA%ij)f?HG?v=Nq~DIX>D z^ux1Xf^-zxjy2McjOFBK)Ne!U1z43m)-i$)RxC}pVD&_uQcp0PRw1g-GzV=%7@QIl z9YTCP?b`95$cWgOD+eO}Gj17Q{%72RVSGhc#O=8_B)qSQ01Dbc#}HU}^SfY^nb2_5 zh?sXh99j2CABknKFVMkYNfMk#p76id1qlLS@?f(Qze0<+10lofW}>*K%Jy_pV>#?H5>=;41=qC<>?k>r~3;5@tHfk+jM`#J@Y#@|pn zy49MkaGqYR)$3Pl!H6DN-688+POn-NbZzg~mRFO%D>5ip z^14l}eVhlBl5~>qup^rRggb*VR>anZycAdGBNzVAZZHb!oa}!Md-(Z9#sx$np2zA< zCA+9m+RPBWBlD^72 zO7~`YcL~nUQ*QJpYJ|)V=sZ7psL&aD`22#>oM4$W<%+a!V?}nQ_2oSSx|w^&uLV>% z<_4VmFkEVfzk?-uL0vP0W|nzZ9(a1jCxis|F+88KaqhY3uEVB|^K5ZZdurqUCAj@W z=JRSK@6Db>r|(mKV_msCZ7=J+`%C^Nrz+lAQ50ec+~$zI(_R>#7-ZE%^~+_$kG6VkS>k$S}FG zSjj$su%M8f>vnQavQ)XI)Iz?`RlkJ-->RC0LVr8f1h@*7&{2GkoNa~a#>$$CKJl4N zluCyHgoX59-{s1p?1H-3n_FuYjRg4EX=5W0ndO%IThx>eQk7a4X z4q>4t%{WzO0kmC#xRu~O+9g4q|GUdTgDI`lQ#Cb){DHHte=kkm)7H2c-^Nzkbmx+# zLA`^?t=m-Y9e{BBty>lS$~?B3{VW~(rRi2&6v=ysixkCYG2?c6-L{_*x0+|Yl9m4< zEFk3S>Xje$0jYi`eMteiwRWYuwO=M!4@k9Pqz9hd+W7ggeYeX1*8WB64Bq1y)cUzF zI#4gw*SL2OdrR?i54fmZqmSh$s-8{|5dGT6E!Ans*PX(0?mpE+&u%d0Gev2{gtOwl ziWzHNKMoY|=EBS4CMI$6Y7Z+$eLP5+-OnNj73oH=@#VeG^uFo;eaPyk|GGPD#3RO( zh{!Kb;S`_OJwL;-t$&dgLjqtP$L-sb^o&;!-Gaw4ZE>wZ!-1)9LXV45|FGuB0>5w* z(ft5d&d{WzowaIli*RRqI_

        l}XbBr=h?=B;k=(_14>1vV37Z_y!yXo}Y^ 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 JzdyXn{{lcal#u`c literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/subflow-labels.png b/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/subflow-labels.png new file mode 100644 index 0000000000000000000000000000000000000000..80023be78c9ac9377961782cd2c168e5d9e754cf GIT binary patch literal 30101 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 Cd{%}4 literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js new file mode 100644 index 000000000..f46ef3fbb --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js @@ -0,0 +1,156 @@ +export default { + version: "2.2.0", + steps: [ + { + titleIcon: "fa fa-map-o", + title: { + "en-US": "Welcome to Node-RED 2.2!", + "ja": "Node-RED 2.2へようこそ!" + }, + description: { + "en-US": "Let's take a moment to discover the new features in this release.", + "ja": "本リリースの新機能を見つけてみましょう。" + } + }, + { + title: { + "en-US": "Search history", + "ja": "検索履歴" + }, + description: { + "en-US": "

        The Search dialog now keeps a history of your searches, making it easier to go back to a previous search.

        ", + "ja": "

        検索ダイアログが検索履歴を保持するようになりました。これによって、過去の検索に戻りやすくなりました。

        " + }, + element: "#red-ui-search .red-ui-searchBox-form", + prepare(done) { + RED.search.show(); + setTimeout(done,400); + }, + complete() { + RED.search.hide(); + }, + }, + { + title: { + "en-US": "Remembering Zoom & Position", + "ja": "拡大/縮小のレベルや位置を記憶" + }, + description: { + "en-US": "

        The editor has new options to restore the zoom level and scroll position when reloading the editor.

        ", + "ja": "

        エディタを再読み込みした時に、拡大/縮小のレベルやスクロール位置を復元するための新しいオプションを利用できます。

        " + }, + element: function() { return $("#user-settings-view-store-position").parent()}, + prepare(done) { + RED.actions.invoke("core:show-user-settings") + setTimeout(done,400); + }, + complete(done) { + $("#node-dialog-ok").trigger("click"); + setTimeout(done,400); + }, + }, + { + title: { + "en-US": "New wiring actions", + "ja": "新しいワイヤー操作" + }, + // image: "images/", + description: { + "en-US": `

        A pair of new actions have been added to help with wiring nodes together:

        +
          +
        • Wire Series Of Nodes - adds a wire (if necessary) between each pair of nodes in the order they were selected.
        • +
        • Wire Node To Multiple - wires the first node selected to all of the other selected nodes.
        • +
        +

        Actions can be accessed from the Action List in the main menu.

        `, + "ja": `

        ノード接続を支援する2つの新しい操作が追加されました:

        +
          +
        • Wire Series Of Nodes - ノードを選択した順序で、各ノードのペアの間にワイヤーを(必要に応じて)追加します。
        • +
        • Wire Node To Multiple - 最初に選択したノードから、他の選択した全てのノードに対して、ワイヤーを追加します。
        • +
        +

        メインメニュー内の動作一覧から、これらの操作を利用できます。

        ` + }, + }, + { + title: { + "en-US": "Deleting nodes and reconnecting wires", + "ja": "ノードの削除とワイヤーの再接続" + }, + image: "2.2/images/delete-repair.gif", + description: { + "en-US": `

        It is now possible to delete a selection of nodes and automatically repair the wiring behind them.

        +

        This is really useful if you want to remove a node from the middle of the flow.

        +

        Hold the Ctrl (or Cmd) key when you press Delete and the nodes will be gone and the wires repaired.

        + `, + "ja": `

        選択したノードを削除した後、その背後にあるワイヤーを自動的に修復できるようになりました。

        +

        これは、フローの中からノードを削除する時に、とても便利に使えます。

        +

        Ctrl (またはCmd)キーを押しながらDeleteキーを押すと、ノードがなくなり、ワイヤーが修復されます。

        + ` + } + }, + { + title: { + "en-US": "Detaching nodes from a flow", + "ja": "フローからノードの切り離し" + }, + image: "2.2/images/detach-repair.gif", + description: { + "en-US": `

        If you want to remove a node from a flow without deleting it, + you can use the Detach Selected Nodes action.

        +

        The nodes will be removed from their flow, the wiring repaired behind them, and then attached to the mouse + so you can drop them wherever you want in the workspace.

        +

        There isn't a default keyboard shortcut assigned for this new action, but + you can add your own via the Keyboard pane of the main Settings dialog.

        `, + "ja": `

        ノードを削除することなく、フローからノードを除きたい場合は、Detach Selected Nodes操作を利用できます。

        +

        フローからノードが除かれた後、背後のワイヤーが修復され、ノードはマウスポインタにつながります。そのため、ワークスペースの好きな所にノードを配置できます。

        +

        この新しい操作に対して、デフォルトのキーボードショートカットは登録されていませんが、メイン設定ダイアログのキーボード設定から追加できます。

        ` + } + }, + { + title: { + "en-US": "More wiring tricks", + "ja": "その他のワイヤー操作" + }, + image: "2.2/images/slice.gif", + description: { + "en-US": `

        A couple more wiring tricks to share.

        +

        You can now select multiple wires by holding the Ctrl (or Cmd) key + when clicking on a wire. This makes it easier to delete multiple wires in one go.

        +

        If you hold the Ctrl (or Cmd) key, then click and drag with the right-hand mouse button, + you can slice through wires to remove them.

        `, + "ja": `

        その他のいくつかのワイヤー操作

        +

        Ctrl (またはCmd)キーを押しながらワイヤーをクリックすることで、複数のワイヤーを選択できるようになりました。これによって、複数のワイヤーを一度に削除することが簡単になりました。

        +

        Ctrl (またはCmd)キーを押しながら、マウスの右ボタンを用いてドラッグすると、ワイヤーを切って削除できます。

        ` + } + }, + { + title: { + "en-US": "Subflow Output Labels", + "ja": "サブフローの出力ラベル" + }, + image: "2.2/images/subflow-labels.png", + description: { + "en-US": "

        If a subflow has labels set for its outputs, they now get shown on the ports within the subflow template view.

        ", + "ja": "

        サブフローの出力にラベルが設定されている場合、サブフローテンプレート画面内のポートにラベルが表示されるようになりました。

        " + }, + }, + { + title: { + "en-US": "Node Updates", + "ja": "ノードの更新" + }, + // image: "images/", + description: { + "en-US": `
          +
        • The JSON node will now handle parsing Buffer payloads
        • +
        • The TCP Client nodes support TLS connections
        • +
        • The WebSocket node allows you to specify a sub-protocol when connecting
        • +
        `, + "ja": `
          +
        • JSONノードが、バッファ形式のペイロードを解析できるようになりました。
        • +
        • TCPクライアントノードが、TLS接続をサポートしました。
        • +
        • WebSocketノードで、接続時にサブプロトコルを指定できるようになりました。
        • +
        ` + } + } + ] +} From 42ecf54df6c839e0513422a7f5f01dffc7002e9c Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 21 Jun 2022 22:39:22 +0900 Subject: [PATCH 107/195] add i18n support adn English and Japanese message --- .../@node-red/editor-client/locales/en-US/editor.json | 3 ++- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- .../node_modules/@node-red/editor-client/src/js/ui/tab-help.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 4f3a3b104..e6fe4a4af 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1158,7 +1158,8 @@ "tourGuide": { "takeATour": "Take a tour", "start": "Start", - "next": "Next" + "next": "Next", + "tours": "Tours" }, "diagnostics": { "title": "System Info" diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index d4a638137..791b553e8 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1157,7 +1157,8 @@ "tourGuide": { "takeATour": "ツアーを開始", "start": "開始", - "next": "次へ" + "next": "次へ", + "tours": "ツアー" }, "languages": { "de": "ドイツ語", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index c6b33dbc7..09a67e338 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -218,7 +218,7 @@ RED.sidebar.help = (function() { nodeHelp, { id: "tours", - label: "Tours", + label: RED._("tourGuide.tours"), children: tours }, ]; From d66def5530346b5ed38e57bf69cece29e00a6a6e Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 21 Jun 2022 10:56:18 -0400 Subject: [PATCH 108/195] Add fix from flowforge-nr-theme https://github.com/flowforge/flowforge-nr-theme/blob/main/common/forge-common.css#L92-L98 --- .../@node-red/editor-client/src/sass/jquery.scss | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss index 27661c459..e25617637 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss @@ -31,6 +31,8 @@ } .ui-widget-content { border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-secondary-background); + color: var(--red-ui-primary-text-color); } .ui-widget-header { @@ -173,6 +175,16 @@ html .ui-button.ui-state-disabled:active { background: var(--red-ui-form-button-background); } +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button{ + color: var(--red-ui-primary-text-color); +} + .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, From 8aa922c55f0f102fc42e840d16c12d3531545370 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 21 Jun 2022 11:02:56 -0400 Subject: [PATCH 109/195] Fix indentation --- .../@node-red/editor-client/src/sass/jquery.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss index e25617637..ec76049f9 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss @@ -27,12 +27,12 @@ } .ui-widget.ui-widget-content { - border: 1px solid var(--red-ui-tertiary-border-color); + border: 1px solid var(--red-ui-tertiary-border-color); } .ui-widget-content { - border: 1px solid var(--red-ui-secondary-border-color); - background: var(--red-ui-secondary-background); - color: var(--red-ui-primary-text-color); + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-secondary-background); + color: var(--red-ui-primary-text-color); } .ui-widget-header { @@ -182,7 +182,7 @@ a.ui-button, a:link.ui-button, a:visited.ui-button, .ui-button{ - color: var(--red-ui-primary-text-color); + color: var(--red-ui-primary-text-color); } .ui-state-hover, From 2ea10206faa4892426b8be3b0284ab1e96ad2cd7 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 21 Jun 2022 11:43:02 -0400 Subject: [PATCH 110/195] Fix select box alignment --- .../node_modules/@node-red/nodes/core/common/20-inject.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index de3990a93..0fafa9df0 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -118,7 +118,7 @@ .inject-time-row { padding-left: 110px; } - .inject-time-row select { + .inject-time-row:not(#inject-time-row-interval) select { margin: 3px 0; } .inject-time-days label { From 6939546f22c9cedf0cb16b762ff1639dd4d6af4c Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Wed, 22 Jun 2022 19:58:02 +0200 Subject: [PATCH 111/195] Disable keyboard shortcut mapping when showing Edit[..]Dialog --- .../editor-client/src/js/ui/editor.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index fb4c200f5..0ea00621e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1033,6 +1033,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + if (editing_node.hasOwnProperty('outputs')) { editing_node.__outputs = editing_node.outputs; } @@ -1075,6 +1077,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1178,6 +1182,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayHeader = tray.find(".red-ui-tray-header"); var trayBody = tray.find('.red-ui-tray-body'); var trayFooter = tray.find(".red-ui-tray-footer"); @@ -1258,6 +1264,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + RED.workspaces.refresh(); activeEditPanes.forEach(function(pane) { @@ -1635,6 +1643,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
        ", { class: "red-ui-tray-footer-left" @@ -1666,6 +1676,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1755,6 +1767,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
        ", { class: "red-ui-tray-footer-left" @@ -1776,6 +1790,8 @@ RED.editor = (function() { }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1888,6 +1904,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayFooter = tray.find(".red-ui-tray-footer"); var trayBody = tray.find('.red-ui-tray-body'); trayBody.parent().css('overflow','hidden'); @@ -1915,6 +1933,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } From d2e84925f7738dcceda6465627e4945d20b23ffc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 22 Jun 2022 21:43:25 +0100 Subject: [PATCH 112/195] Set default editor to monaco in absence of user preference --- .../src/js/ui/editors/code-editor.js | 16 ++++++++-------- .../@node-red/runtime/lib/api/settings.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js index 13ed25611..7cee2026b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js @@ -21,7 +21,7 @@ const MONACO = "monaco"; const ACE = "ace"; - const defaultEditor = ACE; + const defaultEditor = MONACO; const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} }; var selectedCodeEditor = null; var initialised = false; @@ -48,12 +48,12 @@ } function create(options) { - //TODO: (quandry - for consideration) + //TODO: (quandry - for consideration) // Below, I had to create a hidden element if options.id || options.element is not in the DOM - // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an + // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an // invalid (non existing html element selector) (e.g. node-red-contrib-components does this) - // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre' - // code is thus skipped. + // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre' + // code is thus skipped. // In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI) // Because one (or more) contrib nodes have left this bad code in place, how would we handle this? // For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur. @@ -79,7 +79,7 @@ return this.editor.create(options);//fallback to ACE } } - + return { init: init, /** @@ -91,7 +91,7 @@ }, /** * Get user selected code editor - * @return {string} Returns + * @return {string} Returns * @memberof RED.editor.codeEditor */ get editor() { @@ -104,4 +104,4 @@ */ create: create } -})(); \ No newline at end of file +})(); diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index f56b8ab61..9bf640227 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -91,7 +91,7 @@ var api = module.exports = { safeSettings.context = runtime.nodes.listContextStores(); if (runtime.settings.editorTheme && runtime.settings.editorTheme.codeEditor) { safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {}; - safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "ace"; + safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco"; safeSettings.codeEditor.options = safeSettings.codeEditor.options || {}; } safeSettings.libraries = runtime.library.getLibraries(); From 4bd71da056a7fbd3acbc233f0375ffe6a38643be Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 23 Jun 2022 16:52:43 +0900 Subject: [PATCH 113/195] fix credential type input item of subflow template --- .../@node-red/editor-client/src/js/ui/editors/envVarList.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index c1e1a0217..41a528e21 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -94,6 +94,11 @@ RED.editor.envVarList = (function() { } opt.ui.label = opt.ui.label || {}; opt.ui.type = opt.ui.type || "input"; + if ((opt.ui.type === "cred") && + opt.ui.opts && + opt.ui.opts.types) { + opt.ui.type = "input"; + } var uiRow = $('
        ').appendTo(container).hide(); // save current info for reverting on cancel From d1efd2137ad6bd89588db4e99e5a14d9742ee27a Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sat, 25 Jun 2022 16:24:28 +0200 Subject: [PATCH 114/195] Add german contextMenu translation --- .../@node-red/editor-client/locales/de/editor.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index ba3007f51..f47ebf5cf 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -1185,5 +1185,11 @@ "missing-config": "__prop__: Konfigurations-Node fehlt", "validation-error": "__prop__: Validierungsfehler: __node__, __id__: __error__" } + }, + "contextMenu": { + "insert": "Einfügen", + "node": "Node", + "junction": "Kreuzung", + "linkNodes": "Verknüpfe Nodes" } } From 5393fa81b906f1ba2c943e913a5d3c19e6707bff Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 25 Jun 2022 20:27:32 -0400 Subject: [PATCH 115/195] Leave Monaco theme commented out by default --- packages/node_modules/node-red/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index d8f879851..99fa6437f 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -401,7 +401,7 @@ module.exports = { * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" */ - theme: "vs", + // theme: "vs", /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html */ From cd71b3daf3cccffa88d7542dacac1a125233617e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E5=BB=BA=E5=9B=BD?= <50353452+hotlong@users.noreply.github.com> Date: Sun, 26 Jun 2022 13:02:16 +0800 Subject: [PATCH 116/195] =?UTF-8?q?fix=20chinese=20translate:=20=E7=99=BB?= =?UTF-8?q?=E9=99=86=20->=20=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor-client/locales/zh-CN/editor.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index af3e833da..eca5878ae 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -97,7 +97,7 @@ "rename": "重命名", "delete": "删除", "keyboardShortcuts": "键盘快捷方式", - "login": "登陆", + "login": "登录", "logout": "退出", "editPalette": "节点管理", "other": "其他", @@ -122,16 +122,16 @@ "zoom-in": "放大" }, "user": { - "loggedInAs": "作为 __name__ 登陆", + "loggedInAs": "作为 __name__ 登录", "username": "账号", "password": "密码", - "login": "登陆", - "loginFailed": "登陆失败", + "login": "登录", + "loginFailed": "登录失败", "notAuthorized": "未授权", "errors": { - "settings": "设置信息需要登陆后才能访问", - "deploy": "改动需要登陆后才能部署", - "notAuthorized": "此操作需要登陆后才能执行" + "settings": "设置信息需要登录后才能访问", + "deploy": "改动需要登录后才能部署", + "notAuthorized": "此操作需要登录后才能执行" } }, "notification": { From 78327716a4ed027582723fdc8094645513eed0d1 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 26 Jun 2022 09:08:26 +0100 Subject: [PATCH 117/195] ensure workspace clean after undoing dropped node --- .../@node-red/editor-client/src/js/ui/view-tools.js | 5 +++-- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 ++- .../node_modules/@node-red/nodes/core/common/21-debug.html | 2 +- .../node_modules/@node-red/nodes/core/common/60-link.html | 2 +- .../@node-red/nodes/core/function/10-function.html | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 888fb4d7f..a781c44fc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -979,13 +979,14 @@ RED.view.tools = (function() { * - it uses ` ` - where N is the next available integer that * doesn't clash with any existing nodes of that type * @param {Object} node The node to set the name of - if not provided, uses current selection + * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory` */ function generateNodeNames(node, options) { - options = options || { + options = Object.assign({ renameBlank: true, renameClash: true, generateHistory: true - } + }, options) let nodes = node; if (node) { if (!Array.isArray(node)) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index b55244d0f..0ddc06a8c 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5872,6 +5872,7 @@ RED.view = (function() { * @private */ function createNode(type, x, y, z) { + const wasDirty = RED.nodes.dirty() var m = /^subflow:(.+)$/.exec(type); var activeSubflow = z ? RED.nodes.subflow(z) : null; if (activeSubflow && m) { @@ -5930,7 +5931,7 @@ RED.view = (function() { var historyEvent = { t: "add", nodes: [nn.id], - dirty: RED.nodes.dirty() + dirty: wasDirty } if (activeSubflow) { var subflowRefresh = RED.subflow.refresh(true); diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index 62da1b259..f861f518b 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -558,7 +558,7 @@ onadd: function() { if (this.name === '_DEFAULT_') { this.name = '' - RED.actions.invoke("core:generate-node-names", this) + RED.actions.invoke("core:generate-node-names", this, {generateHistory: false}) } } }); diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.html b/packages/node_modules/@node-red/nodes/core/common/60-link.html index f592bcd3d..4b8c9a3d6 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.html +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.html @@ -221,7 +221,7 @@ function onAdd() { if (this.name === '_DEFAULT_') { this.name = '' - RED.actions.invoke("core:generate-node-names", this) + RED.actions.invoke("core:generate-node-names", this, {generateHistory: false}) } for (var i=0;i Date: Sun, 26 Jun 2022 10:17:16 +0100 Subject: [PATCH 118/195] Fix delay rate limit last timing when empty --- .../@node-red/nodes/core/function/89-delay.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index 5205a5b18..6524aa040 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -275,18 +275,22 @@ module.exports = function(RED) { if (msg.hasOwnProperty("flush")) { var len = node.buffer.length; if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush),len); } - while (len > 0) { - const msgInfo = node.buffer.shift(); - if (Object.keys(msgInfo.msg).length > 1) { - node.send(msgInfo.msg); - msgInfo.done(); - } - len = len - 1; - } - if (node.buffer.length === 0) { + if (len === 0) { clearInterval(node.intervalID); node.intervalID = -1; } + else { + while (len > 0) { + const msgInfo = node.buffer.shift(); + if (Object.keys(msgInfo.msg).length > 1) { + node.send(msgInfo.msg); + msgInfo.done(); + } + len = len - 1; + } + clearInterval(node.intervalID); + node.intervalID = setInterval(sendMsgFromBuffer, node.rate); + } node.status({fill:"blue",shape:"dot",text:node.buffer.length}); done(); } From f8ad75329d648799d9a47f130b97b00210e8c622 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 26 Jun 2022 11:52:55 +0100 Subject: [PATCH 119/195] use $node-port-background as config node icon background Makse sense since it contrasts the $node-port-label-color used for icon text Also, only show pointer if node icon has count --- .../@node-red/editor-client/src/sass/tab-config.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 5c8d0ba94..f88fce07a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -37,7 +37,7 @@ ul.red-ui-sidebar-node-config-list { } .red-ui-palette-node { overflow: hidden; - + cursor: default; &.selected { border-color: transparent; box-shadow: 0 0 0 2px $node-selected-color; @@ -58,7 +58,7 @@ ul.red-ui-sidebar-node-config-list { .red-ui-palette-icon-container { font-size: 12px; line-height: 30px; - background-color: $node-icon-background-color; + background-color: $node-port-background; border-top-right-radius: 4px; border-bottom-right-radius: 4px; a { @@ -68,6 +68,7 @@ ul.red-ui-sidebar-node-config-list { left: 0; right: 0; color: $node-port-label-color; + cursor: pointer; &:hover { text-decoration: none; background: $node-port-background-hover; From 125b37e98f6d33894593a907b3d25339791709f2 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 26 Jun 2022 12:16:39 +0100 Subject: [PATCH 120/195] increase quick-add height to reveal 2 most recent fixes #3568 --- .../node_modules/@node-red/editor-client/src/sass/search.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index cce1e69e4..168bc7a94 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -54,7 +54,7 @@ } .red-ui-search-results-container { display: none; - height: 150px; + height: 195px; .red-ui-editableList-container { border: 1px dashed $primary-border-color; border-top: 1px solid $secondary-border-color; From 495a81768d5b3da535fd6341c7fffe8cbaa5904d Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sun, 26 Jun 2022 16:27:11 +0100 Subject: [PATCH 121/195] Delay node - add example for simple queue with release --- .../delay/06 - Simple Queue with release | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release diff --git a/packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release b/packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release new file mode 100644 index 000000000..51d7595f2 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release @@ -0,0 +1,149 @@ +[ + { + "id": "48d660b3a4109400", + "type": "inject", + "z": "9e5f48c16729e4f0", + "name": "inject", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 185, + "y": 795, + "wires": [ + [ + "e0f9e206681f3504" + ] + ] + }, + { + "id": "e0f9e206681f3504", + "type": "delay", + "z": "9e5f48c16729e4f0", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "30", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 430, + "y": 795, + "wires": [ + [ + "e470f1d794e1bef9", + "af7cea1dfb797a75" + ] + ] + }, + { + "id": "943543cf7a1958e4", + "type": "change", + "z": "9e5f48c16729e4f0", + "name": "set flush to 1", + "rules": [ + { + "t": "set", + "p": "flush", + "pt": "msg", + "to": "1", + "tot": "num" + }, + { + "t": "delete", + "p": "payload", + "pt": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 510, + "y": 915, + "wires": [ + [ + "e0f9e206681f3504" + ] + ] + }, + { + "id": "e470f1d794e1bef9", + "type": "function", + "z": "9e5f48c16729e4f0", + "name": "Do something that takes a few seconds", + "func": "\n//send on the message between 3 and 6 seconds later\nsetTimeout(\n function() { \n node.send(msg) \n }, \n Math.random() * 3000 + 3000\n);\nreturn null;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 760, + "y": 795, + "wires": [ + [ + "943543cf7a1958e4", + "859258551b8389b7" + ] + ] + }, + { + "id": "af7cea1dfb797a75", + "type": "debug", + "z": "9e5f48c16729e4f0", + "name": "IN", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 710, + "y": 735, + "wires": [] + }, + { + "id": "859258551b8389b7", + "type": "debug", + "z": "9e5f48c16729e4f0", + "name": "OUT", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 895, + "y": 735, + "wires": [] + }, + { + "id": "ecaaf26326da10ee", + "type": "comment", + "z": "9e5f48c16729e4f0", + "name": "Simple Queue with release", + "info": "This example shows how to use a delay node set to rate limit mode as a simple queue to feed a\nprocess that may take some time to complete. Once that process completes the feedback is then\nset to flush out the next message - thus running the \"loop\" as fast as possible with no overlaps.\n\n**Note**: only the `msg.flush` property msut be set - otherwise the other properties that are fed \nback will be added as another new message to the queue.", + "x": 235, + "y": 915, + "wires": [] + } +] \ No newline at end of file From ab1e38dde81bb1325d02d574a990e9b8772cafa0 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 27 Jun 2022 23:56:24 +0900 Subject: [PATCH 122/195] Fix use default button for node icon --- .../editor-client/src/js/ui/editors/panes/appearance.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js index f4534965d..912fa3528 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -37,8 +37,7 @@ if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) { var icon = $("#red-ui-editor-node-icon").val()||""; if (!this.isDefaultIcon) { - if ((icon !== node.icon) && - (icon !== "")) { + if ((node.icon && icon !== node.icon) || (!node.icon && icon !== "")) { editState.changes.icon = node.icon; node.icon = icon; editState.changed = true; From 2f1f587c50a24355e3329bc15030d5eebfdb7a06 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 27 Jun 2022 18:03:14 +0100 Subject: [PATCH 123/195] Use HTTP body instead of header for setting flows run state --- .../node_modules/@node-red/editor-api/lib/admin/flows.js | 2 +- .../node_modules/@node-red/editor-client/src/js/ui/deploy.js | 5 +---- test/unit/@node-red/editor-api/lib/admin/flows_spec.js | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js index 611d9c2ca..2ad233f8f 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js @@ -83,7 +83,7 @@ module.exports = { postState: function(req,res) { const opts = { user: req.user, - requestedState: req.get("Node-RED-Flow-Run-State-Change")||"", + requestedState: req.body.state||"", req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.setState(opts).then(function(result) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 69460f8cf..ab36df9ff 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -306,10 +306,7 @@ RED.deploy = (function() { $.ajax({ url:"flows/state", type: "POST", - data: {state: state}, - headers: { - "Node-RED-Flow-Run-State-Change": state - } + data: {state: state} }).done(function(data,textStatus,xhr) { if (deployWasEnabled) { $("#red-ui-header-button-deploy").removeClass("disabled"); diff --git a/test/unit/@node-red/editor-api/lib/admin/flows_spec.js b/test/unit/@node-red/editor-api/lib/admin/flows_spec.js index ba09c9fa1..9ec6a3bc9 100644 --- a/test/unit/@node-red/editor-api/lib/admin/flows_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/flows_spec.js @@ -254,7 +254,7 @@ describe("api/admin/flows", function() { request(app) .post('/flows/state') .set('Accept', 'application/json') - .set('Node-RED-Flow-Run-State-Change', 'stop') + .send({state:'stop'}) .expect(200) .end(function (err, res) { if (err) { @@ -295,7 +295,7 @@ describe("api/admin/flows", function() { request(app) .post('/flows/state') .set('Accept', 'application/json') - .set('Node-RED-Flow-Run-State-Change', 'bad-state') + .send({state:'bad-state'}) .expect(400) .end(function(err,res) { if (err) { From 51baed493220261d33d84a31f464c22b1f19676a Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 27 Jun 2022 18:06:53 +0100 Subject: [PATCH 124/195] default stop/start feature to `enabled:false` --- .../@node-red/editor-api/lib/admin/index.js | 2 +- .../@node-red/editor-client/src/js/runtime.js | 6 +++--- .../@node-red/editor-client/src/js/ui/deploy.js | 2 +- .../node_modules/@node-red/runtime/lib/api/flows.js | 2 +- .../@node-red/runtime/lib/api/settings.js | 8 ++++---- packages/node_modules/node-red/settings.js | 12 ++++++------ 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index 078779f5a..8406fa8e9 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -56,7 +56,7 @@ module.exports = { // Flows/state adminApp.get("/flows/state", needsPermission("flows.read"), flows.getState, apiUtil.errorHandler); - if (!settings.runtimeState || settings.runtimeState.enabled !== false) { + if (settings.runtimeState && settings.runtimeState.enabled === true) { adminApp.post("/flows/state", needsPermission("flows.write"), flows.postState, apiUtil.errorHandler); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/runtime.js b/packages/node_modules/@node-red/editor-client/src/js/runtime.js index 939517877..49960e382 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/runtime.js +++ b/packages/node_modules/@node-red/editor-client/src/js/runtime.js @@ -1,12 +1,12 @@ RED.runtime = (function() { let state = "" - let settings = {ui: true, enabled: true}; + let settings = {ui: false, enabled: false}; const STOPPED = "stopped" const STARTED = "started" return { init: function() { // refresh the current runtime status from server - settings = RED.settings.runtimeState; + settings = Object.assign({}, settings, RED.settings.runtimeState); RED.runtime.requestState() // {id:"flows-run-state", started: false, state: "stopped", retain:true} @@ -29,7 +29,7 @@ RED.runtime = (function() { // disable pointer events on node buttons (e.g. inject/debug nodes) $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state === STOPPED) // show/hide Start/Stop based on current state - if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) { + if(settings.enabled === true && settings.ui === true) { RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) RED.menu.setVisible("deploymenu-item-runtime-start", state === STOPPED) } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index ab36df9ff..3766763c7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -69,7 +69,7 @@ RED.deploy = (function() { {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, null ] - if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) { + if(RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 83ef68021..b3c471a5a 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -310,7 +310,7 @@ var api = module.exports = { } } - if(runtime.settings.runtimeState ? runtime.settings.runtimeState.enabled === false : false) { + if(!runtime.settings.runtimeState || runtime.settings.runtimeState.enabled !== true) { throw (makeError("Method Not Allowed", "not_allowed", 405)) } switch (opts.requestedState) { diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 7e17f57b5..6c13596ce 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -153,11 +153,11 @@ var api = module.exports = { } safeSettings.runtimeState = { - //unless runtimeState.ui and runtimeState.enabled are explicitly false, they will default to true. - enabled: (runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === false) ? false : true, - ui: (runtime.settings.runtimeState && runtime.settings.runtimeState.ui === false) ? false : true + //unless runtimeState.ui and runtimeState.enabled are explicitly true, they will default to false. + enabled: !!runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === true, + ui: !!runtime.settings.runtimeState && runtime.settings.runtimeState.ui === true } - if(safeSettings.runtimeState.enabled === false) { + if(safeSettings.runtimeState.enabled !== true) { safeSettings.runtimeState.ui = false; // cannot have UI without endpoint } diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 26e5ca8de..fae6c8079 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -269,17 +269,17 @@ module.exports = { ui: true, }, /** Configure runtimeState options - * - enabled: When `enabled` is `true` (or unset), runtime Start/Stop will - * be available at http://localhost:1880/flows/state - * - ui: When `ui` is `true` (or unset), the action `core:start-flows` and - * `core:stop-flows` be available to logged in users of node-red editor + * - enabled: When `enabled` is `true` flows runtime can be Started/Stoped + * by POSTing to available at http://localhost:1880/flows/state + * - ui: When `ui` is `true`, the action `core:start-flows` and + * `core:stop-flows` will be available to logged in users of node-red editor * Also, the deploy menu (when set to default) will show a stop or start button */ runtimeState: { /** enable or disable flows/state endpoint. Must be set to `false` to disable */ - enabled: true, + enabled: false, /** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */ - ui: true, + ui: false, }, /** Configure the logging output */ logging: { From 1b8a4577d5107e5704f51ad611729b1e9ee5bcc9 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 27 Jun 2022 18:07:22 +0100 Subject: [PATCH 125/195] improve UI, i18n and layout of stop/start feature --- .../editor-client/locales/en-US/editor.json | 11 + .../editor-client/src/js/ui/deploy.js | 46 +- .../editor-client/src/js/ui/view_copy.js | 6287 +++++++++++++++++ .../editor-client/src/sass/header.scss | 2 +- .../@node-red/runtime/lib/api/flows.js | 2 +- 5 files changed, 6328 insertions(+), 20 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index e3028272b..b25885e21 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -288,6 +288,17 @@ "copyMessageValue": "Value copied", "copyMessageValue_truncated": "Truncated value copied" }, + "stopstart":{ + "status": { + "state_changed": "Flows runtime has been changed to '__state__' state" + }, + "errors": { + "notAllowed": "Method not allowed", + "notAuthorized": "Not authorized", + "notFound": "Not found", + "noResponse": "No response from server" + } + }, "deploy": { "deploy": "Deploy", "full": "Full", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 3766763c7..46408cb6c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -297,41 +297,51 @@ RED.deploy = (function() { $(".red-ui-deploy-button-spinner").hide(); } function stopStartFlows(state) { - const startTime = Date.now(); - const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled"); - deployInflight = true; - deployButtonSetBusy(); - shadeShow(); - RED.runtime.updateState(state); + const startTime = Date.now() + const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled") + deployInflight = true + deployButtonSetBusy() + shadeShow() + RED.runtime.updateState(state) $.ajax({ url:"flows/state", type: "POST", data: {state: state} }).done(function(data,textStatus,xhr) { if (deployWasEnabled) { - $("#red-ui-header-button-deploy").removeClass("disabled"); + $("#red-ui-header-button-deploy").removeClass("disabled") } - RED.runtime.updateState((data && data.state) || "unknown" ) - RED.notify('

        Done

        ',"success"); + RED.runtime.updateState((data && data.state) || "unknown") + RED.notify(RED._("stopstart.status.state_changed", data), "success") }).fail(function(xhr,textStatus,err) { if (deployWasEnabled) { - $("#red-ui-header-button-deploy").removeClass("disabled"); + $("#red-ui-header-button-deploy").removeClass("disabled") } if (xhr.status === 401) { - RED.notify("Not authorized" ,"error"); + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAuthorized") }), "error") + } else if (xhr.status === 404) { + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notFound") }), "error") + } else if (xhr.status === 405) { + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAllowed") }), "error") } else if (xhr.responseText) { - RED.notify("Operation failed: " + xhr.responseText,"error"); + const errorDetail = { message: err ? (err + "") : "" } + try { + errorDetail.message = JSON.parse(xhr.responseText).message + } finally { + errorDetail.message = errorDetail.message || xhr.responseText + } + RED.notify(RED._("notification.error", errorDetail), "error") } else { - RED.notify("Operation failed: no response","error"); + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.noResponse") }), "error") } RED.runtime.requestState() }).always(function() { - const delta = Math.max(0,300-(Date.now()-startTime)); - setTimeout(function() { - deployButtonClearBusy(); + const delta = Math.max(0, 300 - (Date.now() - startTime)) + setTimeout(function () { + deployButtonClearBusy() shadeHide() - deployInflight = false; - },delta); + deployInflight = false + }, delta); }); } function restart() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js new file mode 100644 index 000000000..569fee1d5 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js @@ -0,0 +1,6287 @@ +/** + * 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. + **/ + + + /*
        #red-ui-workspace-chart + * \- "outer" + * \- + * \- .red-ui-workspace-chart-event-layer "eventLayer" + * |- .red-ui-workspace-chart-background + * |- .red-ui-workspace-chart-grid "gridLayer" + * |- "groupLayer" + * |- "groupSelectLayer" + * |- "linkLayer" + * |- "junctionLayer" + * |- "dragGroupLayer" + * |- "nodeLayer" + */ + +RED.view = (function() { + var space_width = 5000, + space_height = 5000, + lineCurveScale = 0.75, + scaleFactor = 1, + node_width = 100, + node_height = 30, + dblClickInterval = 650; + + var touchLongPressTimeout = 1000, + startTouchDistance = 0, + startTouchCenter = [], + moveTouchCenter = [], + touchStartTime = 0; + + var workspaceScrollPositions = {}; + var entryCoordinates = {x:-1, y:-1}; + var gridSize = 20; + var snapGrid = false; + + var activeSpliceLink; + var spliceActive = false; + var spliceTimer; + var groupHoverTimer; + + var activeSubflow = null; + var activeNodes = []; + var activeLinks = []; + var activeJunctions = []; + var activeFlowLinks = []; + var activeLinkNodes = {}; + var activeGroup = null; + var activeHoverGroup = null; + var activeGroups = []; + var dirtyGroups = {}; + + var mousedown_link = null; + var mousedown_node = null; + var mousedown_group = null; + var mousedown_port_type = null; + var mousedown_port_index = 0; + var mouseup_node = null; + var mouse_offset = [0,0]; + var mouse_position = null; + var mouse_mode = 0; + var mousedown_group_handle = null; + var lasso = null; + var slicePath = null; + var slicePathLast = null; + var ghostNode = null; + var showStatus = false; + var lastClickNode = null; + var dblClickPrimed = null; + var clickTime = 0; + var clickElapsed = 0; + var scroll_position = []; + var quickAddActive = false; + var quickAddLink = null; + var showAllLinkPorts = -1; + var groupNodeSelectPrimed = false; + var lastClickPosition = []; + var selectNodesOptions; + + let flashingNodeId; + + var clipboard = ""; + + // Note: these are the permitted status colour aliases. The actual RGB values + // are set in the CSS - flow.scss/colors.scss + var status_colours = { + "red": "#c00", + "green": "#5a8", + "yellow": "#F9DF31", + "blue": "#53A3F3", + "grey": "#d3d3d3", + "gray": "#d3d3d3" + } + + var PORT_TYPE_INPUT = 1; + var PORT_TYPE_OUTPUT = 0; + + var chart; + var outer; + var eventLayer; + var gridLayer; + var linkLayer; + var junctionLayer; + var dragGroupLayer; + var groupSelectLayer; + var nodeLayer; + var groupLayer; + var drag_lines; + + var movingSet = (function() { + var setIds = new Set(); + var set = []; + var api = { + add: function(node) { + if (Array.isArray(node)) { + for (var i=0;i1) { + clearTimeout(touchStartTime); + touchStartTime = null; + d3.event.preventDefault(); + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0["pageY"]-touch1["pageY"]; + var b = touch0["pageX"]-touch1["pageX"]; + + var offset = chart.offset(); + var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; + startTouchCenter = [ + (touch1["pageX"]+(b/2)-offset.left+scrollPos[0])/scaleFactor, + (touch1["pageY"]+(a/2)-offset.top+scrollPos[1])/scaleFactor + ]; + moveTouchCenter = [ + touch1["pageX"]+(b/2), + touch1["pageY"]+(a/2) + ] + startTouchDistance = Math.sqrt((a*a)+(b*b)); + } else { + var obj = d3.select(document.body); + touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + var point = d3.touches(this)[0]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + //lasso = eventLayer.append("rect") + // .attr("ox",point[0]) + // .attr("oy",point[1]) + // .attr("rx",2) + // .attr("ry",2) + // .attr("x",point[0]) + // .attr("y",point[1]) + // .attr("width",0) + // .attr("height",0) + // .attr("class","nr-ui-view-lasso"); + },touchLongPressTimeout); + } + d3.event.preventDefault(); + }) + .on("touchmove", function(){ + if (RED.touch.radialMenu.active()) { + d3.event.preventDefault(); + return; + } + if (RED.view.DEBUG) { console.warn("eventLayer.touchmove", mouse_mode, mousedown_node); } + var touch0; + if (d3.event.touches.length<2) { + if (touchStartTime) { + touch0 = d3.event.touches.item(0); + var dx = (touch0.pageX-startTouchCenter[0]); + var dy = (touch0.pageY-startTouchCenter[1]); + var d = Math.abs(dx*dx+dy*dy); + if (d > 64) { + clearTimeout(touchStartTime); + touchStartTime = null; + if (!mousedown_node && !mousedown_group) { + mouse_mode = RED.state.PANNING; + mouse_position = [touch0.pageX,touch0.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + } + + } + } else if (lasso) { + d3.event.preventDefault(); + } + canvasMouseMove.call(this); + } else { + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0["pageY"]-touch1["pageY"]; + var b = touch0["pageX"]-touch1["pageX"]; + var offset = chart.offset(); + var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; + var moveTouchDistance = Math.sqrt((a*a)+(b*b)); + var touchCenter = [ + touch1["pageX"]+(b/2), + touch1["pageY"]+(a/2) + ]; + + if (!isNaN(moveTouchDistance)) { + oldScaleFactor = scaleFactor; + scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); + + var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% + startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), + startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) + ]; + + startTouchDistance = moveTouchDistance; + moveTouchCenter = touchCenter; + + chart.scrollLeft(scrollPos[0]+deltaTouchCenter[0]); + chart.scrollTop(scrollPos[1]+deltaTouchCenter[1]); + redraw(); + } + } + d3.event.preventDefault(); + }); + + // Workspace Background + eventLayer.append("svg:rect") + .attr("class","red-ui-workspace-chart-background") + .attr("width", space_width) + .attr("height", space_height); + + gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); + updateGrid(); + + groupLayer = eventLayer.append("g"); + groupSelectLayer = eventLayer.append("g"); + linkLayer = eventLayer.append("g"); + dragGroupLayer = eventLayer.append("g"); + junctionLayer = eventLayer.append("g"); + nodeLayer = eventLayer.append("g"); + + drag_lines = []; + + RED.events.on("workspace:change",function(event) { + if (event.old !== 0) { + workspaceScrollPositions[event.old] = { + left:chart.scrollLeft(), + top:chart.scrollTop() + }; + } + var scrollStartLeft = chart.scrollLeft(); + var scrollStartTop = chart.scrollTop(); + + activeSubflow = RED.nodes.subflow(event.workspace); + + RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0); + RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow); + + if (workspaceScrollPositions[event.workspace]) { + chart.scrollLeft(workspaceScrollPositions[event.workspace].left); + chart.scrollTop(workspaceScrollPositions[event.workspace].top); + } else { + chart.scrollLeft(0); + chart.scrollTop(0); + } + var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; + var scrollDeltaTop = chart.scrollTop() - scrollStartTop; + if (mouse_position != null) { + mouse_position[0] += scrollDeltaLeft; + mouse_position[1] += scrollDeltaTop; + } + if (RED.workspaces.selection().length === 0) { + resetMouseVars(); + clearSelection(); + } + RED.nodes.eachNode(function(n) { + n.dirty = true; + n.dirtyStatus = true; + }); + updateSelection(); + updateActiveNodes(); + redraw(); + }); + + RED.statusBar.add({ + id: "view-zoom-controls", + align: "right", + element: $(''+ + ''+ + ''+ + ''+ + '') + }) + + $("#red-ui-view-zoom-out").on("click", zoomOut); + RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out'); + $("#red-ui-view-zoom-zero").on("click", zoomZero); + RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset'); + $("#red-ui-view-zoom-in").on("click", zoomIn); + RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in'); + chart.on("DOMMouseScroll mousewheel", function (evt) { + if ( evt.altKey ) { + evt.preventDefault(); + evt.stopPropagation(); + var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; + if (move <= 0) { zoomOut(); } + else { zoomIn(); } + } + }); + + //add search to status-toolbar + RED.statusBar.add({ + id: "view-search-tools", + align: "left", + hidden: false, + element: $(''+ + '' + + '' + + '' + + '? of ?' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '') + }) + $("#red-ui-view-searchtools-search").on("click", searchFlows); + RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); + $("#red-ui-view-searchtools-prev").on("click", searchPrev); + RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); + $("#red-ui-view-searchtools-next").on("click", searchNext); + RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); + RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); + + // Handle nodes dragged from the palette + chart.droppable({ + accept:".red-ui-palette-node", + drop: function( event, ui ) { + d3.event = event; + var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); + var result = createNode(selected_tool); + if (!result) { + return; + } + var historyEvent = result.historyEvent; + var nn = result.node; + + RED.nodes.add(nn); + + var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); + if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { + nn.l = showLabel; + } + + var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); + var helperWidth = ui.helper.width(); + var helperHeight = ui.helper.height(); + var mousePos = d3.touches(this)[0]||d3.mouse(this); + + try { + var isLink = (nn.type === "link in" || nn.type === "link out") + var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; + + var label = RED.utils.getNodeLabel(nn, nn.type); + var labelParts = getLabelParts(label, "red-ui-flow-node-label"); + if (hideLabel) { + nn.w = node_height; + nn.h = Math.max(node_height,(nn.outputs || 0) * 15); + } else { + nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) ); + nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30); + } + } catch(err) { + } + + mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); + mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); + mousePos[1] /= scaleFactor; + mousePos[0] /= scaleFactor; + + nn.x = mousePos[0]; + nn.y = mousePos[1]; + + if (snapGrid) { + var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); + nn.x -= gridOffset.x; + nn.y -= gridOffset.y; + } + + var spliceLink = $(ui.helper).data("splice"); + if (spliceLink) { + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: nn + }; + var link2 = { + source:nn, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + historyEvent.links = [link1,link2]; + historyEvent.removedLinks = [spliceLink]; + } + + + var group = $(ui.helper).data("group"); + if (group) { + RED.group.addToGroup(group, nn); + historyEvent = { + t: 'multi', + events: [historyEvent], + + } + historyEvent.events.push({ + t: "addToGroup", + group: group, + nodes: nn + }) + } + + RED.history.push(historyEvent); + RED.editor.validateNode(nn); + RED.nodes.dirty(true); + // auto select dropped node - so info shows (if visible) + exitActiveGroup(); + clearSelection(); + nn.selected = true; + movingSet.add(nn); + if (group) { + selectGroup(group,false); + enterActiveGroup(group); + activeGroup = group; + } + updateActiveNodes(); + updateSelection(); + redraw(); + + if (nn._def.autoedit) { + RED.editor.edit(nn); + } + } + }); + chart.on("focus", function() { + $("#red-ui-workspace-tabs").addClass("red-ui-workspace-focussed"); + }); + chart.on("blur", function() { + $("#red-ui-workspace-tabs").removeClass("red-ui-workspace-focussed"); + }); + + RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); + RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); + RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});}); + + RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) + + RED.events.on("view:selection-changed", function(selection) { + var hasSelection = (selection.nodes && selection.nodes.length > 0); + var hasMultipleSelection = hasSelection && selection.nodes.length > 1; + RED.menu.setDisabled("menu-item-edit-cut",!hasSelection); + RED.menu.setDisabled("menu-item-edit-copy",!hasSelection); + RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection); + + RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection); + }) + + RED.actions.add("core:delete-selection",deleteSelection); + RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) }); + RED.actions.add("core:edit-selected-node",editSelection); + RED.actions.add("core:go-to-selection",function() { + if (movingSet.length() > 0) { + var node = movingSet.get(0).n; + if (/^subflow:/.test(node.type)) { + RED.workspaces.show(node.type.substring(8)) + } else if (node.type === 'group') { + enterActiveGroup(node); + redraw(); + } + } + }); + RED.actions.add("core:undo",RED.history.pop); + RED.actions.add("core:redo",RED.history.redo); + RED.actions.add("core:select-all-nodes",selectAll); + RED.actions.add("core:select-none", selectNone); + RED.actions.add("core:zoom-in",zoomIn); + RED.actions.add("core:zoom-out",zoomOut); + RED.actions.add("core:zoom-reset",zoomZero); + RED.actions.add("core:enable-selected-nodes", function() { setSelectedNodeState(false)}); + RED.actions.add("core:disable-selected-nodes", function() { setSelectedNodeState(true)}); + + RED.actions.add("core:toggle-show-grid",function(state) { + if (state === undefined) { + RED.userSettings.toggle("view-show-grid"); + } else { + toggleShowGrid(state); + } + }); + RED.actions.add("core:toggle-snap-grid",function(state) { + if (state === undefined) { + RED.userSettings.toggle("view-snap-grid"); + } else { + toggleSnapGrid(state); + } + }); + RED.actions.add("core:toggle-status",function(state) { + if (state === undefined) { + RED.userSettings.toggle("view-node-status"); + } else { + toggleStatus(state); + } + }); + + RED.view.annotations.init(); + RED.view.navigator.init(); + RED.view.tools.init(); + + + RED.view.annotations.register("red-ui-flow-node-changed",{ + type: "badge", + class: "red-ui-flow-node-changed", + element: function() { + var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); + changeBadge.setAttribute("cx",5); + changeBadge.setAttribute("cy",5); + changeBadge.setAttribute("r",5); + return changeBadge; + }, + show: function(n) { return n.changed||n.moved } + }) + + RED.view.annotations.register("red-ui-flow-node-error",{ + type: "badge", + class: "red-ui-flow-node-error", + element: function(d) { + var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); + errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z"); + return errorBadge + }, + tooltip: function(d) { + if (d.validationErrors && d.validationErrors.length > 0) { + return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ") + } + }, + show: function(n) { return !n.valid } + }) + + if (RED.settings.get("editor.view.view-store-zoom")) { + var userZoomLevel = parseFloat(RED.settings.getLocal('zoom-level')) + if (!isNaN(userZoomLevel)) { + scaleFactor = userZoomLevel + } + } + + var onScrollTimer = null; + function storeScrollPosition() { + workspaceScrollPositions[RED.workspaces.active()] = { + left:chart.scrollLeft(), + top:chart.scrollTop() + }; + RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) ) + } + chart.on("scroll", function() { + if (RED.settings.get("editor.view.view-store-position")) { + if (onScrollTimer) { + clearTimeout(onScrollTimer) + } + onScrollTimer = setTimeout(storeScrollPosition, 200); + } + }) + + if (RED.settings.get("editor.view.view-store-position")) { + var scrollPositions = RED.settings.getLocal('scroll-positions') + if (scrollPositions) { + try { + workspaceScrollPositions = JSON.parse(scrollPositions) + } catch(err) { + } + } + } + } + + + + function updateGrid() { + var gridTicks = []; + for (var i=0;i 0) { + if (delta < node_width) { + scale = 0.75-0.75*((node_width-delta)/node_width); + // scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); + // if (Math.abs(dy) < 3*node_height) { + // scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; + // } + } + } else { + scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width)); + } + if (dx*sc > 0) { + return "M "+origX+" "+origY+ + " C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+ + (destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+ + destX+" "+destY + } else { + + var midX = Math.floor(destX-dx/2); + var midY = Math.floor(destY-dy/2); + // + if (dy === 0) { + midY = destY + node_height; + } + var cp_height = node_height/2; + var y1 = (destY + midY)/2 + var topX =origX + sc*node_width*scale; + var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height); + var bottomX = destX - sc*node_width*scale; + var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height); + var x1 = (origX+topX)/2; + var scy = dy>0?1:-1; + var cp = [ + // Orig -> Top + [x1,origY], + [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)], + // Top -> Mid + // [Mirror previous cp] + [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)], + // Mid -> Bottom + // [Mirror previous cp] + [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)], + // Bottom -> Dest + // [Mirror previous cp] + [(destX+bottomX)/2,destY] + ]; + if (cp[2][1] === topY+scy*cp_height) { + if (Math.abs(dy) < cp_height*10) { + cp[1][1] = topY-scy*cp_height/2; + cp[3][1] = bottomY-scy*cp_height/2; + } + cp[2][0] = topX; + } + return "M "+origX+" "+origY+ + " C "+ + cp[0][0]+" "+cp[0][1]+" "+ + cp[1][0]+" "+cp[1][1]+" "+ + topX+" "+topY+ + " S "+ + cp[2][0]+" "+cp[2][1]+" "+ + midX+" "+midY+ + " S "+ + cp[3][0]+" "+cp[3][1]+" "+ + bottomX+" "+bottomY+ + " S "+ + cp[4][0]+" "+cp[4][1]+" "+ + destX+" "+destY + } + } + + function canvasMouseDown() { + if (RED.view.DEBUG) { + console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); + } + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + if (d3.event.button === 1) { + // Middle Click pan + mouse_mode = RED.state.PANNING; + mouse_position = [d3.event.pageX,d3.event.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + return; + } + if (!mousedown_node && !mousedown_link && !mousedown_group) { + selectedLinks.clear(); + updateSelection(); + } + if (mouse_mode === 0 && lasso) { + lasso.remove(); + lasso = null; + } + if (d3.event.touches || d3.event.button === 0) { + if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.metaKey || d3.event.ctrlKey) && !(d3.event.altKey || d3.event.shiftKey)) { + // Trigger quick add dialog + d3.event.stopPropagation(); + clearSelection(); + const point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0], point[1]); + if (drag_lines.length > 0) { + clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) + } + showQuickAddDialog({ position: point, group: clickedGroup }); + } else if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { + // CTRL not being held + if (!d3.event.altKey) { + // ALT not held (shift is allowed) Trigger lasso + if (!touchStartTime) { + const point = d3.mouse(this); + lasso = eventLayer.append("rect") + .attr("ox", point[0]) + .attr("oy", point[1]) + .attr("rx", 1) + .attr("ry", 1) + .attr("x", point[0]) + .attr("y", point[1]) + .attr("width", 0) + .attr("height", 0) + .attr("class", "nr-ui-view-lasso"); + d3.event.preventDefault(); + } + } else if (d3.event.altKey) { + //Alt [+shift] held - Begin slicing + clearSelection(); + mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING; + const point = d3.mouse(this); + slicePath = eventLayer.append("path").attr("class", "nr-ui-view-slice").attr("d", `M${point[0]} ${point[1]}`) + slicePathLast = point; + RED.view.redraw(); + } + } + } + } + + function showQuickAddDialog(options) { + options = options || {}; + var point = options.position || lastClickPosition; + var spliceLink = options.splice; + var targetGroup = options.group; + var touchTrigger = options.touchTrigger; + + if (targetGroup && !targetGroup.active) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + RED.view.redraw(); + } + + var ox = point[0]; + var oy = point[1]; + + if (RED.settings.get("editor").view['view-snap-grid']) { + // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') + point[0] = Math.round(point[0] / gridSize) * gridSize; + point[1] = Math.round(point[1] / gridSize) * gridSize; + // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','blue') + } + + var mainPos = $("#red-ui-main-container").position(); + + if (mouse_mode !== RED.state.QUICK_JOINING) { + mouse_mode = RED.state.QUICK_JOINING; + $(window).on('keyup',disableQuickJoinEventHandler); + } + quickAddActive = true; + + if (ghostNode) { + ghostNode.remove(); + } + ghostNode = eventLayer.append("g").attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); + ghostNode.append("rect") + .attr("class","red-ui-flow-node-placeholder") + .attr("rx", 5) + .attr("ry", 5) + .attr("width",node_width) + .attr("height",node_height) + .attr("fill","none") + // var ghostLink = ghostNode.append("svg:path") + // .attr("class","red-ui-flow-link-link") + // .attr("d","M 0 "+(node_height/2)+" H "+(gridSize * -2)) + // .attr("opacity",0); + + var filter; + if (drag_lines.length > 0) { + if (drag_lines[0].virtualLink) { + filter = {type:drag_lines[0].node.type === 'link in'?'link out':'link in'} + } else if (drag_lines[0].portType === PORT_TYPE_OUTPUT) { + filter = {input:true} + } else { + filter = {output:true} + } + + quickAddLink = { + node: drag_lines[0].node, + port: drag_lines[0].port, + portType: drag_lines[0].portType, + } + if (drag_lines[0].virtualLink) { + quickAddLink.virtualLink = true; + } + hideDragLines(); + } + if (spliceLink) { + filter = {input:true, output:true} + } + + var rebuildQuickAddLink = function() { + if (!quickAddLink) { + return; + } + if (!quickAddLink.el) { + quickAddLink.el = dragGroupLayer.append("svg:path").attr("class", "red-ui-flow-drag-line"); + } + var numOutputs = (quickAddLink.portType === PORT_TYPE_OUTPUT)?(quickAddLink.node.outputs || 1):1; + var sourcePort = quickAddLink.port; + var portY = -((numOutputs-1)/2)*13 +13*sourcePort; + var sc = (quickAddLink.portType === PORT_TYPE_OUTPUT)?1:-1; + quickAddLink.el.attr("d",generateLinkPath(quickAddLink.node.x+sc*quickAddLink.node.w/2,quickAddLink.node.y+portY,point[0]-sc*node_width/2,point[1],sc)); + } + if (quickAddLink) { + rebuildQuickAddLink(); + } + + + var lastAddedX; + var lastAddedWidth; + + RED.typeSearch.show({ + x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), + y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), + disableFocus: touchTrigger, + filter: filter, + move: function(dx,dy) { + if (ghostNode) { + var pos = d3.transform(ghostNode.attr("transform")).translate; + ghostNode.attr("transform","translate("+(pos[0]+dx)+","+(pos[1]+dy)+")") + point[0] += dx; + point[1] += dy; + rebuildQuickAddLink(); + } + }, + cancel: function() { + if (quickAddLink) { + if (quickAddLink.el) { + quickAddLink.el.remove(); + } + quickAddLink = null; + } + quickAddActive = false; + if (ghostNode) { + ghostNode.remove(); + } + resetMouseVars(); + updateSelection(); + hideDragLines(); + redraw(); + }, + add: function(type,keepAdding) { + if (touchTrigger) { + keepAdding = false; + resetMouseVars(); + } + + var nn; + var historyEvent; + if (type === 'junction') { + nn = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + historyEvent = { + t:'add', + junctions:[nn] + } + } else { + var result = createNode(type); + if (!result) { + return; + } + nn = result.node; + historyEvent = result.historyEvent; + } + if (keepAdding) { + mouse_mode = RED.state.QUICK_JOINING; + } + + nn.x = point[0]; + nn.y = point[1]; + var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); + if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { + nn.l = showLabel; + } + if (quickAddLink) { + var drag_line = quickAddLink; + var src = null,dst,src_port; + if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { + src = drag_line.node; + src_port = drag_line.port; + dst = nn; + } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { + src = nn; + dst = drag_line.node; + src_port = 0; + } + + if (src !== null) { + // Joining link nodes via virual wires. Need to update + // the src and dst links property + if (drag_line.virtualLink) { + historyEvent = { + t:'multi', + events: [historyEvent] + } + var oldSrcLinks = $.extend(true,{},{v:src.links}).v + var oldDstLinks = $.extend(true,{},{v:dst.links}).v + src.links.push(dst.id); + dst.links.push(src.id); + src.dirty = true; + dst.dirty = true; + + historyEvent.events.push({ + t:'edit', + node: src, + dirty: RED.nodes.dirty(), + changed: src.changed, + changes: { + links:oldSrcLinks + } + }); + historyEvent.events.push({ + t:'edit', + node: dst, + dirty: RED.nodes.dirty(), + changed: dst.changed, + changes: { + links:oldDstLinks + } + }); + src.changed = true; + dst.changed = true; + } else { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + historyEvent.links = [link]; + } + if (!keepAdding) { + quickAddLink.el.remove(); + quickAddLink = null; + if (mouse_mode === RED.state.QUICK_JOINING) { + if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + } + } else { + quickAddLink.node = nn; + quickAddLink.port = 0; + } + } else { + hideDragLines(); + resetMouseVars(); + } + } else { + if (!keepAdding) { + if (mouse_mode === RED.state.QUICK_JOINING) { + if (nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + } + } else { + if (nn.outputs > 0) { + quickAddLink = { + node: nn, + port: 0, + portType: PORT_TYPE_OUTPUT + } + } else if (nn.inputs > 0) { + quickAddLink = { + node: nn, + port: 0, + portType: PORT_TYPE_INPUT + } + } else { + resetMouseVars(); + } + } + } + if (nn.type === 'junction') { + RED.nodes.addJunction(nn); + } else { + RED.nodes.add(nn); + } + RED.editor.validateNode(nn); + + if (targetGroup) { + RED.group.addToGroup(targetGroup, nn); + if (historyEvent.t !== "multi") { + historyEvent = { + t:'multi', + events: [historyEvent] + } + } + historyEvent.events.push({ + t: "addToGroup", + group: targetGroup, + nodes: nn + }) + + } + + if (spliceLink) { + resetMouseVars(); + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: nn + }; + var link2 = { + source:nn, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + historyEvent.links = (historyEvent.links || []).concat([link1,link2]); + historyEvent.removedLinks = [spliceLink]; + } + RED.history.push(historyEvent); + RED.nodes.dirty(true); + // auto select dropped node - so info shows (if visible) + clearSelection(); + nn.selected = true; + if (targetGroup) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + } + movingSet.add(nn); + updateActiveNodes(); + updateSelection(); + redraw(); + // At this point the newly added node will have a real width, + // so check if the position needs nudging + if (lastAddedX !== undefined) { + var lastNodeRHEdge = lastAddedX + lastAddedWidth/2; + var thisNodeLHEdge = nn.x - nn.w/2; + var gap = thisNodeLHEdge - lastNodeRHEdge; + if (gap != gridSize *2) { + nn.x = nn.x + gridSize * 2 - gap; + nn.dirty = true; + nn.x = Math.ceil(nn.x / gridSize) * gridSize; + redraw(); + } + } + if (keepAdding) { + if (lastAddedX === undefined) { + // ghostLink.attr("opacity",1); + setTimeout(function() { + RED.typeSearch.refresh({filter:{input:true}}); + },100); + } + + lastAddedX = nn.x; + lastAddedWidth = nn.w; + + point[0] = nn.x + nn.w/2 + node_width/2 + gridSize * 2; + ghostNode.attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); + rebuildQuickAddLink(); + } else { + quickAddActive = false; + ghostNode.remove(); + } + } + }); + + updateActiveNodes(); + updateSelection(); + redraw(); + } + + function canvasMouseMove() { + var i; + var node; + // Prevent touch scrolling... + //if (d3.touches(this)[0]) { + // d3.event.preventDefault(); + //} + + // TODO: auto scroll the container + //var point = d3.mouse(this); + //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } + //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); + + if (mouse_mode === RED.state.PANNING) { + var pos = [d3.event.pageX,d3.event.pageY]; + if (d3.event.touches) { + var touch0 = d3.event.touches.item(0); + pos = [touch0.pageX, touch0.pageY]; + } + var deltaPos = [ + mouse_position[0]-pos[0], + mouse_position[1]-pos[1] + ]; + + chart.scrollLeft(scroll_position[0]+deltaPos[0]) + chart.scrollTop(scroll_position[1]+deltaPos[1]) + return + } + if (entryCoordinates.x != -1) { + mouse_position = [entryCoordinates.x, entryCoordinates.y] + } else { + mouse_position = d3.touches(this)[0]||d3.mouse(this); + } +if(RED.view.DEBUG) { console.log(`mousemove ${JSON.stringify(mouse_position)}`)} + if (lasso) { + var ox = parseInt(lasso.attr("ox")); + var oy = parseInt(lasso.attr("oy")); + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var w; + var h; + if (mouse_position[0] < ox) { + x = mouse_position[0]; + w = ox-x; + } else { + w = mouse_position[0]-x; + } + if (mouse_position[1] < oy) { + y = mouse_position[1]; + h = oy-y; + } else { + h = mouse_position[1]-y; + } + lasso + .attr("x",x) + .attr("y",y) + .attr("width",w) + .attr("height",h) + ; + return; + } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) { + if (slicePath) { + var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1])) + if (delta > 20) { + var currentPath = slicePath.attr("d") + currentPath += " L"+mouse_position[0]+" "+mouse_position[1] + slicePath.attr("d",currentPath); + slicePathLast = mouse_position + } + } + return + } + + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && mouse_mode != RED.state.DETACHED_DRAGGING && !mousedown_node && !mousedown_group && selectedLinks.length() === 0) { + return; + } + + var mousePos; + // if (mouse_mode === RED.state.GROUP_RESIZE) { + // mousePos = mouse_position; + // var nx = mousePos[0] + mousedown_group.dx; + // var ny = mousePos[1] + mousedown_group.dy; + // switch(mousedown_group.activeHandle) { + // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; + // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; + // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; + // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; + // } + // mousedown_group.dirty = true; + // } + if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + // update drag line + if (drag_lines.length === 0 && mousedown_port_type !== null) { + if (d3.event.shiftKey) { + // Get all the wires we need to detach. + var links = []; + var existingLinks = []; + if (selectedLinks.length() > 0) { + selectedLinks.forEach(function(link) { + if (((mousedown_port_type === PORT_TYPE_OUTPUT && + link.source === mousedown_node && + link.sourcePort === mousedown_port_index + ) || + (mousedown_port_type === PORT_TYPE_INPUT && + link.target === mousedown_node + ))) { + existingLinks.push(link); + } + }) + } else { + var filter; + if (mousedown_port_type === PORT_TYPE_OUTPUT) { + filter = { + source:mousedown_node, + sourcePort: mousedown_port_index + } + } else { + filter = { + target: mousedown_node + } + } + existingLinks = RED.nodes.filterLinks(filter); + } + for (i=0;i 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { + mouse_mode = RED.state.MOVING_ACTIVE; + clickElapsed = 0; + spliceActive = false; + if (movingSet.length() === 1) { + node = movingSet.get(0); + spliceActive = node.n.hasOwnProperty("_def") && + ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && + RED.nodes.filterLinks({ source: node.n }).length === 0 && + RED.nodes.filterLinks({ target: node.n }).length === 0; + } + } + } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { + mousePos = mouse_position; + var minX = 0; + var minY = 0; + var maxX = space_width; + var maxY = space_height; + if(RED.view.DEBUG) { console.log(`mousemove - MOVING_ACTIVE ${JSON.stringify(mousePos)}`)} + for (var n = 0; n 0) { + var i = 0; + + // Prefer to snap nodes to the grid if there is one in the selection + do { + node = movingSet.get(i++); + } while(i 0) { + historyEvent = { + t:"delete", + links: removedLinks, + dirty:RED.nodes.dirty() + }; + RED.history.push(historyEvent); + RED.nodes.dirty(true); + } + hideDragLines(); + } + if (lasso) { + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var x2 = x+parseInt(lasso.attr("width")); + var y2 = y+parseInt(lasso.attr("height")); + var ag = activeGroup; + if (!d3.event.shiftKey) { + clearSelection(); + if (ag) { + if (x < ag.x+ag.w && x2 > ag.x && y < ag.y+ag.h && y2 > ag.y) { + // There was an active group and the lasso intersects with it, + // so reenter the group + enterActiveGroup(ag); + activeGroup.selected = true; + } + } + } + activeGroups.forEach(function(g) { + if (!g.selected) { + if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { + if (!activeGroup || RED.group.contains(activeGroup,g)) { + while (g.g && (!activeGroup || g.g !== activeGroup.id)) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } + } + } + } + }) + + activeNodes.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + if (!activeGroup || RED.group.contains(activeGroup,n)) { + if (n.g && (!activeGroup || n.g !== activeGroup.id)) { + var group = RED.nodes.group(n.g); + while (group.g && (!activeGroup || group.g !== activeGroup.id)) { + group = RED.nodes.group(group.g); + } + if (!group.selected) { + selectGroup(group,true); + } + } else { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + } + } + } + }); + activeJunctions.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + } + }) + + + + // var selectionChanged = false; + // do { + // selectionChanged = false; + // selectedGroups.forEach(function(g) { + // if (g.g && g.selected && RED.nodes.group(g.g).selected) { + // g.selected = false; + // selectionChanged = true; + // } + // }) + // } while(selectionChanged); + + if (activeSubflow) { + activeSubflow.in.forEach(function(n) { + n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); + if (n.selected) { + n.dirty = true; + movingSet.add(n); + } + }); + activeSubflow.out.forEach(function(n) { + n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); + if (n.selected) { + n.dirty = true; + movingSet.add(n); + } + }); + if (activeSubflow.status) { + activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); + if (activeSubflow.status.selected) { + activeSubflow.status.dirty = true; + movingSet.add(activeSubflow.status); + } + } + } + updateSelection(); + lasso.remove(); + lasso = null; + } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { + clearSelection(); + updateSelection(); + } else if (mouse_mode == RED.state.SLICING) { + deleteSelection(); + slicePath.remove(); + slicePath = null; + RED.view.redraw(true); + } else if (mouse_mode == RED.state.SLICING_JUNCTION) { + var removedLinks = new Set() + var addedLinks = [] + var addedJunctions = [] + + var groupedLinks = {} + selectedLinks.forEach(function(l) { + var sourceId = l.source.id+":"+l.sourcePort + groupedLinks[sourceId] = groupedLinks[sourceId] || [] + groupedLinks[sourceId].push(l) + + groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] + groupedLinks[l.target.id].push(l) + }); + var linkGroups = Object.keys(groupedLinks) + linkGroups.sort(function(A,B) { + return groupedLinks[B].length - groupedLinks[A].length + }) + linkGroups.forEach(function(gid) { + var links = groupedLinks[gid] + var junction = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + links = links.filter(function(l) { return !removedLinks.has(l) }) + if (links.length === 0) { + return + } + links.forEach(function(l) { + junction.x += l._sliceLocation.x + junction.y += l._sliceLocation.y + }) + junction.x = Math.round(junction.x/links.length) + junction.y = Math.round(junction.y/links.length) + if (snapGrid) { + junction.x = (gridSize*Math.round(junction.x/gridSize)); + junction.y = (gridSize*Math.round(junction.y/gridSize)); + } + + var nodeGroups = new Set() + + RED.nodes.addJunction(junction) + addedJunctions.push(junction) + let newLink + if (gid === links[0].source.id+":"+links[0].sourcePort) { + newLink = { + source: links[0].source, + sourcePort: links[0].sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: links[0].target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + links.forEach(function(l) { + removedLinks.add(l) + RED.nodes.removeLink(l) + let newLink + if (gid === l.target.id) { + newLink = { + source: l.source, + sourcePort: l.sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: l.target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + nodeGroups.add(l.source.g || "__NONE__") + nodeGroups.add(l.target.g || "__NONE__") + }) + if (nodeGroups.size === 1) { + var group = nodeGroups.values().next().value + if (group !== "__NONE__") { + RED.group.addToGroup(RED.nodes.group(group), junction) + } + } + }) + slicePath.remove(); + slicePath = null; + + if (addedJunctions.length > 0) { + RED.history.push({ + t: 'add', + links: addedLinks, + junctions: addedJunctions, + removedLinks: Array.from(removedLinks) + }) + RED.nodes.dirty(true) + } + RED.view.redraw(true); + } + if (mouse_mode == RED.state.MOVING_ACTIVE) { + if (movingSet.length() > 0) { + var addedToGroup = null; + if (activeHoverGroup) { + for (var j=0;j 0 && mouse_mode == RED.state.MOVING_ACTIVE) { + historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; + if (activeSpliceLink) { + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp + var spliceLink = d3.select(activeSpliceLink).data()[0]; + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: movingSet.get(0).n + }; + var link2 = { + source:movingSet.get(0).n, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + historyEvent.links = [link1,link2]; + historyEvent.removedLinks = [spliceLink]; + updateActiveNodes(); + } + if (addedToGroup) { + historyEvent.addToGroup = addedToGroup; + } + RED.nodes.dirty(true); + RED.history.push(historyEvent); + } + } + } + // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { + // if (mousedown_node.gSelected) { + // delete mousedown_node.gSelected + // } else { + // if (!d3.event.ctrlKey && !d3.event.metaKey) { + // clearSelection(); + // } + // RED.nodes.group(mousedown_node.g).selected = true; + // mousedown_node.selected = true; + // mousedown_node.dirty = true; + // movingSet.add(mousedown_node); + // } + // } + if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) { + // if (mousedown_node) { + // delete mousedown_node.gSelected; + // } + if (mouse_mode === RED.state.DETACHED_DRAGGING) { + var ns = []; + for (var j=0;j 0.3) { + zoomView(scaleFactor-0.1); + } + } + function zoomZero() { zoomView(1); } + function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); } + function searchPrev() { RED.actions.invoke("core:search-previous"); } + function searchNext() { RED.actions.invoke("core:search-next"); } + + + function zoomView(factor) { + var screenSize = [chart.width(),chart.height()]; + var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; + var center = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; + scaleFactor = factor; + var newCenter = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; + var delta = [(newCenter[0]-center[0])*scaleFactor,(newCenter[1]-center[1])*scaleFactor] + chart.scrollLeft(scrollPos[0]-delta[0]); + chart.scrollTop(scrollPos[1]-delta[1]); + + RED.view.navigator.resize(); + redraw(); + if (RED.settings.get("editor.view.view-store-zoom")) { + RED.settings.setLocal('zoom-level', factor.toFixed(1)) + } + } + + function selectNone() { + if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) { + return; + } + if (mouse_mode === RED.state.DETACHED_DRAGGING) { + for (var j=0;j 0) { + activeFlowLinks.push({ + refresh: Math.floor(Math.random()*10000), + node: linkNode, + links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};}) + }); + } + } + } + if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) { + selectedLinks.forEach(function(link) { + if (link.link) { + activeLinks.push(link); + activeLinkNodes[link.source.id] = link.source; + link.source.dirty = true; + activeLinkNodes[link.target.id] = link.target; + link.target.dirty = true; + } + }) + } + } else { + selection.flows = workspaceSelection; + } + } + var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { + if (key === 'nodes' || key === 'flows') { + return value.map(function(n) { return n.id }) + } else if (key === 'link') { + return value.source.id+":"+value.sourcePort+":"+value.target.id; + } else if (key === 'links') { + return value.map(function(link) { + return link.source.id+":"+link.sourcePort+":"+link.target.id; + }); + } + return value; + }); + if (selectionJSON !== lastSelection) { + lastSelection = selectionJSON; + RED.events.emit("view:selection-changed",selection); + } + } + + function editSelection() { + if (movingSet.length() > 0) { + var node = movingSet.get(0).n; + if (node.type === "subflow") { + RED.editor.editSubflow(activeSubflow); + } else if (node.type === "group") { + RED.editor.editGroup(node); + } else { + RED.editor.edit(node); + } + } + } + function deleteSelection(reconnectWires) { + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + var workspaceSelection = RED.workspaces.selection(); + if (workspaceSelection.length > 0) { + var workspaceCount = 0; + workspaceSelection.forEach(function(ws) { if (ws.type === 'tab') { workspaceCount++ } }); + if (workspaceCount === RED.workspaces.count()) { + // Cannot delete all workspaces + return; + } + var historyEvent = { + t: 'delete', + dirty: RED.nodes.dirty(), + nodes: [], + links: [], + groups: [], + junctions: [], + workspaces: [], + subflows: [] + } + var workspaceOrder = RED.nodes.getWorkspaceOrder().slice(0); + + for (var i=0;i 0 || selectedLinks.length() > 0) { + var result; + var node; + var removedNodes = []; + var removedLinks = []; + var removedGroups = []; + var removedJunctions = []; + var removedSubflowOutputs = []; + var removedSubflowInputs = []; + var removedSubflowStatus; + var subflowInstances = []; + var historyEvents = []; + var addToRemovedLinks = function(links) { + if(!links) { return; } + var _links = Array.isArray(links) ? links : [links]; + _links.forEach(function(l) { + removedLinks.push(l); + selectedLinks.remove(l); + }) + } + if (reconnectWires) { + var reconnectResult = RED.nodes.detachNodes(movingSet.nodes()) + var addedLinks = reconnectResult.newLinks; + if (addedLinks.length > 0) { + historyEvents.push({ t:'add', links: addedLinks }) + } + addToRemovedLinks(reconnectResult.removedLinks) + } + + var startDirty = RED.nodes.dirty(); + var startChanged = false; + var selectedGroups = []; + if (movingSet.length() > 0) { + + for (var i=0;i=0; i--) { + var g = selectedGroups[i]; + removedGroups.push(g); + RED.nodes.removeGroup(g); + } + if (removedSubflowOutputs.length > 0) { + result = RED.subflow.removeOutput(removedSubflowOutputs); + if (result) { + addToRemovedLinks(result.links); + } + } + // Assume 0/1 inputs + if (removedSubflowInputs.length == 1) { + result = RED.subflow.removeInput(); + if (result) { + addToRemovedLinks(result.links); + } + } + if (removedSubflowStatus) { + result = RED.subflow.removeStatus(); + if (result) { + addToRemovedLinks(result.links); + } + } + + var instances = RED.subflow.refresh(true); + if (instances) { + subflowInstances = instances.instances; + } + movingSet.clear(); + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) { + RED.nodes.dirty(true); + } + } + + if (selectedLinks.length() > 0) { + selectedLinks.forEach(function(link) { + if (link.link) { + var sourceId = link.source.id; + var targetId = link.target.id; + var sourceIdIndex = link.target.links.indexOf(sourceId); + var targetIdIndex = link.source.links.indexOf(targetId); + historyEvents.push({ + t: "edit", + node: link.source, + changed: link.source.changed, + changes: { + links: $.extend(true,{},{v:link.source.links}).v + } + }) + historyEvents.push({ + t: "edit", + node: link.target, + changed: link.target.changed, + changes: { + links: $.extend(true,{},{v:link.target.links}).v + } + }) + link.source.changed = true; + link.target.changed = true; + link.target.links.splice(sourceIdIndex,1); + link.source.links.splice(targetIdIndex,1); + link.source.dirty = true; + link.target.dirty = true; + + } else { + RED.nodes.removeLink(link); + removedLinks.push(link); + } + }) + } + RED.nodes.dirty(true); + var historyEvent = { + t:"delete", + nodes:removedNodes, + links:removedLinks, + groups: removedGroups, + junctions: removedJunctions, + subflowOutputs:removedSubflowOutputs, + subflowInputs:removedSubflowInputs, + subflow: { + id: activeSubflow?activeSubflow.id:undefined, + instances: subflowInstances + }, + dirty:startDirty + }; + if (removedSubflowStatus) { + historyEvent.subflow.status = removedSubflowStatus; + } + if (historyEvents.length > 0) { + historyEvents.unshift(historyEvent); + RED.history.push({ + t:"multi", + events: historyEvents + }) + } else { + RED.history.push(historyEvent); + } + + selectedLinks.clear(); + updateActiveNodes(); + updateSelection(); + redraw(); + } + } + + function copySelection() { + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + var nodes = []; + var selection = RED.workspaces.selection(); + if (selection.length > 0) { + nodes = []; + selection.forEach(function(n) { + if (n.type === 'tab') { + nodes.push(n); + nodes = nodes.concat(RED.nodes.groups(n.id)); + nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); + } + }); + } else { + selection = RED.view.selection(); + if (selection.nodes) { + selection.nodes.forEach(function(n) { + nodes.push(n); + if (n.type === 'group') { + nodes = nodes.concat(RED.group.getNodes(n,true)); + } + }) + } + } + + if (nodes.length > 0) { + var nns = []; + var nodeCount = 0; + var groupCount = 0; + var junctionCount = 0; + var handled = {}; + for (var n=0;n 0) { + RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); + } else if (groupCount > 0) { + RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"}); + } + } + } + + + function detachSelectedNodes() { + var selection = RED.view.selection(); + if (selection.nodes) { + const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes); + if (removedLinks.length || newLinks.length) { + RED.history.push({ + t: "multi", + events: [ + { t:'delete', links: removedLinks }, + { t:'add', links: newLinks } + ], + dirty: RED.nodes.dirty() + }) + RED.nodes.dirty(true) + } + prepareDrag([selection.nodes[0].x,selection.nodes[0].y]); + mouse_mode = RED.state.DETACHED_DRAGGING; + RED.view.redraw(true); + } + } + + function calculateTextWidth(str, className) { + var result = convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i 1) { + var i=0; + for (i=0;i 0) { + if (drag_lines[0].node === d) { + // Cannot quick-join to self + return + } + if (drag_lines[0].virtualLink && + ( + (drag_lines[0].node.type === 'link in' && d.type !== 'link out') || + (drag_lines[0].node.type === 'link out' && d.type !== 'link in') + ) + ) { + return + } + } + document.body.style.cursor = ""; + if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) { + if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) { + var found = false; + RED.nodes.eachNode(function(n) { + if (n.z == RED.workspaces.active()) { + var hw = n.w/2; + var hh = n.h/2; + if (n.x-hw mouse_position[0] && + n.y-hhmouse_position[1]) { + found = true; + mouseup_node = n; + portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT; + portIndex = 0; + } + } + }); + if (!found && activeSubflow) { + var subflowPorts = []; + if (activeSubflow.status) { + subflowPorts.push(activeSubflow.status) + } + if (activeSubflow.in) { + subflowPorts = subflowPorts.concat(activeSubflow.in) + } + if (activeSubflow.out) { + subflowPorts = subflowPorts.concat(activeSubflow.out) + } + for (var i=0;i mouse_position[0] && + n.y-hhmouse_position[1]) { + found = true; + mouseup_node = n; + portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT; + portIndex = 0; + break; + } + } + } + } else { + mouseup_node = d; + } + var addedLinks = []; + var removedLinks = []; + var modifiedNodes = []; // joining link nodes + + var select_link = null; + + for (i=0;i 0 || removedLinks.length > 0 || modifiedNodes.length > 0) { + // console.log(addedLinks); + // console.log(removedLinks); + // console.log(modifiedNodes); + var historyEvent; + if (modifiedNodes.length > 0) { + historyEvent = { + t:"multi", + events: linkEditEvents, + dirty:RED.nodes.dirty() + }; + } else { + historyEvent = { + t:"add", + links:addedLinks, + removedLinks: removedLinks, + dirty:RED.nodes.dirty() + }; + } + if (activeSubflow) { + var subflowRefresh = RED.subflow.refresh(true); + if (subflowRefresh) { + historyEvent.subflow = { + id:activeSubflow.id, + changed: activeSubflow.changed, + instances: subflowRefresh.instances + } + } + } + RED.history.push(historyEvent); + updateActiveNodes(); + RED.nodes.dirty(true); + } + if (mouse_mode === RED.state.QUICK_JOINING) { + if (addedLinks.length > 0 || modifiedNodes.length > 0) { + hideDragLines(); + if (portType === PORT_TYPE_INPUT && d.outputs > 0) { + showDragLines([{node:d,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (portType === PORT_TYPE_OUTPUT && d.inputs > 0) { + showDragLines([{node:d,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + mousedown_link = select_link; + if (select_link) { + selectedLinks.clear(); + selectedLinks.add(select_link); + updateSelection(); + } else { + selectedLinks.clear(); + } + } + redraw(); + return; + } + + resetMouseVars(); + hideDragLines(); + if (select_link) { + selectedLinks.clear(); + selectedLinks.add(select_link); + } + mousedown_link = select_link; + if (select_link) { + updateSelection(); + } + redraw(); + } + } + + var portLabelHoverTimeout = null; + var portLabelHover = null; + + + function getElementPosition(node) { + var d3Node = d3.select(node); + if (d3Node.attr('class') === 'red-ui-workspace-chart-event-layer') { + return [0,0]; + } + var result = []; + var localPos = [0,0]; + if (node.nodeName.toLowerCase() === 'g') { + var transform = d3Node.attr("transform"); + if (transform) { + localPos = d3.transform(transform).translate; + } + } else { + localPos = [d3Node.attr("x")||0,d3Node.attr("y")||0]; + } + var parentPos = getElementPosition(node.parentNode); + return [localPos[0]+parentPos[0],localPos[1]+parentPos[1]] + + } + + function getPortLabel(node,portType,portIndex) { + var result; + var nodePortLabels = (portType === PORT_TYPE_INPUT)?node.inputLabels:node.outputLabels; + if (nodePortLabels && nodePortLabels[portIndex]) { + return nodePortLabels[portIndex]; + } + var portLabels = (portType === PORT_TYPE_INPUT)?node._def.inputLabels:node._def.outputLabels; + if (typeof portLabels === 'string') { + result = portLabels; + } else if (typeof portLabels === 'function') { + try { + result = portLabels.call(node,portIndex); + } catch(err) { + console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err); + result = null; + } + } else if ($.isArray(portLabels)) { + result = portLabels[portIndex]; + } + return result; + } + function showTooltip(x,y,content,direction) { + var tooltip = eventLayer.append("g") + .attr("transform","translate("+x+","+y+")") + .attr("class","red-ui-flow-port-tooltip"); + + // First check for a user-provided newline - "\\n " + var newlineIndex = content.indexOf("\\n "); + if (newlineIndex > -1 && content[newlineIndex-1] !== '\\') { + content = content.substring(0,newlineIndex)+"..."; + } + + var lines = content.split("\n"); + var labelWidth = 6; + var labelHeight = 12; + var labelHeights = []; + var lineHeight = 0; + lines.forEach(function(l,i) { + var labelDimensions = calculateTextDimensions(l||" ", "red-ui-flow-port-tooltip-label"); + labelWidth = Math.max(labelWidth,labelDimensions[0] + 14); + labelHeights.push(labelDimensions[1]); + if (i === 0) { + lineHeight = labelDimensions[1]; + } + labelHeight += labelDimensions[1]; + }); + var labelWidth1 = (labelWidth/2)-5-2; + var labelWidth2 = labelWidth - 4; + + var labelHeight1 = (labelHeight/2)-5-2; + var labelHeight2 = labelHeight - 4; + var path; + var lx; + var ly = -labelHeight/2; + var anchor; + if (direction === "left") { + path = "M0 0 l -5 -5 v -"+(labelHeight1)+" q 0 -2 -2 -2 h -"+labelWidth+" q -2 0 -2 2 v "+(labelHeight2)+" q 0 2 2 2 h "+labelWidth+" q 2 0 2 -2 v -"+(labelHeight1)+" l 5 -5"; + lx = -14; + anchor = "end"; + } else if (direction === "right") { + path = "M0 0 l 5 -5 v -"+(labelHeight1)+" q 0 -2 2 -2 h "+labelWidth+" q 2 0 2 2 v "+(labelHeight2)+" q 0 2 -2 2 h -"+labelWidth+" q -2 0 -2 -2 v -"+(labelHeight1)+" l -5 -5" + lx = 14; + anchor = "start"; + } else if (direction === "top") { + path = "M0 0 l 5 -5 h "+(labelWidth1)+" q 2 0 2 -2 v -"+labelHeight+" q 0 -2 -2 -2 h -"+(labelWidth2)+" q -2 0 -2 2 v "+labelHeight+" q 0 2 2 2 h "+(labelWidth1)+" l 5 5" + lx = -labelWidth/2 + 6; + ly = -labelHeight-lineHeight+12; + anchor = "start"; + } + tooltip.append("path").attr("d",path); + lines.forEach(function(l,i) { + ly += labelHeights[i]; + // tooltip.append("path").attr("d","M "+(lx-10)+" "+ly+" l 20 0 m -10 -5 l 0 10 ").attr('r',2).attr("stroke","#f00").attr("stroke-width","1").attr("fill","none") + tooltip.append("svg:text").attr("class","red-ui-flow-port-tooltip-label") + .attr("x", lx) + .attr("y", ly) + .attr("text-anchor",anchor) + .text(l||" ") + }); + return tooltip; + } + + function portMouseOver(port,d,portType,portIndex) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + clearTimeout(portLabelHoverTimeout); + var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active + ( + drag_lines.length > 0 && // Currently joining + drag_lines[0].portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT + ( + !drag_lines[0].virtualLink || // Not a link wire + (drag_lines[0].node.type === 'link in' && d.type === 'link out') || + (drag_lines[0].node.type === 'link out' && d.type === 'link in') + ) + ) + + if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { + portLabelHoverTimeout = setTimeout(function() { + var tooltip = getPortLabel(d,portType,portIndex); + if (!tooltip) { + return; + } + var pos = getElementPosition(port.node()); + portLabelHoverTimeout = null; + portLabelHover = showTooltip( + (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)), + (pos[1]+5), + tooltip, + portType===PORT_TYPE_INPUT?"left":"right" + ); + },500); + } + port.classed("red-ui-flow-port-hovered",active); + } + function portMouseOut(port,d,portType,portIndex) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + port.classed("red-ui-flow-port-hovered",false); + } + + function junctionMouseOver(junction, d, portType) { + var active = (portType === undefined) || + (mouse_mode !== RED.state.JOINING && mouse_mode !== RED.state.QUICK_JOINING) || + (drag_lines.length > 0 && drag_lines[0].portType !== portType && !drag_lines[0].virtualLink) + junction.classed("red-ui-flow-junction-hovered", active); + } + function junctionMouseOut(junction, d) { + junction.classed("red-ui-flow-junction-hovered",false); + } + + function prepareDrag(mouse) { + mouse_mode = RED.state.MOVING; + // Called when movingSet should be prepared to be dragged + for (i=0;i 0 && clickElapsed < dblClickInterval) { + mouse_mode = RED.state.DEFAULT; + if (d.type != "subflow") { + if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { + RED.workspaces.show(d.type.substring(8)); + } else { + RED.editor.edit(d); + } + } else { + RED.editor.editSubflow(activeSubflow); + } + clickElapsed = 0; + d3.event.stopPropagation(); + return; + } + if (mouse_mode === RED.state.MOVING) { + // Moving primed, but not active. + if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) { + clearSelection(); + + selectGroup(RED.nodes.group(d.g), false); + enterActiveGroup(RED.nodes.group(d.g)) + + mousedown_node.selected = true; + movingSet.add(mousedown_node); + var mouse = d3.touches(this)[0]||d3.mouse(this); + mouse[0] += d.x-d.w/2; + mouse[1] += d.y-d.h/2; + prepareDrag(mouse); + updateSelection(); + return; + } + } + + groupNodeSelectPrimed = false; + + var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1) + var wasJoining = false; + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + wasJoining = true; + if (drag_lines.length > 0) { + if (drag_lines[0].virtualLink) { + if (d.type === 'link in') { + direction = 1; + } else if (d.type === 'link out') { + direction = 0; + } + } else { + if (drag_lines[0].portType === 1) { + direction = PORT_TYPE_OUTPUT; + } else { + direction = PORT_TYPE_INPUT; + } + } + } + } + + portMouseUp(d, direction, 0); + if (wasJoining) { + d3.selectAll(".red-ui-flow-port-hovered").classed("red-ui-flow-port-hovered",false); + } + } + + + document.addEventListener('pointerlockchange', changeCallback, false); + document.addEventListener('mozpointerlockchange', changeCallback, false); + document.addEventListener('webkitpointerlockchange', changeCallback, false); + document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; + function getPosition(canvas, event) { + var x, y; + + if (event.x != undefined && event.y != undefined) { + x = event.x; + y = event.y; + } else // Firefox method to get the position + { + x = event.clientX + document.body.scrollLeft + + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + + document.documentElement.scrollTop; + } + + x -= canvas.offsetLeft == null ? canvas.clientLeft : canvas.offsetLeft; + y -= canvas.offsetTop == null ? canvas.clientTop : canvas.offsetTop; + + return {x:x, y:y}; + } + //temporary proxy for when mouse is locked + function canvasMouseMove_locked(e) { + + var canvas = $(eventLayer[0])[0]; + + + // if we enter this for the first time, get the initial position + if (entryCoordinates.x == -1) { + entryCoordinates = getPosition(canvas, e); + } + + + //get a reference to the canvas + var movementX = e.movementX || + e.mozMovementX || + e.webkitMovementX || + 0; + + var movementY = e.movementY || + e.mozMovementY || + e.webkitMovementY || + 0; + + + // calculate the new coordinates where we should draw the ship + entryCoordinates.x = entryCoordinates.x + movementX; + entryCoordinates.y = entryCoordinates.y + movementY; + + if (entryCoordinates.x > chart.width() -65) { + entryCoordinates.x = chart.width()-65; + } else if (entryCoordinates.x < 0) { + entryCoordinates.x = 0; + } + + if (entryCoordinates.y > chart.height() - 85) { + entryCoordinates.y = chart.height() - 85; + } else if (entryCoordinates.y < 0) { + entryCoordinates.y = 0; + } + + + // determine the direction + var direction = 0; + if (movementX > 0) { + direction = 1; + } else if (movementX < 0) { + direction = -1; + } + // console.log(entryCoordinates) + + + d3.event = e + canvasMouseMove.call(this,e); + //canvasMouseMove.call(document.querySelector("g.red-ui-workspace-chart-event-layer")); + } + function changeCallback(e) { + var canvas = $(eventLayer[0])[0] + // var workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0]; + // var workspaceChart = $("#red-ui-workspace-chart")[0]; + if (document.pointerLockElement === canvas || + document.mozPointerLockElement === canvas || + document.webkitPointerLockElement === canvas) { + + // we've got a pointerlock for our element, add a mouselistener + canvas.addEventListener("mousemove", canvasMouseMove_locked, false); + // outer.on("mousemove", canvasMouseMove_locked) + // document.addEventListener("mousemove", canvasMouseMove_locked, false); + } else { + + // pointer lock is no longer active, remove the callback + canvas.removeEventListener("mousemove", canvasMouseMove_locked, false); + // document.removeEventListener("mousemove", canvasMouseMove_locked, false); + // if(outer.off) {outer.off("mousemove", canvasMouseMove_locked)} + // if(outer.removeEventListener) {outer.removeEventListener("mousemove", canvasMouseMove_locked)} + // and reset the entry coordinates + entryCoordinates = {x:-1, y:-1}; + } + } + + function nodeMouseDown(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } + try { + // workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0] + var canvas = $(eventLayer[0])[0] + canvas.requestPointerLock = canvas.requestPointerLock || + canvas.mozRequestPointerLock || + canvas.webkitRequestPointerLock; + canvas.requestPointerLock() + console.log("got pointer lock") + } catch (error) { + console.error(error) + } + + focusView(); + if (d3.event.button === 1) { + return; + } + //var touch0 = d3.event; + //var pos = [touch0.pageX,touch0.pageY]; + //RED.touch.radialMenu.show(d3.select(this),pos); + if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { + var historyEvent = RED.history.peek(); + if (activeSpliceLink) { + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp + var spliceLink = d3.select(activeSpliceLink).data()[0]; + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: movingSet.get(0).n + }; + var link2 = { + source:movingSet.get(0).n, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + + historyEvent.links = [link1,link2]; + historyEvent.removedLinks = [spliceLink]; + updateActiveNodes(); + } + + if (activeHoverGroup) { + for (var j=0;j 30 ? 25 : (mousedown_node.w > 0 ? 8 : 3); + if (edgeDelta < targetEdgeDelta) { + if (clickPosition < 0) { + cnodes = [mousedown_node].concat(RED.nodes.getAllUpstreamNodes(mousedown_node)); + } else { + cnodes = [mousedown_node].concat(RED.nodes.getAllDownstreamNodes(mousedown_node)); + } + } else { + cnodes = RED.nodes.getAllFlowNodes(mousedown_node); + } + for (var n=0;n 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + function nodeMouseOut(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseOut", mouse_mode,d); } + this.parentNode.classList.remove("red-ui-flow-node-hovered"); + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + if (drag_lines.length > 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + + function portMouseDownProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchStartProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchEndProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + + function junctionMouseOverProxy(e) { junctionMouseOver(d3.select(this), this.__data__, this.__portType__) } + function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) } + + function linkMouseDown(d) { + if (RED.view.DEBUG) { + console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); + } + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + if (d3.event.button === 2) { + return + } + mousedown_link = d; + + if (!(d3.event.metaKey || d3.event.ctrlKey)) { + clearSelection(); + } + if (d3.event.metaKey || d3.event.ctrlKey) { + if (!selectedLinks.has(mousedown_link)) { + selectedLinks.add(mousedown_link); + } else { + if (selectedLinks.length() !== 1) { + selectedLinks.remove(mousedown_link); + } + } + } else { + selectedLinks.add(mousedown_link); + } + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + if (!mousedown_link.link && movingSet.length() === 0 && (d3.event.touches || d3.event.button === 0) && selectedLinks.length() === 1 && selectedLinks.has(mousedown_link) && (d3.event.metaKey || d3.event.ctrlKey)) { + d3.select(this).classed("red-ui-flow-link-splice",true); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup}); + } + } + function linkTouchStart(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + mousedown_link = d; + clearSelection(); + selectedLinks.clear(); + selectedLinks.add(mousedown_link); + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + + var obj = d3.select(document.body); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + },touchLongPressTimeout); + d3.event.preventDefault(); + } + + function groupMouseUp(g) { + if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { + mouse_mode = RED.state.DEFAULT; + RED.editor.editGroup(g); + d3.event.stopPropagation(); + return; + } + + } + + function groupMouseDown(g) { + var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); + // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { + // return + // } + + focusView(); + if (d3.event.button === 1) { + return; + } + + if (mouse_mode == RED.state.QUICK_JOINING) { + d3.event.stopPropagation(); + return; + } else if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + mousedown_group = g; + + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = ( + lastClickNode == g && + (d3.event.touches || d3.event.button === 0) && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && + clickElapsed < dblClickInterval + ); + lastClickNode = g; + + if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { + if (g === activeGroup) { + exitActiveGroup(); + } + deselectGroup(g); + d3.event.stopPropagation(); + } else { + if (!g.selected) { + if (!d3.event.ctrlKey && !d3.event.metaKey) { + var ag = activeGroup; + clearSelection(); + if (ag && g.g === ag.id) { + enterActiveGroup(ag); + activeGroup.selected = true; + } + } + if (activeGroup) { + if (!RED.group.contains(activeGroup,g)) { + // Clicked on a group that is outside the activeGroup + exitActiveGroup(); + } else { + } + } + selectGroup(g,true);//!wasSelected); + } else if (activeGroup && g.g !== activeGroup.id){ + exitActiveGroup(); + } + + + if (d3.event.button != 2) { + var d = g.nodes[0]; + prepareDrag(mouse); + mousedown_group.dx = mousedown_group.x - mouse[0]; + mousedown_group.dy = mousedown_group.y - mouse[1]; + } + } + + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function selectGroup(g, includeNodes, addToMovingSet) { + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + if (addToMovingSet !== false) { + movingSet.add(g); + } + if (includeNodes) { + var currentSet = new Set(movingSet.nodes()); + var allNodes = RED.group.getNodes(g,true); + allNodes.forEach(function(n) { + if (!currentSet.has(n)) { + movingSet.add(n) + // n.selected = true; + } + n.dirty = true; + }) + } + } + function enterActiveGroup(group) { + if (activeGroup) { + exitActiveGroup(); + } + group.active = true; + group.dirty = true; + activeGroup = group; + movingSet.remove(group); + } + function exitActiveGroup() { + if (activeGroup) { + activeGroup.active = false; + activeGroup.dirty = true; + deselectGroup(activeGroup); + selectGroup(activeGroup,true); + activeGroup = null; + } + } + function deselectGroup(g) { + if (g.selected) { + g.selected = false; + g.dirty = true; + } + var nodeSet = new Set(g.nodes); + nodeSet.add(g); + for (var i = movingSet.length()-1; i >= 0; i -= 1) { + var msn = movingSet.get(i); + if (nodeSet.has(msn.n) || msn.n === g) { + msn.n.selected = false; + msn.n.dirty = true; + movingSet.remove(msn.n,i) + } + } + } + function getGroupAt(x,y) { + // x,y expected to be in node-co-ordinate space + var candidateGroups = {}; + for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { + candidateGroups[g.id] = g; + } + } + var ids = Object.keys(candidateGroups); + if (ids.length > 1) { + ids.forEach(function(id) { + if (candidateGroups[id] && candidateGroups[id].g) { + delete candidateGroups[candidateGroups[id].g] + } + }) + ids = Object.keys(candidateGroups); + } + if (ids.length === 0) { + return null; + } else { + return candidateGroups[ids[ids.length-1]] + } + } + + function isButtonEnabled(d) { + var buttonEnabled = true; + var ws = RED.nodes.workspace(RED.workspaces.active()); + if (ws && !ws.disabled && !d.d) { + if (d._def.button.hasOwnProperty('enabled')) { + if (typeof d._def.button.enabled === "function") { + buttonEnabled = d._def.button.enabled.call(d); + } else { + buttonEnabled = d._def.button.enabled; + } + } + } else { + buttonEnabled = false; + } + return buttonEnabled; + } + + function nodeButtonClicked(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + if (d3.event) { + d3.event.stopPropagation(); + } + return; + } + var activeWorkspace = RED.workspaces.active(); + var ws = RED.nodes.workspace(activeWorkspace); + if (ws && !ws.disabled && !d.d) { + if (d._def.button.toggle) { + d[d._def.button.toggle] = !d[d._def.button.toggle]; + d.dirty = true; + } + if (d._def.button.onclick) { + try { + d._def.button.onclick.call(d); + } catch(err) { + console.log("Definition error: "+d.type+".onclick",err); + } + } + if (d.dirty) { + redraw(); + } + } else { + if (activeSubflow) { + RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning"); + } else { + RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning"); + } + } + if (d3.event) { + d3.event.preventDefault(); + } + } + + function showTouchMenu(obj,pos) { + var mdn = mousedown_node; + var options = []; + options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); + options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); + options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); + options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); + options.push({name:"select",onselect:function() {selectAll();}}); + options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); + options.push({name:"add",onselect:function() { + chartPos = chart.offset(); + showQuickAddDialog({ + position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()], + touchTrigger:true + }) + }}); + + RED.touch.radialMenu.show(obj,pos,options); + resetMouseVars(); + } + + function createIconAttributes(iconUrl, icon_group, d) { + var fontAwesomeUnicode = null; + if (iconUrl.indexOf("font-awesome/") === 0) { + var iconName = iconUrl.substr(13); + var fontAwesomeUnicode = RED.nodes.fontAwesome.getIconUnicode(iconName); + if (!fontAwesomeUnicode) { + var iconPath = RED.utils.getDefaultNodeIcon(d._def, d); + iconUrl = RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file; + } + } + if (fontAwesomeUnicode) { + // Since Node-RED workspace uses SVG, i tag cannot be used for font-awesome icon. + // On SVG, use text tag as an alternative. + icon_group.append("text") + .attr("xlink:href",iconUrl) + .attr("class","fa-lg") + .attr("x",15) + .text(fontAwesomeUnicode); + } else { + var icon = icon_group.append("image") + .style("display","none") + .attr("xlink:href",iconUrl) + .attr("class","red-ui-flow-node-icon") + .attr("x",0) + .attr("width","30") + .attr("height","30"); + + var img = new Image(); + img.src = iconUrl; + img.onload = function() { + if (!iconUrl.match(/\.svg$/)) { + var largestEdge = Math.max(img.width,img.height); + var scaleFactor = 1; + if (largestEdge > 30) { + scaleFactor = 30/largestEdge; + } + var width = img.width * scaleFactor; + var height = img.height * scaleFactor; + icon.attr("width",width); + icon.attr("height",height); + icon.attr("x",15-width/2); + } + icon.attr("xlink:href",iconUrl); + icon.style("display",null); + //if ("right" == d._def.align) { + // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);}); + // icon_shade.attr("x",function(d){return d.w-30}); + // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);}); + //} + } + } + } + + function redrawStatus(d,nodeEl) { + if (d.z !== RED.workspaces.active()) { + return; + } + if (!nodeEl) { + nodeEl = document.getElementById(d.id); + } + if (nodeEl) { + // Do not show node status if: + // - global flag set + // - node has no status + // - node is disabled + if (!showStatus || !d.status || d.d === true) { + nodeEl.__statusGroup__.style.display = "none"; + } else { + nodeEl.__statusGroup__.style.display = "inline"; + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + nodeEl.__statusShape__.style.display = "none"; + nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); + } else { + nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); + var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; + nodeEl.__statusShape__.style.display = "inline"; + nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); + } + if (d.status.hasOwnProperty('text')) { + nodeEl.__statusLabel__.textContent = d.status.text; + } else { + nodeEl.__statusLabel__.textContent = ""; + } + } + delete d.dirtyStatus; + } + } + + var pendingRedraw; + + function redraw() { + if (RED.view.DEBUG_SYNC_REDRAW) { + _redraw(); + } else { + if (pendingRedraw) { + cancelAnimationFrame(pendingRedraw); + } + pendingRedraw = requestAnimationFrame(_redraw); + } + } + + function _redraw() { + eventLayer.attr("transform","scale("+scaleFactor+")"); + outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); + + // Don't bother redrawing nodes if we're drawing links + if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) { + + var dirtyNodes = {}; + + if (activeSubflow) { + var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); + subflowOutputs.exit().remove(); + var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output") + outGroup.each(function(d,i) { + var node = d3.select(this); + var nodeContents = document.createDocumentFragment(); + + d.h = 40; + d.resize = true; + d.dirty = true; + + var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); + mainRect.__data__ = d; + mainRect.setAttribute("class", "red-ui-flow-subflow-port"); + mainRect.setAttribute("rx", 8); + mainRect.setAttribute("ry", 8); + mainRect.setAttribute("width", 40); + mainRect.setAttribute("height", 40); + node[0][0].__mainRect__ = mainRect; + d3.select(mainRect) + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend",nodeTouchEnd) + nodeContents.appendChild(mainRect); + + var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + output_groupEl.setAttribute("x",0); + output_groupEl.setAttribute("y",0); + node[0][0].__outputLabelGroup__ = output_groupEl; + + var output_output = document.createElementNS("http://www.w3.org/2000/svg","text"); + output_output.setAttribute("class","red-ui-flow-port-label"); + output_output.style["font-size"] = "10px"; + output_output.textContent = "output"; + output_groupEl.appendChild(output_output); + node[0][0].__outputOutput__ = output_output; + + var output_number = document.createElementNS("http://www.w3.org/2000/svg","text"); + output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index"); + output_number.setAttribute("x",0); + output_number.setAttribute("y",0); + output_number.textContent = d.i+1; + output_groupEl.appendChild(output_number); + node[0][0].__outputNumber__ = output_number; + + var output_border = document.createElementNS("http://www.w3.org/2000/svg","path"); + output_border.setAttribute("d","M 40 1 l 0 38") + output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border") + output_groupEl.appendChild(output_border); + node[0][0].__outputBorder__ = output_border; + + nodeContents.appendChild(output_groupEl); + + var text = document.createElementNS("http://www.w3.org/2000/svg","g"); + text.setAttribute("class","red-ui-flow-port-label"); + text.setAttribute("transform","translate(38,0)"); + text.setAttribute('style', 'fill : #888'); // hard coded here! + node[0][0].__textGroup__ = text; + nodeContents.append(text); + + var portEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + portEl.setAttribute('transform','translate(-5,15)') + + var port = document.createElementNS("http://www.w3.org/2000/svg","rect"); + port.setAttribute("class","red-ui-flow-port"); + port.setAttribute("rx",3); + port.setAttribute("ry",3); + port.setAttribute("width",10); + port.setAttribute("height",10); + portEl.appendChild(port); + port.__data__ = d; + + d3.select(port) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) + .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); + + node[0][0].__port__ = portEl + nodeContents.appendChild(portEl); + node[0][0].appendChild(nodeContents); + }); + + var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); + subflowInputs.exit().remove(); + var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); + inGroup.each(function(d,i) { + d.w=40; + d.h=40; + }); + inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) + // TODO: This is exactly the same set of handlers used for regular nodes - DRY + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); + + inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);}) + .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);}); + + inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input"); + + var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); + subflowStatus.exit().remove(); + + var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); + statusGroup.each(function(d,i) { + d.w=40; + d.h=40; + }); + statusGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) + // TODO: This is exactly the same set of handlers used for regular nodes - DRY + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); + + statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) + .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); + + statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status"); + + subflowOutputs.each(function(d,i) { + if (d.dirty) { + + var port_height = 40; + + var self = this; + var thisNode = d3.select(this); + + dirtyNodes[d.id] = d; + + var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || ""; + var hideLabel = (label.length < 1) + + var labelParts; + if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) { + labelParts = getLabelParts(label, "red-ui-flow-node-label"); + if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) { + d.resize = true; + } + this.__label__ = label; + this.__labelLineCount__ = labelParts.lines.length; + + if (hideLabel) { + d.h = Math.max(port_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height); + } + this.__hideLabel__ = hideLabel; + } + + if (d.resize) { + var ow = d.w; + if (hideLabel) { + d.w = port_height; + } else { + d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) ); + } + if (ow !== undefined) { + d.x += (d.w-ow)/2; + } + d.resize = false; + } + + this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + // This might be the first redraw after a node has been click-dragged to start a move. + // So its selected state might have changed since the last redraw. + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + if (mouse_mode != RED.state.MOVING_ACTIVE) { + this.classList.toggle("red-ui-flow-node-disabled", d.d === true); + this.__mainRect__.setAttribute("width", d.w) + this.__mainRect__.setAttribute("height", d.h) + this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); + + if (labelParts) { + // The label has changed + var sa = labelParts.lines; + var sn = labelParts.lines.length; + var textLines = this.__textGroup__.childNodes; + while(textLines.length > sn) { + textLines[textLines.length-1].remove(); + } + for (var i=0; i0?7:0))/20)) ); + } + if (ow !== undefined) { + d.x += (d.w-ow)/2; + } + d.resize = false; + } + if (d._colorChanged) { + var newColor = RED.utils.getNodeColor(d.type,d._def); + this.__mainRect__.setAttribute("fill",newColor); + if (this.__buttonGroupButton__) { + this.__buttonGroupButton__.settAttribute("fill",newColor); + } + delete d._colorChanged; + } + //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); + this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + // This might be the first redraw after a node has been click-dragged to start a move. + // So its selected state might have changed since the last redraw. + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + if (mouse_mode != RED.state.MOVING_ACTIVE) { + this.classList.toggle("red-ui-flow-node-disabled", d.d === true); + this.__mainRect__.setAttribute("width", d.w) + this.__mainRect__.setAttribute("height", d.h) + this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); + + if (labelParts) { + // The label has changed + var sa = labelParts.lines; + var sn = labelParts.lines.length; + var textLines = this.__textGroup__.childNodes; + while(textLines.length > sn) { + textLines[textLines.length-1].remove(); + } + for (var i=0; i numOutputs) { + var port = this.__outputs__.pop(); + RED.hooks.trigger("viewRemovePort",{ + node:d, + el:this, + port:port, + portType: "output", + portIndex: this.__outputs__.length + }) + port.remove(); + } + for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { + var portGroup; + if (portIndex === this.__outputs__.length) { + portGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + portGroup.setAttribute("class","red-ui-flow-port-output"); + var portPort; + if (d.type === "link out") { + portPort = document.createElementNS("http://www.w3.org/2000/svg","circle"); + portPort.setAttribute("cx",11); + portPort.setAttribute("cy",5); + portPort.setAttribute("r",5); + portPort.setAttribute("class","red-ui-flow-port red-ui-flow-link-port"); + } else { + portPort = document.createElementNS("http://www.w3.org/2000/svg","rect"); + portPort.setAttribute("rx",3); + portPort.setAttribute("ry",3); + portPort.setAttribute("width",10); + portPort.setAttribute("height",10); + portPort.setAttribute("class","red-ui-flow-port"); + } + portGroup.appendChild(portPort); + portGroup.__port__ = portPort; + portPort.__data__ = this.__data__; + portPort.__portType__ = PORT_TYPE_OUTPUT; + portPort.__portIndex__ = portIndex; + portPort.addEventListener("mousedown", portMouseDownProxy); + portPort.addEventListener("touchstart", portTouchStartProxy); + portPort.addEventListener("mouseup", portMouseUpProxy); + portPort.addEventListener("touchend", portTouchEndProxy); + portPort.addEventListener("mouseover", portMouseOverProxy); + portPort.addEventListener("mouseout", portMouseOutProxy); + + this.appendChild(portGroup); + this.__outputs__.push(portGroup); + RED.hooks.trigger("viewAddPort",{node:d,el: this, port: portGroup, portType: "output", portIndex: portIndex}) + } else { + portGroup = this.__outputs__[portIndex]; + } + var x = d.w - 5; + var y = (d.h/2)-((numOutputs-1)/2)*13; + portGroup.setAttribute("transform","translate("+x+","+((y+13*portIndex)-5)+")") + } + if (d._def.icon) { + var icon = thisNode.select(".red-ui-flow-node-icon"); + var faIcon = thisNode.select(".fa-lg"); + var current_url; + if (!icon.empty()) { + current_url = icon.attr("xlink:href"); + } else { + current_url = faIcon.attr("xlink:href"); + } + var new_url = RED.utils.getNodeIcon(d._def,d); + if (new_url !== current_url) { + if (!icon.empty()) { + icon.remove(); + } else { + faIcon.remove(); + } + var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); + createIconAttributes(new_url, iconGroup, d); + icon = thisNode.select(".red-ui-flow-node-icon"); + faIcon = thisNode.select(".fa-lg"); + } + + icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); + this.__iconShade__.setAttribute("height", d.h ); + this.__iconShadeBorder__.setAttribute("d", + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + ); + faIcon.attr("y",(d.h+13)/2); + } + // this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); + // this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); + // this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); + // this.__errorBadge__.classList.toggle("hide", d.valid); + + thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { + var port = d3.select(this); + port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) + }); + + if (d._def.button) { + var buttonEnabled = isButtonEnabled(d); + this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); + if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { + this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); + } + + var x = d._def.align == "right"?d.w-6:-25; + if (d._def.button.toggle && !d[d._def.button.toggle]) { + x = x - (d._def.align == "right"?8:-8); + } + this.__buttonGroup__.setAttribute("transform", "translate("+x+",2)"); + + if (d._def.button.toggle) { + this.__buttonGroupButton__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) + this.__buttonGroupBackground__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) + } + + if (typeof d._def.button.visible === "function") { // is defined and a function... + if (d._def.button.visible.call(d) === false) { + this.__buttonGroup__.style.display = "none"; + } + else { + this.__buttonGroup__.style.display = "inherit"; + } + } + } + // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); + // thisNode.selectAll("text.node_badge_label").text(function(d,i) { + // if (d._def.badge) { + // if (typeof d._def.badge == "function") { + // try { + // return d._def.badge.call(d); + // } catch(err) { + // console.log("Definition error: "+d.type+".badge",err); + // return ""; + // } + // } else { + // return d._def.badge; + // } + // } + // return ""; + // }); + } + + if (d.dirtyStatus) { + redrawStatus(d,this); + } + d.dirty = false; + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } + } + + RED.hooks.trigger("viewRedrawNode",{node:d,el:this}) + }); + + if (nodesReordered) { + node.sort(function(a,b) { + return a._index - b._index; + }) + } + + var junction = junctionLayer.selectAll(".red-ui-flow-junction").data( + activeJunctions, + d => d.id + ) + var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction") + junctionEnter.each(function(d,i) { + var junction = d3.select(this); + var contents = document.createDocumentFragment(); + // d.added = true; + var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionBack.setAttribute("class","red-ui-flow-junction-background"); + junctionBack.setAttribute("x",-5); + junctionBack.setAttribute("y",-5); + junctionBack.setAttribute("width",10); + junctionBack.setAttribute("height",10); + junctionBack.setAttribute("rx",3); + junctionBack.setAttribute("ry",3); + junctionBack.__data__ = d; + this.__junctionBack__ = junctionBack; + contents.appendChild(junctionBack); + + var junctionInput = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionInput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-input"); + junctionInput.setAttribute("x",-5); + junctionInput.setAttribute("y",-5); + junctionInput.setAttribute("width",10); + junctionInput.setAttribute("height",10); + junctionInput.setAttribute("rx",3); + junctionInput.setAttribute("ry",3); + junctionInput.__data__ = d; + junctionInput.__portType__ = PORT_TYPE_INPUT; + junctionInput.__portIndex__ = 0; + this.__junctionInput__ = junctionOutput; + contents.appendChild(junctionInput); + junctionInput.addEventListener("mouseup", portMouseUpProxy); + junctionInput.addEventListener("mousedown", portMouseDownProxy); + + + this.__junctionInput__ = junctionInput; + contents.appendChild(junctionInput); + var junctionOutput = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionOutput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-output"); + junctionOutput.setAttribute("x",-5); + junctionOutput.setAttribute("y",-5); + junctionOutput.setAttribute("width",10); + junctionOutput.setAttribute("height",10); + junctionOutput.setAttribute("rx",3); + junctionOutput.setAttribute("ry",3); + junctionOutput.__data__ = d; + junctionOutput.__portType__ = PORT_TYPE_OUTPUT; + junctionOutput.__portIndex__ = 0; + this.__junctionOutput__ = junctionOutput; + contents.appendChild(junctionOutput); + junctionOutput.addEventListener("mouseup", portMouseUpProxy); + junctionOutput.addEventListener("mousedown", portMouseDownProxy); + + junctionOutput.addEventListener("mouseover", junctionMouseOverProxy); + junctionOutput.addEventListener("mouseout", junctionMouseOutProxy); + junctionInput.addEventListener("mouseover", junctionMouseOverProxy); + junctionInput.addEventListener("mouseout", junctionMouseOutProxy); + junctionBack.addEventListener("mouseover", junctionMouseOverProxy); + junctionBack.addEventListener("mouseout", junctionMouseOutProxy); + + // These handlers expect to be registered as d3 events + d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp); + + junction[0][0].appendChild(contents); + }) + junction.exit().remove(); + junction.each(function(d) { + var junction = d3.select(this); + this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")"); + if (d.dirty) { + junction.classed("red-ui-flow-junction-dragging", mouse_mode === RED.state.MOVING_ACTIVE && movingSet.has(d)) + junction.classed("selected", !!d.selected) + dirtyNodes[d.id] = d; + + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } + + } + + }) + + var link = linkLayer.selectAll(".red-ui-flow-link").data( + activeLinks, + function(d) { + return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; + } + ); + var linkEnter = link.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link"); + + linkEnter.each(function(d,i) { + var l = d3.select(this); + var pathContents = document.createDocumentFragment(); + + d.added = true; + var pathBack = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathBack.__data__ = d; + pathBack.setAttribute("class","red-ui-flow-link-background red-ui-flow-link-path"+(d.link?" red-ui-flow-link-link":"")); + this.__pathBack__ = pathBack; + pathContents.appendChild(pathBack); + d3.select(pathBack) + .on("mousedown",linkMouseDown) + .on("touchstart",linkTouchStart) + .on("mousemove", function(d) { + if (mouse_mode === RED.state.SLICING) { + + selectedLinks.add(d) + l.classed("red-ui-flow-link-splice",true) + redraw() + } else if (mouse_mode === RED.state.SLICING_JUNCTION && !d.link) { + if (!l.classed("red-ui-flow-link-splice")) { + // Find intersection point + var lineLength = pathLine.getTotalLength(); + var pos; + var delta = Infinity; + for (var i = 0; i < lineLength; i++) { + var linePos = pathLine.getPointAtLength(i); + var posDeltaX = Math.abs(linePos.x-d3.event.offsetX) + var posDeltaY = Math.abs(linePos.y-d3.event.offsetY) + var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY + if (posDelta < delta) { + pos = linePos + delta = posDelta + } + } + d._sliceLocation = pos + selectedLinks.add(d) + l.classed("red-ui-flow-link-splice",true) + redraw() + } + } + }) + + var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathOutline.__data__ = d; + pathOutline.setAttribute("class","red-ui-flow-link-outline red-ui-flow-link-path"); + this.__pathOutline__ = pathOutline; + pathContents.appendChild(pathOutline); + + var pathLine = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathLine.__data__ = d; + pathLine.setAttribute("class","red-ui-flow-link-line red-ui-flow-link-path"+ + (d.link?" red-ui-flow-link-link":(activeSubflow?" red-ui-flow-subflow-link":""))); + this.__pathLine__ = pathLine; + pathContents.appendChild(pathLine); + + l[0][0].appendChild(pathContents); + }); + + link.exit().remove(); + link.each(function(d) { + var link = d3.select(this); + if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { + var numOutputs = d.source.outputs || 1; + var sourcePort = d.sourcePort || 0; + var y = -((numOutputs-1)/2)*13 +13*sourcePort; + d.x1 = d.source.x+(d.source.w/2||0); + d.y1 = d.source.y+y; + d.x2 = d.target.x-(d.target.w/2||0); + d.y2 = d.target.y; + + // return "M "+d.x1+" "+d.y1+ + // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ + // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ + // d.x2+" "+d.y2; + var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); + if (/NaN/.test(path)) { + path = "" + } + this.__pathBack__.setAttribute("d",path); + this.__pathOutline__.setAttribute("d",path); + this.__pathLine__.setAttribute("d",path); + this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); + this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); + } + + this.classList.toggle("red-ui-flow-link-selected", !!d.selected); + + var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); + this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) + delete d.added; + }) + var offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow").data( + activeFlowLinks, + function(d) { + return d.node.id+":"+d.refresh + } + ); + + var offLinksEnter = offLinks.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link-off-flow"); + offLinksEnter.each(function(d,i) { + var g = d3.select(this); + var s = 1; + var labelAnchor = "start"; + if (d.node.type === "link in") { + s = -1; + labelAnchor = "end"; + } + var stemLength = s*30; + var branchLength = s*20; + var l = g.append("svg:path").attr("class","red-ui-flow-link-link").attr("d","M 0 0 h "+stemLength); + var links = d.links; + var flows = Object.keys(links); + var tabOrder = RED.nodes.getWorkspaceOrder(); + flows.sort(function(A,B) { + return tabOrder.indexOf(A) - tabOrder.indexOf(B); + }); + var linkWidth = 10; + var h = node_height; + var y = -(flows.length-1)*h/2; + var linkGroups = g.selectAll(".red-ui-flow-link-group").data(flows); + var enterLinkGroups = linkGroups.enter().append("g").attr("class","red-ui-flow-link-group") + .on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',true)}) + .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',false)}) + .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) + .on('mouseup', function(f) { + if (mouse_mode !== 0) { + return + } + d3.event.stopPropagation(); + var targets = d.links[f]; + RED.workspaces.show(f); + targets.forEach(function(n) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + if (targets.length === 1) { + RED.view.reveal(n.id); + } + }); + updateSelection(); + redraw(); + }); + enterLinkGroups.each(function(f) { + var linkG = d3.select(this); + linkG.append("svg:path") + .attr("class","red-ui-flow-link-link") + .attr("d", + "M "+stemLength+" 0 "+ + "C "+(stemLength+(1.7*branchLength))+" "+0+ + " "+(stemLength+(0.1*branchLength))+" "+y+" "+ + (stemLength+branchLength*1.5)+" "+y+" " + ); + linkG.append("svg:path") + .attr("class","red-ui-flow-link-port") + .attr("d", + "M "+(stemLength+branchLength*1.5+s*(linkWidth+7))+" "+(y-12)+" "+ + "h "+(-s*linkWidth)+" "+ + "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*-3)+" 3 "+ + "v 18 "+ + "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*3)+" 3 "+ + "h "+(s*linkWidth) + ); + linkG.append("svg:path") + .attr("class","red-ui-flow-link-port") + .attr("d", + "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y-12)+" "+ + "h "+(s*(linkWidth*3))+" "+ + "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y+12)+" "+ + "h "+(s*(linkWidth*3)) + ).style("stroke-dasharray","12 3 8 4 3"); + linkG.append("rect").attr("class","red-ui-flow-port red-ui-flow-link-port") + .attr("x",stemLength+branchLength*1.5-4+(s*4)) + .attr("y",y-4) + .attr("rx",2) + .attr("ry",2) + .attr("width",8) + .attr("height",8); + linkG.append("rect") + .attr("x",stemLength+branchLength*1.5-(s===-1?node_width:0)) + .attr("y",y-12) + .attr("width",node_width) + .attr("height",24) + .style("stroke","none") + .style("fill","transparent") + var tab = RED.nodes.workspace(f); + var label; + if (tab) { + label = tab.label || tab.id; + } + linkG.append("svg:text") + .attr("class","red-ui-flow-port-label") + .attr("x",stemLength+branchLength*1.5+(s*15)) + .attr("y",y+1) + .style("font-size","10px") + .style("text-anchor",labelAnchor) + .text(label); + + y += h; + }); + linkGroups.exit().remove(); + }); + offLinks.exit().remove(); + offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow"); + offLinks.each(function(d) { + var s = 1; + if (d.node.type === "link in") { + s = -1; + } + var link = d3.select(this); + link.attr("transform", function(d) { return "translate(" + (d.node.x+(s*d.node.w/2)) + "," + (d.node.y) + ")"; }); + + }) + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.exit().each(function(d,i) { + document.getElementById("group_select_"+d.id).remove() + }).remove(); + var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") + var addedGroups = false; + groupEnter.each(function(d,i) { + addedGroups = true; + var g = d3.select(this); + g.attr("id",d.id); + + var groupBorderRadius = 4; + + var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .classed("red-ui-flow-group-outline-select-background",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4); + + + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4) + selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); + selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); + selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); + + g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); + + g.append('rect').classed("red-ui-flow-group-body",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({ + "fill":d.fill||"none", + "stroke": d.stroke||"none", + }) + g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + g.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + g.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); + + g.append('svg:text').attr("class","red-ui-flow-group-label"); + d.dirty = true; + }); + if (addedGroups) { + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._index - b._index; + } + }) + } + group[0].reverse(); + var groupOpCount=0; + group.each(function(d,i) { + groupOpCount++ + if (d.resize) { + d.minWidth = 0; + delete d.resize; + } + if (d.dirty || dirtyGroups[d.id]) { + var g = d3.select(this); + var recalculateLabelOffsets = false; + if (d.nodes.length > 0) { + // If the group was just moved, all of its contents was + // also moved - so no need to recalculate its bounding box + if (!d.groupMoved) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + var margin = 26; + d.nodes.forEach(function(n) { + groupOpCount++ + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-margin); + maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+margin); + } else { + minX = Math.min(minX,n.x-margin) + minY = Math.min(minY,n.y-margin) + maxX = Math.max(maxX,n.x+n.w+margin) + maxY = Math.max(maxY,n.y+n.h+margin) + } + }); + + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + recalculateLabelOffsets = true; + // if set explicitly to false, this group has just been + // imported so needed this initial resize calculation. + // Now that's done, delete the flag so the normal + // logic kicks in. + if (d.groupMoved === false) { + delete d.groupMoved; + } + } else { + delete d.groupMoved; + } + } else { + d.w = 40; + d.h = 40; + recalculateLabelOffsets = true; + } + if (recalculateLabelOffsets) { + if (!d.minWidth) { + if (d.style.label && d.name) { + var labelParts = getLabelParts(d.name||"","red-ui-flow-group-label"); + d.minWidth = labelParts.width + 8; + d.labels = labelParts.lines; + } else { + d.minWidth = 40; + d.labels = []; + } + } + d.w = Math.max(d.minWidth,d.w); + if (d.style.label && d.labels.length > 0) { + var labelPos = d.style["label-position"] || "nw"; + var h = (d.labels.length-1) * 16; + if (labelPos[0] === "s") { + h += 8; + } + d.h += h; + if (labelPos[0] === "n") { + if (d.nodes.length > 0) { + d.y -= h; + } + } + } + } + + g.attr("transform","translate("+d.x+","+d.y+")") + g.selectAll(".red-ui-flow-group-outline") + .attr("width",d.w) + .attr("height",d.h) + + + var selectGroup = document.getElementById("group_select_"+d.id); + selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); + if (d.hovered) { + selectGroup.classList.add("red-ui-flow-group-hovered") + } else { + selectGroup.classList.remove("red-ui-flow-group-hovered") + } + var selectGroupRect = selectGroup.children[0]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + selectGroupRect = selectGroup.children[1]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + + if (d.highlighted) { + selectGroup.classList.add("red-ui-flow-node-highlighted"); + } else { + selectGroup.classList.remove("red-ui-flow-node-highlighted"); + } + + + g.selectAll(".red-ui-flow-group-body") + .attr("width",d.w) + .attr("height",d.h) + .style("stroke", d.style.stroke || "") + .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") + .style("fill", d.style.fill || "") + .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") + + var label = g.selectAll(".red-ui-flow-group-label"); + label.classed("hide",!!!d.style.label) + if (d.style.label) { + var labelPos = d.style["label-position"] || "nw"; + var labelX = 0; + var labelY = 0; + + if (labelPos[0] === 'n') { + labelY = 0+15; // Allow for font-height + } else { + labelY = d.h - 5 -(d.labels.length -1) * 16; + } + if (labelPos[1] === 'w') { + labelX = 5; + labelAnchor = "start" + } else if (labelPos[1] === 'e') { + labelX = d.w-5; + labelAnchor = "end" + } else { + labelX = d.w/2; + labelAnchor = "middle" + } + if (d.style.hasOwnProperty('color')) { + label.style("fill",d.style.color) + } else { + label.style("fill",null) + } + label.attr("transform","translate("+labelX+","+labelY+")") + .attr("text-anchor",labelAnchor); + if (d.labels) { + var ypos = 0; + g.selectAll(".red-ui-flow-group-label-text").remove(); + d.labels.forEach(function (name) { + label.append("tspan") + .classed("red-ui-flow-group-label-text", true) + .text(name) + .attr("x", 0) + .attr("y", ypos); + ypos += 16; + }); + } else { + g.selectAll(".red-ui-flow-group-label-text").remove(); + } + } + + delete dirtyGroups[d.id]; + delete d.dirty; + } + }) + } else { + // JOINING - unselect any selected links + linkLayer.selectAll(".red-ui-flow-link-selected").data( + activeLinks, + function(d) { + return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; + } + ).classed("red-ui-flow-link-selected", false); + } + RED.view.navigator.refresh(); + if (d3.event) { + d3.event.preventDefault(); + } + } + + function focusView() { + try { + // Workaround for browser unexpectedly scrolling iframe into full + // view - record the parent scroll position and restore it after + // setting the focus + var scrollX = window.parent.window.scrollX; + var scrollY = window.parent.window.scrollY; + chart.trigger("focus"); + window.parent.window.scrollTo(scrollX,scrollY); + } catch(err) { + // In case we're iframed into a page of a different origin, just focus + // the view following the inevitable DOMException + chart.trigger("focus"); + } + } + + + /** + * Imports a new collection of nodes from a JSON String. + * + * - all get new IDs assigned + * - all "selected" + * - attached to mouse for placing - "IMPORT_DRAGGING" + * @param {String/Array} newNodesObj nodes to import + * @param {Object} options options object + * + * Options: + * - addFlow - whether to import nodes to a new tab + * - touchImport - whether this is a touch import. If not, imported nodes are + * attachedto mouse for placing - "IMPORT_DRAGGING" state + * - generateIds - whether to automatically generate new ids for all imported nodes + * - generateDefaultNames - whether to automatically update any nodes with clashing + * default names + */ + function importNodes(newNodesObj,options) { + options = options || { + addFlow: false, + touchImport: false, + generateIds: false, + generateDefaultNames: false + } + var addNewFlow = options.addFlow + var touchImport = options.touchImport; + + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + + var nodesToImport; + if (typeof newNodesObj === "string") { + if (newNodesObj === "") { + return; + } + try { + nodesToImport = JSON.parse(newNodesObj); + } catch(err) { + var e = new Error(RED._("clipboard.invalidFlow",{message:err.message})); + e.code = "NODE_RED"; + throw e; + } + } else { + nodesToImport = newNodesObj; + } + + if (!$.isArray(nodesToImport)) { + nodesToImport = [nodesToImport]; + } + if (options.generateDefaultNames) { + RED.actions.invoke("core:generate-node-names", nodesToImport, { + renameBlank: false, + renameClash: true, + generateHistory: false + }) + } + + try { + var activeSubflowChanged; + if (activeSubflow) { + activeSubflowChanged = activeSubflow.changed; + } + var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap}); + if (result) { + var new_nodes = result.nodes; + var new_links = result.links; + var new_groups = result.groups; + var new_junctions = result.junctions; + var new_workspaces = result.workspaces; + var new_subflows = result.subflows; + var removedNodes = result.removedNodes; + var new_default_workspace = result.missingWorkspace; + if (addNewFlow && new_default_workspace) { + RED.workspaces.show(new_default_workspace.id); + } + var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); + new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) + new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()})) + var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); + + clearSelection(); + movingSet.clear(); + movingSet.add(new_ms); + + + // TODO: pick a more sensible root node + if (movingSet.length() > 0) { + if (mouse_position == null) { + mouse_position = [0,0]; + } + + var dx = mouse_position[0]; + var dy = mouse_position[1]; + if (movingSet.length() > 0) { + var root_node = movingSet.get(0).n; + dx = root_node.x; + dy = root_node.y; + } + + var minX = 0; + var minY = 0; + var i; + var node,group; + var l =movingSet.length(); + for (i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) + + + } + } + + } + + var historyEvent = { + t:"add", + nodes:new_node_ids, + links:new_links, + groups:new_groups, + junctions: new_junctions, + workspaces:new_workspaces, + subflows:new_subflows, + dirty:RED.nodes.dirty() + }; + if (movingSet.length() === 0) { + RED.nodes.dirty(true); + } + if (activeSubflow) { + var subflowRefresh = RED.subflow.refresh(true); + if (subflowRefresh) { + historyEvent.subflow = { + id:activeSubflow.id, + changed: activeSubflowChanged, + instances: subflowRefresh.instances + } + } + } + if (removedNodes) { + var replaceEvent = { + t: "replace", + config: removedNodes + } + historyEvent = { + t:"multi", + events: [ + replaceEvent, + historyEvent + ] + } + } + + RED.history.push(historyEvent); + + updateActiveNodes(); + redraw(); + + var counts = []; + var newNodeCount = 0; + var newConfigNodeCount = 0; + new_nodes.forEach(function(n) { + if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) { + newNodeCount++; + } else { + newConfigNodeCount++; + } + }) + var newGroupCount = new_groups.length; + var newJunctionCount = new_junctions.length; + if (new_workspaces.length > 0) { + counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); + } + if (newNodeCount > 0) { + counts.push(RED._("clipboard.node",{count:newNodeCount})); + } + if (newGroupCount > 0) { + counts.push(RED._("clipboard.group",{count:newGroupCount})); + } + if (newConfigNodeCount > 0) { + counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount})); + } + if (new_subflows.length > 0) { + counts.push(RED._("clipboard.subflow",{count:new_subflows.length})); + } + if (removedNodes && removedNodes.length > 0) { + counts.push(RED._("clipboard.replacedNodes",{count:removedNodes.length})); + } + if (counts.length > 0) { + var countList = "
        • "+counts.join("
        • ")+"
        "; + RED.notify("

        "+RED._("clipboard.nodesImported")+"

        "+countList,{id:"clipboard"}); + } + + } + } catch(error) { + if (error.code === "import_conflict") { + // Pass this up for the called to resolve + throw error; + } else if (error.code != "NODE_RED") { + console.log(error.stack); + RED.notify(RED._("notification.error",{message:error.toString()}),"error"); + } else { + RED.notify(RED._("notification.error",{message:error.message}),"error"); + } + } + } + + function toggleShowGrid(state) { + if (state) { + gridLayer.style("visibility","visible"); + } else { + gridLayer.style("visibility","hidden"); + } + } + function toggleSnapGrid(state) { + snapGrid = state; + redraw(); + } + function toggleStatus(s) { + showStatus = s; + RED.nodes.eachNode(function(n) { n.dirtyStatus = true; n.dirty = true;}); + //TODO: subscribe/unsubscribe here + redraw(); + } + function setSelectedNodeState(isDisabled) { + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + var workspaceSelection = RED.workspaces.selection(); + var changed = false; + if (workspaceSelection.length > 0) { + // TODO: toggle workspace state + } else if (movingSet.length() > 0) { + var historyEvents = []; + for (var i=0;i 0) { + RED.history.push({ + t:"multi", + events: historyEvents, + dirty:RED.nodes.dirty() + }) + RED.nodes.dirty(true) + } + } + RED.view.redraw(); + + } + function getSelection() { + var selection = {}; + + var allNodes = new Set(); + + if (movingSet.length() > 0) { + movingSet.forEach(function(n) { + if (n.n.type !== 'group') { + allNodes.add(n.n); + } + }); + } + var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); + if (selectedGroups.length > 0) { + if (selectedGroups.length === 1 && selectedGroups[0].active) { + // Let nodes be nodes + } else { + selectedGroups.forEach(function(g) { + var groupNodes = RED.group.getNodes(g,true); + groupNodes.forEach(function(n) { + allNodes.delete(n); + }); + allNodes.add(g); + }); + } + } + if (allNodes.size > 0) { + selection.nodes = Array.from(allNodes); + } + if (selectedLinks.length() > 0) { + selection.links = selectedLinks.toArray(); + selection.link = selection.links[0]; + } + return selection; + } + + /** + * Create a node from a type string. + * **NOTE:** Can throw on error - use `try` `catch` block when calling + * @param {string} type The node type to create + * @param {number} [x] (optional) The horizontal position on the workspace + * @param {number} [y] (optional)The vertical on the workspace + * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace. + * @returns An object containing the `node` and a `historyEvent` + * @private + */ + function createNode(type, x, y, z) { + var m = /^subflow:(.+)$/.exec(type); + var activeSubflow = z ? RED.nodes.subflow(z) : null; + if (activeSubflow && m) { + var subflowId = m[1]; + if (subflowId === activeSubflow.id) { + throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) + } + if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { + throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) + } + } + + var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() }; + + nn.type = type; + nn._def = RED.nodes.getType(nn.type); + + if (!m) { + nn.inputs = nn._def.inputs || 0; + nn.outputs = nn._def.outputs; + + for (var d in nn._def.defaults) { + if (nn._def.defaults.hasOwnProperty(d)) { + if (nn._def.defaults[d].value !== undefined) { + nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); + } + } + } + + if (nn._def.onadd) { + try { + nn._def.onadd.call(nn); + } catch (err) { + console.log("Definition error: " + nn.type + ".onadd:", err); + } + } + } else { + var subflow = RED.nodes.subflow(m[1]); + nn.name = ""; + nn.inputs = subflow.in.length; + nn.outputs = subflow.out.length; + } + + nn.changed = true; + nn.moved = true; + + nn.w = RED.view.node_width; + nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15); + nn.resize = true; + if (x != null && typeof x == "number" && x >= 0) { + nn.x = x; + } + if (y != null && typeof y == "number" && y >= 0) { + nn.y = y; + } + var historyEvent = { + t: "add", + nodes: [nn.id], + dirty: RED.nodes.dirty() + } + if (activeSubflow) { + var subflowRefresh = RED.subflow.refresh(true); + if (subflowRefresh) { + historyEvent.subflow = { + id: activeSubflow.id, + changed: activeSubflow.changed, + instances: subflowRefresh.instances + } + } + } + return { + node: nn, + historyEvent: historyEvent + } + } + + function calculateNodeDimensions(node) { + var result = [node_width,node_height]; + try { + var isLink = (node.type === "link in" || node.type === "link out") + var hideLabel = node.hasOwnProperty('l')?!node.l : isLink; + var label = RED.utils.getNodeLabel(node, node.type); + var labelParts = getLabelParts(label, "red-ui-flow-node-label"); + if (hideLabel) { + result[1] = Math.max(node_height,(node.outputs || 0) * 15); + } else { + result[1] = Math.max(6+24*labelParts.lines.length,(node.outputs || 0) * 15, 30); + } + if (hideLabel) { + result[0] = node_height; + } else { + result[0] = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(node._def.inputs>0?7:0))/20)) ); + } + }catch(err) { + console.log("Error",node); + } + return result; + } + + + function flashNode(n) { + let node = n; + if(typeof node === "string") { node = RED.nodes.node(n); } + if(!node) { return; } + + const flashingNode = flashingNodeId && RED.nodes.node(flashingNodeId); + if(flashingNode) { + //cancel current flashing node before flashing new node + clearInterval(flashingNode.__flashTimer); + delete flashingNode.__flashTimer; + flashingNode.dirty = true; + flashingNode.highlighted = false; + } + node.__flashTimer = setInterval(function(flashEndTime, n) { + n.dirty = true; + if (flashEndTime >= Date.now()) { + n.highlighted = !n.highlighted; + } else { + clearInterval(n.__flashTimer); + delete n.__flashTimer; + flashingNodeId = null; + n.highlighted = false; + } + RED.view.redraw(); + }, 100, Date.now() + 2200, node) + flashingNodeId = node.id; + node.highlighted = true; + RED.view.redraw(); + } + return { + init: init, + state:function(state) { + if (state == null) { + return mouse_mode + } else { + mouse_mode = state; + } + }, + + updateActive: updateActiveNodes, + redraw: function(updateActive, syncRedraw) { + if (updateActive) { + updateActiveNodes(); + updateSelection(); + } + if (syncRedraw) { + _redraw(); + } else { + redraw(); + } + }, + focus: focusView, + importNodes: importNodes, + calculateTextWidth: calculateTextWidth, + select: function(selection) { + if (typeof selection !== "undefined") { + clearSelection(); + if (typeof selection == "string") { + var selectedNode = RED.nodes.node(selection); + if (selectedNode) { + selectedNode.selected = true; + selectedNode.dirty = true; + movingSet.clear(); + movingSet.add(selectedNode); + } + } else if (selection) { + if (selection.nodes) { + updateActiveNodes(); + movingSet.clear(); + // TODO: this selection group span groups + // - if all in one group -> activate the group + // - if in multiple groups (or group/no-group) + // -> select the first 'set' of things in the same group/no-group + selection.nodes.forEach(function(n) { + if (n.type !== "group") { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } else { + selectGroup(n,true); + } + }) + } + } + } + updateSelection(); + redraw(true); + }, + selection: getSelection, + clearSelection: clearSelection, + createNode: createNode, + /** default node width */ + get node_width() { + return node_width; + }, + /** default node height */ + get node_height() { + return node_height; + }, + /** snap to grid option state */ + get snapGrid() { + return snapGrid; + }, + /** gets the current scale factor */ + scale: function() { + return scaleFactor; + }, + getLinksAtPoint: function(x,y) { + // x,y must be in SVG co-ordinate space + // if they come from a node.x/y, they will need to be scaled using + // scaleFactor first. + var result = []; + var links = outer.selectAll(".red-ui-flow-link-background")[0]; + for (var i=0;i= bb.x && y >= bb.y && x <= bb.x+bb.width && y <= bb.y+bb.height) { + result.push(links[i]) + } + } + return result; + }, + getGroupAtPoint: getGroupAt, + getActiveGroup: function() { return activeGroup }, + reveal: function(id,triggerHighlight) { + if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { + RED.workspaces.show(id, null, null, true); + } else { + var node = RED.nodes.node(id) || RED.nodes.group(id); + if (node) { + if (node.z && (node.type === "group" || node._def.category !== 'config')) { + node.dirty = true; + RED.workspaces.show(node.z); + + var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor]; + var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; + var cx = node.x; + var cy = node.y; + if (node.type === "group") { + cx += node.w/2; + cy += node.h/2; + } + if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) { + var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor); + var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor); + chart.animate({ + scrollLeft: deltaX, + scrollTop: deltaY + },200); + } + if (triggerHighlight !== false) { + flashNode(node); + } + } else if (node._def.category === 'config') { + RED.sidebar.config.show(id); + } + } + } + }, + gridSize: function(v) { + if (v === undefined) { + return gridSize; + } else { + gridSize = Math.max(5,v); + updateGrid(); + } + }, + getActiveNodes: function() { + return activeNodes; + }, + getSubflowPorts: function() { + var result = []; + if (activeSubflow) { + var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); + subflowOutputs.each(function(d,i) { result.push(d) }) + var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); + subflowInputs.each(function(d,i) { result.push(d) }) + var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); + subflowStatus.each(function(d,i) { result.push(d) }) + } + return result; + }, + selectNodes: function(options) { + $("#red-ui-workspace-tabs-shade").show(); + $("#red-ui-palette-shade").show(); + $("#red-ui-sidebar-shade").show(); + $("#red-ui-header-shade").show(); + $("#red-ui-workspace").addClass("red-ui-workspace-select-mode"); + + mouse_mode = RED.state.SELECTING_NODE; + clearSelection(); + if (options.selected) { + options.selected.forEach(function(id) { + var n = RED.nodes.node(id); + if (n) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + }) + } + redraw(); + selectNodesOptions = options||{}; + var closeNotification = function() { + clearSelection(); + $("#red-ui-workspace-tabs-shade").hide(); + $("#red-ui-palette-shade").hide(); + $("#red-ui-sidebar-shade").hide(); + $("#red-ui-header-shade").hide(); + $("#red-ui-workspace").removeClass("red-ui-workspace-select-mode"); + resetMouseVars(); + notification.close(); + } + selectNodesOptions.done = function(selection) { + closeNotification(); + if (selectNodesOptions.onselect) { + selectNodesOptions.onselect(selection); + } + } + var buttons = [{ + text: RED._("common.label.cancel"), + click: function(e) { + closeNotification(); + if (selectNodesOptions.oncancel) { + selectNodesOptions.oncancel(); + } + } + }]; + if (!selectNodesOptions.single) { + buttons.push({ + text: RED._("common.label.done"), + class: "primary", + click: function(e) { + var selection = movingSet.nodes() + selectNodesOptions.done(selection); + } + }); + } + var notification = RED.notify(selectNodesOptions.prompt || RED._("workspace.selectNodes"),{ + modal: false, + fixed: true, + type: "compact", + buttons: buttons + }) + }, + scroll: function(x,y) { + chart.scrollLeft(chart.scrollLeft()+x); + chart.scrollTop(chart.scrollTop()+y) + }, + clickNodeButton: function(n) { + if (n._def.button) { + nodeButtonClicked(n); + } + }, + clipboard: function() { + return clipboard + }, + redrawStatus: redrawStatus, + showQuickAddDialog:showQuickAddDialog, + calculateNodeDimensions: calculateNodeDimensions, + getElementPosition:getElementPosition, + showTooltip:showTooltip + }; +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index 697a90729..bfe5c283d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -219,7 +219,7 @@ span.red-ui-menu-sublabel { color: $header-menu-sublabel-color; font-size: 13px; - display: inline-block; + display: block; text-indent: 0px; } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index b3c471a5a..2e71cbdca 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -335,7 +335,7 @@ var api = module.exports = { throw (makeError(err, err.code, 500)) } default: - throw (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400)) + throw (makeError(`Cannot change flows runtime state to '${opts.requestedState}'}`, "invalid_run_state", 400)) } }, } From 68c1e49f6289616f770514db27fa2576a23aa777 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Mon, 27 Jun 2022 18:12:45 +0100 Subject: [PATCH 126/195] Delete view_copy.js Remove file that slipped through the net --- .../editor-client/src/js/ui/view_copy.js | 6287 ----------------- 1 file changed, 6287 deletions(-) delete mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js deleted file mode 100644 index 569fee1d5..000000000 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js +++ /dev/null @@ -1,6287 +0,0 @@ -/** - * 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. - **/ - - - /*
        #red-ui-workspace-chart - * \- "outer" - * \- - * \- .red-ui-workspace-chart-event-layer "eventLayer" - * |- .red-ui-workspace-chart-background - * |- .red-ui-workspace-chart-grid "gridLayer" - * |- "groupLayer" - * |- "groupSelectLayer" - * |- "linkLayer" - * |- "junctionLayer" - * |- "dragGroupLayer" - * |- "nodeLayer" - */ - -RED.view = (function() { - var space_width = 5000, - space_height = 5000, - lineCurveScale = 0.75, - scaleFactor = 1, - node_width = 100, - node_height = 30, - dblClickInterval = 650; - - var touchLongPressTimeout = 1000, - startTouchDistance = 0, - startTouchCenter = [], - moveTouchCenter = [], - touchStartTime = 0; - - var workspaceScrollPositions = {}; - var entryCoordinates = {x:-1, y:-1}; - var gridSize = 20; - var snapGrid = false; - - var activeSpliceLink; - var spliceActive = false; - var spliceTimer; - var groupHoverTimer; - - var activeSubflow = null; - var activeNodes = []; - var activeLinks = []; - var activeJunctions = []; - var activeFlowLinks = []; - var activeLinkNodes = {}; - var activeGroup = null; - var activeHoverGroup = null; - var activeGroups = []; - var dirtyGroups = {}; - - var mousedown_link = null; - var mousedown_node = null; - var mousedown_group = null; - var mousedown_port_type = null; - var mousedown_port_index = 0; - var mouseup_node = null; - var mouse_offset = [0,0]; - var mouse_position = null; - var mouse_mode = 0; - var mousedown_group_handle = null; - var lasso = null; - var slicePath = null; - var slicePathLast = null; - var ghostNode = null; - var showStatus = false; - var lastClickNode = null; - var dblClickPrimed = null; - var clickTime = 0; - var clickElapsed = 0; - var scroll_position = []; - var quickAddActive = false; - var quickAddLink = null; - var showAllLinkPorts = -1; - var groupNodeSelectPrimed = false; - var lastClickPosition = []; - var selectNodesOptions; - - let flashingNodeId; - - var clipboard = ""; - - // Note: these are the permitted status colour aliases. The actual RGB values - // are set in the CSS - flow.scss/colors.scss - var status_colours = { - "red": "#c00", - "green": "#5a8", - "yellow": "#F9DF31", - "blue": "#53A3F3", - "grey": "#d3d3d3", - "gray": "#d3d3d3" - } - - var PORT_TYPE_INPUT = 1; - var PORT_TYPE_OUTPUT = 0; - - var chart; - var outer; - var eventLayer; - var gridLayer; - var linkLayer; - var junctionLayer; - var dragGroupLayer; - var groupSelectLayer; - var nodeLayer; - var groupLayer; - var drag_lines; - - var movingSet = (function() { - var setIds = new Set(); - var set = []; - var api = { - add: function(node) { - if (Array.isArray(node)) { - for (var i=0;i1) { - clearTimeout(touchStartTime); - touchStartTime = null; - d3.event.preventDefault(); - touch0 = d3.event.touches.item(0); - var touch1 = d3.event.touches.item(1); - var a = touch0["pageY"]-touch1["pageY"]; - var b = touch0["pageX"]-touch1["pageX"]; - - var offset = chart.offset(); - var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; - startTouchCenter = [ - (touch1["pageX"]+(b/2)-offset.left+scrollPos[0])/scaleFactor, - (touch1["pageY"]+(a/2)-offset.top+scrollPos[1])/scaleFactor - ]; - moveTouchCenter = [ - touch1["pageX"]+(b/2), - touch1["pageY"]+(a/2) - ] - startTouchDistance = Math.sqrt((a*a)+(b*b)); - } else { - var obj = d3.select(document.body); - touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - var point = d3.touches(this)[0]; - touchStartTime = setTimeout(function() { - touchStartTime = null; - showTouchMenu(obj,pos); - //lasso = eventLayer.append("rect") - // .attr("ox",point[0]) - // .attr("oy",point[1]) - // .attr("rx",2) - // .attr("ry",2) - // .attr("x",point[0]) - // .attr("y",point[1]) - // .attr("width",0) - // .attr("height",0) - // .attr("class","nr-ui-view-lasso"); - },touchLongPressTimeout); - } - d3.event.preventDefault(); - }) - .on("touchmove", function(){ - if (RED.touch.radialMenu.active()) { - d3.event.preventDefault(); - return; - } - if (RED.view.DEBUG) { console.warn("eventLayer.touchmove", mouse_mode, mousedown_node); } - var touch0; - if (d3.event.touches.length<2) { - if (touchStartTime) { - touch0 = d3.event.touches.item(0); - var dx = (touch0.pageX-startTouchCenter[0]); - var dy = (touch0.pageY-startTouchCenter[1]); - var d = Math.abs(dx*dx+dy*dy); - if (d > 64) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (!mousedown_node && !mousedown_group) { - mouse_mode = RED.state.PANNING; - mouse_position = [touch0.pageX,touch0.pageY] - scroll_position = [chart.scrollLeft(),chart.scrollTop()]; - } - - } - } else if (lasso) { - d3.event.preventDefault(); - } - canvasMouseMove.call(this); - } else { - touch0 = d3.event.touches.item(0); - var touch1 = d3.event.touches.item(1); - var a = touch0["pageY"]-touch1["pageY"]; - var b = touch0["pageX"]-touch1["pageX"]; - var offset = chart.offset(); - var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; - var moveTouchDistance = Math.sqrt((a*a)+(b*b)); - var touchCenter = [ - touch1["pageX"]+(b/2), - touch1["pageY"]+(a/2) - ]; - - if (!isNaN(moveTouchDistance)) { - oldScaleFactor = scaleFactor; - scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); - - var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% - startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), - startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) - ]; - - startTouchDistance = moveTouchDistance; - moveTouchCenter = touchCenter; - - chart.scrollLeft(scrollPos[0]+deltaTouchCenter[0]); - chart.scrollTop(scrollPos[1]+deltaTouchCenter[1]); - redraw(); - } - } - d3.event.preventDefault(); - }); - - // Workspace Background - eventLayer.append("svg:rect") - .attr("class","red-ui-workspace-chart-background") - .attr("width", space_width) - .attr("height", space_height); - - gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); - updateGrid(); - - groupLayer = eventLayer.append("g"); - groupSelectLayer = eventLayer.append("g"); - linkLayer = eventLayer.append("g"); - dragGroupLayer = eventLayer.append("g"); - junctionLayer = eventLayer.append("g"); - nodeLayer = eventLayer.append("g"); - - drag_lines = []; - - RED.events.on("workspace:change",function(event) { - if (event.old !== 0) { - workspaceScrollPositions[event.old] = { - left:chart.scrollLeft(), - top:chart.scrollTop() - }; - } - var scrollStartLeft = chart.scrollLeft(); - var scrollStartTop = chart.scrollTop(); - - activeSubflow = RED.nodes.subflow(event.workspace); - - RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0); - RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow); - - if (workspaceScrollPositions[event.workspace]) { - chart.scrollLeft(workspaceScrollPositions[event.workspace].left); - chart.scrollTop(workspaceScrollPositions[event.workspace].top); - } else { - chart.scrollLeft(0); - chart.scrollTop(0); - } - var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; - var scrollDeltaTop = chart.scrollTop() - scrollStartTop; - if (mouse_position != null) { - mouse_position[0] += scrollDeltaLeft; - mouse_position[1] += scrollDeltaTop; - } - if (RED.workspaces.selection().length === 0) { - resetMouseVars(); - clearSelection(); - } - RED.nodes.eachNode(function(n) { - n.dirty = true; - n.dirtyStatus = true; - }); - updateSelection(); - updateActiveNodes(); - redraw(); - }); - - RED.statusBar.add({ - id: "view-zoom-controls", - align: "right", - element: $(''+ - ''+ - ''+ - ''+ - '') - }) - - $("#red-ui-view-zoom-out").on("click", zoomOut); - RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out'); - $("#red-ui-view-zoom-zero").on("click", zoomZero); - RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset'); - $("#red-ui-view-zoom-in").on("click", zoomIn); - RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in'); - chart.on("DOMMouseScroll mousewheel", function (evt) { - if ( evt.altKey ) { - evt.preventDefault(); - evt.stopPropagation(); - var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; - if (move <= 0) { zoomOut(); } - else { zoomIn(); } - } - }); - - //add search to status-toolbar - RED.statusBar.add({ - id: "view-search-tools", - align: "left", - hidden: false, - element: $(''+ - '' + - '' + - '' + - '? of ?' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '') - }) - $("#red-ui-view-searchtools-search").on("click", searchFlows); - RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); - $("#red-ui-view-searchtools-prev").on("click", searchPrev); - RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); - $("#red-ui-view-searchtools-next").on("click", searchNext); - RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); - RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); - - // Handle nodes dragged from the palette - chart.droppable({ - accept:".red-ui-palette-node", - drop: function( event, ui ) { - d3.event = event; - var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); - var result = createNode(selected_tool); - if (!result) { - return; - } - var historyEvent = result.historyEvent; - var nn = result.node; - - RED.nodes.add(nn); - - var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); - if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { - nn.l = showLabel; - } - - var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); - var helperWidth = ui.helper.width(); - var helperHeight = ui.helper.height(); - var mousePos = d3.touches(this)[0]||d3.mouse(this); - - try { - var isLink = (nn.type === "link in" || nn.type === "link out") - var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; - - var label = RED.utils.getNodeLabel(nn, nn.type); - var labelParts = getLabelParts(label, "red-ui-flow-node-label"); - if (hideLabel) { - nn.w = node_height; - nn.h = Math.max(node_height,(nn.outputs || 0) * 15); - } else { - nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) ); - nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30); - } - } catch(err) { - } - - mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); - mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); - mousePos[1] /= scaleFactor; - mousePos[0] /= scaleFactor; - - nn.x = mousePos[0]; - nn.y = mousePos[1]; - - if (snapGrid) { - var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); - nn.x -= gridOffset.x; - nn.y -= gridOffset.y; - } - - var spliceLink = $(ui.helper).data("splice"); - if (spliceLink) { - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: nn - }; - var link2 = { - source:nn, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - historyEvent.links = [link1,link2]; - historyEvent.removedLinks = [spliceLink]; - } - - - var group = $(ui.helper).data("group"); - if (group) { - RED.group.addToGroup(group, nn); - historyEvent = { - t: 'multi', - events: [historyEvent], - - } - historyEvent.events.push({ - t: "addToGroup", - group: group, - nodes: nn - }) - } - - RED.history.push(historyEvent); - RED.editor.validateNode(nn); - RED.nodes.dirty(true); - // auto select dropped node - so info shows (if visible) - exitActiveGroup(); - clearSelection(); - nn.selected = true; - movingSet.add(nn); - if (group) { - selectGroup(group,false); - enterActiveGroup(group); - activeGroup = group; - } - updateActiveNodes(); - updateSelection(); - redraw(); - - if (nn._def.autoedit) { - RED.editor.edit(nn); - } - } - }); - chart.on("focus", function() { - $("#red-ui-workspace-tabs").addClass("red-ui-workspace-focussed"); - }); - chart.on("blur", function() { - $("#red-ui-workspace-tabs").removeClass("red-ui-workspace-focussed"); - }); - - RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); - RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); - RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});}); - - RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) - - RED.events.on("view:selection-changed", function(selection) { - var hasSelection = (selection.nodes && selection.nodes.length > 0); - var hasMultipleSelection = hasSelection && selection.nodes.length > 1; - RED.menu.setDisabled("menu-item-edit-cut",!hasSelection); - RED.menu.setDisabled("menu-item-edit-copy",!hasSelection); - RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection); - - RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection); - }) - - RED.actions.add("core:delete-selection",deleteSelection); - RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) }); - RED.actions.add("core:edit-selected-node",editSelection); - RED.actions.add("core:go-to-selection",function() { - if (movingSet.length() > 0) { - var node = movingSet.get(0).n; - if (/^subflow:/.test(node.type)) { - RED.workspaces.show(node.type.substring(8)) - } else if (node.type === 'group') { - enterActiveGroup(node); - redraw(); - } - } - }); - RED.actions.add("core:undo",RED.history.pop); - RED.actions.add("core:redo",RED.history.redo); - RED.actions.add("core:select-all-nodes",selectAll); - RED.actions.add("core:select-none", selectNone); - RED.actions.add("core:zoom-in",zoomIn); - RED.actions.add("core:zoom-out",zoomOut); - RED.actions.add("core:zoom-reset",zoomZero); - RED.actions.add("core:enable-selected-nodes", function() { setSelectedNodeState(false)}); - RED.actions.add("core:disable-selected-nodes", function() { setSelectedNodeState(true)}); - - RED.actions.add("core:toggle-show-grid",function(state) { - if (state === undefined) { - RED.userSettings.toggle("view-show-grid"); - } else { - toggleShowGrid(state); - } - }); - RED.actions.add("core:toggle-snap-grid",function(state) { - if (state === undefined) { - RED.userSettings.toggle("view-snap-grid"); - } else { - toggleSnapGrid(state); - } - }); - RED.actions.add("core:toggle-status",function(state) { - if (state === undefined) { - RED.userSettings.toggle("view-node-status"); - } else { - toggleStatus(state); - } - }); - - RED.view.annotations.init(); - RED.view.navigator.init(); - RED.view.tools.init(); - - - RED.view.annotations.register("red-ui-flow-node-changed",{ - type: "badge", - class: "red-ui-flow-node-changed", - element: function() { - var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); - changeBadge.setAttribute("cx",5); - changeBadge.setAttribute("cy",5); - changeBadge.setAttribute("r",5); - return changeBadge; - }, - show: function(n) { return n.changed||n.moved } - }) - - RED.view.annotations.register("red-ui-flow-node-error",{ - type: "badge", - class: "red-ui-flow-node-error", - element: function(d) { - var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); - errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z"); - return errorBadge - }, - tooltip: function(d) { - if (d.validationErrors && d.validationErrors.length > 0) { - return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ") - } - }, - show: function(n) { return !n.valid } - }) - - if (RED.settings.get("editor.view.view-store-zoom")) { - var userZoomLevel = parseFloat(RED.settings.getLocal('zoom-level')) - if (!isNaN(userZoomLevel)) { - scaleFactor = userZoomLevel - } - } - - var onScrollTimer = null; - function storeScrollPosition() { - workspaceScrollPositions[RED.workspaces.active()] = { - left:chart.scrollLeft(), - top:chart.scrollTop() - }; - RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) ) - } - chart.on("scroll", function() { - if (RED.settings.get("editor.view.view-store-position")) { - if (onScrollTimer) { - clearTimeout(onScrollTimer) - } - onScrollTimer = setTimeout(storeScrollPosition, 200); - } - }) - - if (RED.settings.get("editor.view.view-store-position")) { - var scrollPositions = RED.settings.getLocal('scroll-positions') - if (scrollPositions) { - try { - workspaceScrollPositions = JSON.parse(scrollPositions) - } catch(err) { - } - } - } - } - - - - function updateGrid() { - var gridTicks = []; - for (var i=0;i 0) { - if (delta < node_width) { - scale = 0.75-0.75*((node_width-delta)/node_width); - // scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); - // if (Math.abs(dy) < 3*node_height) { - // scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; - // } - } - } else { - scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width)); - } - if (dx*sc > 0) { - return "M "+origX+" "+origY+ - " C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+ - (destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+ - destX+" "+destY - } else { - - var midX = Math.floor(destX-dx/2); - var midY = Math.floor(destY-dy/2); - // - if (dy === 0) { - midY = destY + node_height; - } - var cp_height = node_height/2; - var y1 = (destY + midY)/2 - var topX =origX + sc*node_width*scale; - var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height); - var bottomX = destX - sc*node_width*scale; - var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height); - var x1 = (origX+topX)/2; - var scy = dy>0?1:-1; - var cp = [ - // Orig -> Top - [x1,origY], - [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)], - // Top -> Mid - // [Mirror previous cp] - [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)], - // Mid -> Bottom - // [Mirror previous cp] - [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)], - // Bottom -> Dest - // [Mirror previous cp] - [(destX+bottomX)/2,destY] - ]; - if (cp[2][1] === topY+scy*cp_height) { - if (Math.abs(dy) < cp_height*10) { - cp[1][1] = topY-scy*cp_height/2; - cp[3][1] = bottomY-scy*cp_height/2; - } - cp[2][0] = topX; - } - return "M "+origX+" "+origY+ - " C "+ - cp[0][0]+" "+cp[0][1]+" "+ - cp[1][0]+" "+cp[1][1]+" "+ - topX+" "+topY+ - " S "+ - cp[2][0]+" "+cp[2][1]+" "+ - midX+" "+midY+ - " S "+ - cp[3][0]+" "+cp[3][1]+" "+ - bottomX+" "+bottomY+ - " S "+ - cp[4][0]+" "+cp[4][1]+" "+ - destX+" "+destY - } - } - - function canvasMouseDown() { - if (RED.view.DEBUG) { - console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); - } - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - - if (d3.event.button === 1) { - // Middle Click pan - mouse_mode = RED.state.PANNING; - mouse_position = [d3.event.pageX,d3.event.pageY] - scroll_position = [chart.scrollLeft(),chart.scrollTop()]; - return; - } - if (!mousedown_node && !mousedown_link && !mousedown_group) { - selectedLinks.clear(); - updateSelection(); - } - if (mouse_mode === 0 && lasso) { - lasso.remove(); - lasso = null; - } - if (d3.event.touches || d3.event.button === 0) { - if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.metaKey || d3.event.ctrlKey) && !(d3.event.altKey || d3.event.shiftKey)) { - // Trigger quick add dialog - d3.event.stopPropagation(); - clearSelection(); - const point = d3.mouse(this); - var clickedGroup = getGroupAt(point[0], point[1]); - if (drag_lines.length > 0) { - clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) - } - showQuickAddDialog({ position: point, group: clickedGroup }); - } else if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { - // CTRL not being held - if (!d3.event.altKey) { - // ALT not held (shift is allowed) Trigger lasso - if (!touchStartTime) { - const point = d3.mouse(this); - lasso = eventLayer.append("rect") - .attr("ox", point[0]) - .attr("oy", point[1]) - .attr("rx", 1) - .attr("ry", 1) - .attr("x", point[0]) - .attr("y", point[1]) - .attr("width", 0) - .attr("height", 0) - .attr("class", "nr-ui-view-lasso"); - d3.event.preventDefault(); - } - } else if (d3.event.altKey) { - //Alt [+shift] held - Begin slicing - clearSelection(); - mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING; - const point = d3.mouse(this); - slicePath = eventLayer.append("path").attr("class", "nr-ui-view-slice").attr("d", `M${point[0]} ${point[1]}`) - slicePathLast = point; - RED.view.redraw(); - } - } - } - } - - function showQuickAddDialog(options) { - options = options || {}; - var point = options.position || lastClickPosition; - var spliceLink = options.splice; - var targetGroup = options.group; - var touchTrigger = options.touchTrigger; - - if (targetGroup && !targetGroup.active) { - selectGroup(targetGroup,false); - enterActiveGroup(targetGroup); - RED.view.redraw(); - } - - var ox = point[0]; - var oy = point[1]; - - if (RED.settings.get("editor").view['view-snap-grid']) { - // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') - point[0] = Math.round(point[0] / gridSize) * gridSize; - point[1] = Math.round(point[1] / gridSize) * gridSize; - // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','blue') - } - - var mainPos = $("#red-ui-main-container").position(); - - if (mouse_mode !== RED.state.QUICK_JOINING) { - mouse_mode = RED.state.QUICK_JOINING; - $(window).on('keyup',disableQuickJoinEventHandler); - } - quickAddActive = true; - - if (ghostNode) { - ghostNode.remove(); - } - ghostNode = eventLayer.append("g").attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); - ghostNode.append("rect") - .attr("class","red-ui-flow-node-placeholder") - .attr("rx", 5) - .attr("ry", 5) - .attr("width",node_width) - .attr("height",node_height) - .attr("fill","none") - // var ghostLink = ghostNode.append("svg:path") - // .attr("class","red-ui-flow-link-link") - // .attr("d","M 0 "+(node_height/2)+" H "+(gridSize * -2)) - // .attr("opacity",0); - - var filter; - if (drag_lines.length > 0) { - if (drag_lines[0].virtualLink) { - filter = {type:drag_lines[0].node.type === 'link in'?'link out':'link in'} - } else if (drag_lines[0].portType === PORT_TYPE_OUTPUT) { - filter = {input:true} - } else { - filter = {output:true} - } - - quickAddLink = { - node: drag_lines[0].node, - port: drag_lines[0].port, - portType: drag_lines[0].portType, - } - if (drag_lines[0].virtualLink) { - quickAddLink.virtualLink = true; - } - hideDragLines(); - } - if (spliceLink) { - filter = {input:true, output:true} - } - - var rebuildQuickAddLink = function() { - if (!quickAddLink) { - return; - } - if (!quickAddLink.el) { - quickAddLink.el = dragGroupLayer.append("svg:path").attr("class", "red-ui-flow-drag-line"); - } - var numOutputs = (quickAddLink.portType === PORT_TYPE_OUTPUT)?(quickAddLink.node.outputs || 1):1; - var sourcePort = quickAddLink.port; - var portY = -((numOutputs-1)/2)*13 +13*sourcePort; - var sc = (quickAddLink.portType === PORT_TYPE_OUTPUT)?1:-1; - quickAddLink.el.attr("d",generateLinkPath(quickAddLink.node.x+sc*quickAddLink.node.w/2,quickAddLink.node.y+portY,point[0]-sc*node_width/2,point[1],sc)); - } - if (quickAddLink) { - rebuildQuickAddLink(); - } - - - var lastAddedX; - var lastAddedWidth; - - RED.typeSearch.show({ - x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), - y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), - disableFocus: touchTrigger, - filter: filter, - move: function(dx,dy) { - if (ghostNode) { - var pos = d3.transform(ghostNode.attr("transform")).translate; - ghostNode.attr("transform","translate("+(pos[0]+dx)+","+(pos[1]+dy)+")") - point[0] += dx; - point[1] += dy; - rebuildQuickAddLink(); - } - }, - cancel: function() { - if (quickAddLink) { - if (quickAddLink.el) { - quickAddLink.el.remove(); - } - quickAddLink = null; - } - quickAddActive = false; - if (ghostNode) { - ghostNode.remove(); - } - resetMouseVars(); - updateSelection(); - hideDragLines(); - redraw(); - }, - add: function(type,keepAdding) { - if (touchTrigger) { - keepAdding = false; - resetMouseVars(); - } - - var nn; - var historyEvent; - if (type === 'junction') { - nn = { - _def: {defaults:{}}, - type: 'junction', - z: RED.workspaces.active(), - id: RED.nodes.id(), - x: 0, - y: 0, - w: 0, h: 0, - outputs: 1, - inputs: 1, - dirty: true - } - historyEvent = { - t:'add', - junctions:[nn] - } - } else { - var result = createNode(type); - if (!result) { - return; - } - nn = result.node; - historyEvent = result.historyEvent; - } - if (keepAdding) { - mouse_mode = RED.state.QUICK_JOINING; - } - - nn.x = point[0]; - nn.y = point[1]; - var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); - if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { - nn.l = showLabel; - } - if (quickAddLink) { - var drag_line = quickAddLink; - var src = null,dst,src_port; - if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { - src = drag_line.node; - src_port = drag_line.port; - dst = nn; - } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { - src = nn; - dst = drag_line.node; - src_port = 0; - } - - if (src !== null) { - // Joining link nodes via virual wires. Need to update - // the src and dst links property - if (drag_line.virtualLink) { - historyEvent = { - t:'multi', - events: [historyEvent] - } - var oldSrcLinks = $.extend(true,{},{v:src.links}).v - var oldDstLinks = $.extend(true,{},{v:dst.links}).v - src.links.push(dst.id); - dst.links.push(src.id); - src.dirty = true; - dst.dirty = true; - - historyEvent.events.push({ - t:'edit', - node: src, - dirty: RED.nodes.dirty(), - changed: src.changed, - changes: { - links:oldSrcLinks - } - }); - historyEvent.events.push({ - t:'edit', - node: dst, - dirty: RED.nodes.dirty(), - changed: dst.changed, - changes: { - links:oldDstLinks - } - }); - src.changed = true; - dst.changed = true; - } else { - var link = {source: src, sourcePort:src_port, target: dst}; - RED.nodes.addLink(link); - historyEvent.links = [link]; - } - if (!keepAdding) { - quickAddLink.el.remove(); - quickAddLink = null; - if (mouse_mode === RED.state.QUICK_JOINING) { - if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); - } else { - resetMouseVars(); - } - } - } else { - quickAddLink.node = nn; - quickAddLink.port = 0; - } - } else { - hideDragLines(); - resetMouseVars(); - } - } else { - if (!keepAdding) { - if (mouse_mode === RED.state.QUICK_JOINING) { - if (nn.outputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (nn.inputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); - } else { - resetMouseVars(); - } - } - } else { - if (nn.outputs > 0) { - quickAddLink = { - node: nn, - port: 0, - portType: PORT_TYPE_OUTPUT - } - } else if (nn.inputs > 0) { - quickAddLink = { - node: nn, - port: 0, - portType: PORT_TYPE_INPUT - } - } else { - resetMouseVars(); - } - } - } - if (nn.type === 'junction') { - RED.nodes.addJunction(nn); - } else { - RED.nodes.add(nn); - } - RED.editor.validateNode(nn); - - if (targetGroup) { - RED.group.addToGroup(targetGroup, nn); - if (historyEvent.t !== "multi") { - historyEvent = { - t:'multi', - events: [historyEvent] - } - } - historyEvent.events.push({ - t: "addToGroup", - group: targetGroup, - nodes: nn - }) - - } - - if (spliceLink) { - resetMouseVars(); - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: nn - }; - var link2 = { - source:nn, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - historyEvent.links = (historyEvent.links || []).concat([link1,link2]); - historyEvent.removedLinks = [spliceLink]; - } - RED.history.push(historyEvent); - RED.nodes.dirty(true); - // auto select dropped node - so info shows (if visible) - clearSelection(); - nn.selected = true; - if (targetGroup) { - selectGroup(targetGroup,false); - enterActiveGroup(targetGroup); - } - movingSet.add(nn); - updateActiveNodes(); - updateSelection(); - redraw(); - // At this point the newly added node will have a real width, - // so check if the position needs nudging - if (lastAddedX !== undefined) { - var lastNodeRHEdge = lastAddedX + lastAddedWidth/2; - var thisNodeLHEdge = nn.x - nn.w/2; - var gap = thisNodeLHEdge - lastNodeRHEdge; - if (gap != gridSize *2) { - nn.x = nn.x + gridSize * 2 - gap; - nn.dirty = true; - nn.x = Math.ceil(nn.x / gridSize) * gridSize; - redraw(); - } - } - if (keepAdding) { - if (lastAddedX === undefined) { - // ghostLink.attr("opacity",1); - setTimeout(function() { - RED.typeSearch.refresh({filter:{input:true}}); - },100); - } - - lastAddedX = nn.x; - lastAddedWidth = nn.w; - - point[0] = nn.x + nn.w/2 + node_width/2 + gridSize * 2; - ghostNode.attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); - rebuildQuickAddLink(); - } else { - quickAddActive = false; - ghostNode.remove(); - } - } - }); - - updateActiveNodes(); - updateSelection(); - redraw(); - } - - function canvasMouseMove() { - var i; - var node; - // Prevent touch scrolling... - //if (d3.touches(this)[0]) { - // d3.event.preventDefault(); - //} - - // TODO: auto scroll the container - //var point = d3.mouse(this); - //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } - //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); - - if (mouse_mode === RED.state.PANNING) { - var pos = [d3.event.pageX,d3.event.pageY]; - if (d3.event.touches) { - var touch0 = d3.event.touches.item(0); - pos = [touch0.pageX, touch0.pageY]; - } - var deltaPos = [ - mouse_position[0]-pos[0], - mouse_position[1]-pos[1] - ]; - - chart.scrollLeft(scroll_position[0]+deltaPos[0]) - chart.scrollTop(scroll_position[1]+deltaPos[1]) - return - } - if (entryCoordinates.x != -1) { - mouse_position = [entryCoordinates.x, entryCoordinates.y] - } else { - mouse_position = d3.touches(this)[0]||d3.mouse(this); - } -if(RED.view.DEBUG) { console.log(`mousemove ${JSON.stringify(mouse_position)}`)} - if (lasso) { - var ox = parseInt(lasso.attr("ox")); - var oy = parseInt(lasso.attr("oy")); - var x = parseInt(lasso.attr("x")); - var y = parseInt(lasso.attr("y")); - var w; - var h; - if (mouse_position[0] < ox) { - x = mouse_position[0]; - w = ox-x; - } else { - w = mouse_position[0]-x; - } - if (mouse_position[1] < oy) { - y = mouse_position[1]; - h = oy-y; - } else { - h = mouse_position[1]-y; - } - lasso - .attr("x",x) - .attr("y",y) - .attr("width",w) - .attr("height",h) - ; - return; - } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) { - if (slicePath) { - var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1])) - if (delta > 20) { - var currentPath = slicePath.attr("d") - currentPath += " L"+mouse_position[0]+" "+mouse_position[1] - slicePath.attr("d",currentPath); - slicePathLast = mouse_position - } - } - return - } - - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - - if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && mouse_mode != RED.state.DETACHED_DRAGGING && !mousedown_node && !mousedown_group && selectedLinks.length() === 0) { - return; - } - - var mousePos; - // if (mouse_mode === RED.state.GROUP_RESIZE) { - // mousePos = mouse_position; - // var nx = mousePos[0] + mousedown_group.dx; - // var ny = mousePos[1] + mousedown_group.dy; - // switch(mousedown_group.activeHandle) { - // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; - // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; - // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; - // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; - // } - // mousedown_group.dirty = true; - // } - if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - // update drag line - if (drag_lines.length === 0 && mousedown_port_type !== null) { - if (d3.event.shiftKey) { - // Get all the wires we need to detach. - var links = []; - var existingLinks = []; - if (selectedLinks.length() > 0) { - selectedLinks.forEach(function(link) { - if (((mousedown_port_type === PORT_TYPE_OUTPUT && - link.source === mousedown_node && - link.sourcePort === mousedown_port_index - ) || - (mousedown_port_type === PORT_TYPE_INPUT && - link.target === mousedown_node - ))) { - existingLinks.push(link); - } - }) - } else { - var filter; - if (mousedown_port_type === PORT_TYPE_OUTPUT) { - filter = { - source:mousedown_node, - sourcePort: mousedown_port_index - } - } else { - filter = { - target: mousedown_node - } - } - existingLinks = RED.nodes.filterLinks(filter); - } - for (i=0;i 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { - mouse_mode = RED.state.MOVING_ACTIVE; - clickElapsed = 0; - spliceActive = false; - if (movingSet.length() === 1) { - node = movingSet.get(0); - spliceActive = node.n.hasOwnProperty("_def") && - ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && - ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && - RED.nodes.filterLinks({ source: node.n }).length === 0 && - RED.nodes.filterLinks({ target: node.n }).length === 0; - } - } - } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { - mousePos = mouse_position; - var minX = 0; - var minY = 0; - var maxX = space_width; - var maxY = space_height; - if(RED.view.DEBUG) { console.log(`mousemove - MOVING_ACTIVE ${JSON.stringify(mousePos)}`)} - for (var n = 0; n 0) { - var i = 0; - - // Prefer to snap nodes to the grid if there is one in the selection - do { - node = movingSet.get(i++); - } while(i 0) { - historyEvent = { - t:"delete", - links: removedLinks, - dirty:RED.nodes.dirty() - }; - RED.history.push(historyEvent); - RED.nodes.dirty(true); - } - hideDragLines(); - } - if (lasso) { - var x = parseInt(lasso.attr("x")); - var y = parseInt(lasso.attr("y")); - var x2 = x+parseInt(lasso.attr("width")); - var y2 = y+parseInt(lasso.attr("height")); - var ag = activeGroup; - if (!d3.event.shiftKey) { - clearSelection(); - if (ag) { - if (x < ag.x+ag.w && x2 > ag.x && y < ag.y+ag.h && y2 > ag.y) { - // There was an active group and the lasso intersects with it, - // so reenter the group - enterActiveGroup(ag); - activeGroup.selected = true; - } - } - } - activeGroups.forEach(function(g) { - if (!g.selected) { - if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { - if (!activeGroup || RED.group.contains(activeGroup,g)) { - while (g.g && (!activeGroup || g.g !== activeGroup.id)) { - g = RED.nodes.group(g.g); - } - if (!g.selected) { - selectGroup(g,true); - } - } - } - } - }) - - activeNodes.forEach(function(n) { - if (!n.selected) { - if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { - if (!activeGroup || RED.group.contains(activeGroup,n)) { - if (n.g && (!activeGroup || n.g !== activeGroup.id)) { - var group = RED.nodes.group(n.g); - while (group.g && (!activeGroup || group.g !== activeGroup.id)) { - group = RED.nodes.group(group.g); - } - if (!group.selected) { - selectGroup(group,true); - } - } else { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } - } - } - } - }); - activeJunctions.forEach(function(n) { - if (!n.selected) { - if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } - } - }) - - - - // var selectionChanged = false; - // do { - // selectionChanged = false; - // selectedGroups.forEach(function(g) { - // if (g.g && g.selected && RED.nodes.group(g.g).selected) { - // g.selected = false; - // selectionChanged = true; - // } - // }) - // } while(selectionChanged); - - if (activeSubflow) { - activeSubflow.in.forEach(function(n) { - n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - movingSet.add(n); - } - }); - activeSubflow.out.forEach(function(n) { - n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - movingSet.add(n); - } - }); - if (activeSubflow.status) { - activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); - if (activeSubflow.status.selected) { - activeSubflow.status.dirty = true; - movingSet.add(activeSubflow.status); - } - } - } - updateSelection(); - lasso.remove(); - lasso = null; - } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { - clearSelection(); - updateSelection(); - } else if (mouse_mode == RED.state.SLICING) { - deleteSelection(); - slicePath.remove(); - slicePath = null; - RED.view.redraw(true); - } else if (mouse_mode == RED.state.SLICING_JUNCTION) { - var removedLinks = new Set() - var addedLinks = [] - var addedJunctions = [] - - var groupedLinks = {} - selectedLinks.forEach(function(l) { - var sourceId = l.source.id+":"+l.sourcePort - groupedLinks[sourceId] = groupedLinks[sourceId] || [] - groupedLinks[sourceId].push(l) - - groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] - groupedLinks[l.target.id].push(l) - }); - var linkGroups = Object.keys(groupedLinks) - linkGroups.sort(function(A,B) { - return groupedLinks[B].length - groupedLinks[A].length - }) - linkGroups.forEach(function(gid) { - var links = groupedLinks[gid] - var junction = { - _def: {defaults:{}}, - type: 'junction', - z: RED.workspaces.active(), - id: RED.nodes.id(), - x: 0, - y: 0, - w: 0, h: 0, - outputs: 1, - inputs: 1, - dirty: true - } - links = links.filter(function(l) { return !removedLinks.has(l) }) - if (links.length === 0) { - return - } - links.forEach(function(l) { - junction.x += l._sliceLocation.x - junction.y += l._sliceLocation.y - }) - junction.x = Math.round(junction.x/links.length) - junction.y = Math.round(junction.y/links.length) - if (snapGrid) { - junction.x = (gridSize*Math.round(junction.x/gridSize)); - junction.y = (gridSize*Math.round(junction.y/gridSize)); - } - - var nodeGroups = new Set() - - RED.nodes.addJunction(junction) - addedJunctions.push(junction) - let newLink - if (gid === links[0].source.id+":"+links[0].sourcePort) { - newLink = { - source: links[0].source, - sourcePort: links[0].sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: links[0].target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - links.forEach(function(l) { - removedLinks.add(l) - RED.nodes.removeLink(l) - let newLink - if (gid === l.target.id) { - newLink = { - source: l.source, - sourcePort: l.sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: l.target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - nodeGroups.add(l.source.g || "__NONE__") - nodeGroups.add(l.target.g || "__NONE__") - }) - if (nodeGroups.size === 1) { - var group = nodeGroups.values().next().value - if (group !== "__NONE__") { - RED.group.addToGroup(RED.nodes.group(group), junction) - } - } - }) - slicePath.remove(); - slicePath = null; - - if (addedJunctions.length > 0) { - RED.history.push({ - t: 'add', - links: addedLinks, - junctions: addedJunctions, - removedLinks: Array.from(removedLinks) - }) - RED.nodes.dirty(true) - } - RED.view.redraw(true); - } - if (mouse_mode == RED.state.MOVING_ACTIVE) { - if (movingSet.length() > 0) { - var addedToGroup = null; - if (activeHoverGroup) { - for (var j=0;j 0 && mouse_mode == RED.state.MOVING_ACTIVE) { - historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; - if (activeSpliceLink) { - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp - var spliceLink = d3.select(activeSpliceLink).data()[0]; - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: movingSet.get(0).n - }; - var link2 = { - source:movingSet.get(0).n, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - historyEvent.links = [link1,link2]; - historyEvent.removedLinks = [spliceLink]; - updateActiveNodes(); - } - if (addedToGroup) { - historyEvent.addToGroup = addedToGroup; - } - RED.nodes.dirty(true); - RED.history.push(historyEvent); - } - } - } - // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { - // if (mousedown_node.gSelected) { - // delete mousedown_node.gSelected - // } else { - // if (!d3.event.ctrlKey && !d3.event.metaKey) { - // clearSelection(); - // } - // RED.nodes.group(mousedown_node.g).selected = true; - // mousedown_node.selected = true; - // mousedown_node.dirty = true; - // movingSet.add(mousedown_node); - // } - // } - if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) { - // if (mousedown_node) { - // delete mousedown_node.gSelected; - // } - if (mouse_mode === RED.state.DETACHED_DRAGGING) { - var ns = []; - for (var j=0;j 0.3) { - zoomView(scaleFactor-0.1); - } - } - function zoomZero() { zoomView(1); } - function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); } - function searchPrev() { RED.actions.invoke("core:search-previous"); } - function searchNext() { RED.actions.invoke("core:search-next"); } - - - function zoomView(factor) { - var screenSize = [chart.width(),chart.height()]; - var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; - var center = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; - scaleFactor = factor; - var newCenter = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; - var delta = [(newCenter[0]-center[0])*scaleFactor,(newCenter[1]-center[1])*scaleFactor] - chart.scrollLeft(scrollPos[0]-delta[0]); - chart.scrollTop(scrollPos[1]-delta[1]); - - RED.view.navigator.resize(); - redraw(); - if (RED.settings.get("editor.view.view-store-zoom")) { - RED.settings.setLocal('zoom-level', factor.toFixed(1)) - } - } - - function selectNone() { - if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) { - return; - } - if (mouse_mode === RED.state.DETACHED_DRAGGING) { - for (var j=0;j 0) { - activeFlowLinks.push({ - refresh: Math.floor(Math.random()*10000), - node: linkNode, - links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};}) - }); - } - } - } - if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) { - selectedLinks.forEach(function(link) { - if (link.link) { - activeLinks.push(link); - activeLinkNodes[link.source.id] = link.source; - link.source.dirty = true; - activeLinkNodes[link.target.id] = link.target; - link.target.dirty = true; - } - }) - } - } else { - selection.flows = workspaceSelection; - } - } - var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { - if (key === 'nodes' || key === 'flows') { - return value.map(function(n) { return n.id }) - } else if (key === 'link') { - return value.source.id+":"+value.sourcePort+":"+value.target.id; - } else if (key === 'links') { - return value.map(function(link) { - return link.source.id+":"+link.sourcePort+":"+link.target.id; - }); - } - return value; - }); - if (selectionJSON !== lastSelection) { - lastSelection = selectionJSON; - RED.events.emit("view:selection-changed",selection); - } - } - - function editSelection() { - if (movingSet.length() > 0) { - var node = movingSet.get(0).n; - if (node.type === "subflow") { - RED.editor.editSubflow(activeSubflow); - } else if (node.type === "group") { - RED.editor.editGroup(node); - } else { - RED.editor.edit(node); - } - } - } - function deleteSelection(reconnectWires) { - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - var workspaceSelection = RED.workspaces.selection(); - if (workspaceSelection.length > 0) { - var workspaceCount = 0; - workspaceSelection.forEach(function(ws) { if (ws.type === 'tab') { workspaceCount++ } }); - if (workspaceCount === RED.workspaces.count()) { - // Cannot delete all workspaces - return; - } - var historyEvent = { - t: 'delete', - dirty: RED.nodes.dirty(), - nodes: [], - links: [], - groups: [], - junctions: [], - workspaces: [], - subflows: [] - } - var workspaceOrder = RED.nodes.getWorkspaceOrder().slice(0); - - for (var i=0;i 0 || selectedLinks.length() > 0) { - var result; - var node; - var removedNodes = []; - var removedLinks = []; - var removedGroups = []; - var removedJunctions = []; - var removedSubflowOutputs = []; - var removedSubflowInputs = []; - var removedSubflowStatus; - var subflowInstances = []; - var historyEvents = []; - var addToRemovedLinks = function(links) { - if(!links) { return; } - var _links = Array.isArray(links) ? links : [links]; - _links.forEach(function(l) { - removedLinks.push(l); - selectedLinks.remove(l); - }) - } - if (reconnectWires) { - var reconnectResult = RED.nodes.detachNodes(movingSet.nodes()) - var addedLinks = reconnectResult.newLinks; - if (addedLinks.length > 0) { - historyEvents.push({ t:'add', links: addedLinks }) - } - addToRemovedLinks(reconnectResult.removedLinks) - } - - var startDirty = RED.nodes.dirty(); - var startChanged = false; - var selectedGroups = []; - if (movingSet.length() > 0) { - - for (var i=0;i=0; i--) { - var g = selectedGroups[i]; - removedGroups.push(g); - RED.nodes.removeGroup(g); - } - if (removedSubflowOutputs.length > 0) { - result = RED.subflow.removeOutput(removedSubflowOutputs); - if (result) { - addToRemovedLinks(result.links); - } - } - // Assume 0/1 inputs - if (removedSubflowInputs.length == 1) { - result = RED.subflow.removeInput(); - if (result) { - addToRemovedLinks(result.links); - } - } - if (removedSubflowStatus) { - result = RED.subflow.removeStatus(); - if (result) { - addToRemovedLinks(result.links); - } - } - - var instances = RED.subflow.refresh(true); - if (instances) { - subflowInstances = instances.instances; - } - movingSet.clear(); - if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) { - RED.nodes.dirty(true); - } - } - - if (selectedLinks.length() > 0) { - selectedLinks.forEach(function(link) { - if (link.link) { - var sourceId = link.source.id; - var targetId = link.target.id; - var sourceIdIndex = link.target.links.indexOf(sourceId); - var targetIdIndex = link.source.links.indexOf(targetId); - historyEvents.push({ - t: "edit", - node: link.source, - changed: link.source.changed, - changes: { - links: $.extend(true,{},{v:link.source.links}).v - } - }) - historyEvents.push({ - t: "edit", - node: link.target, - changed: link.target.changed, - changes: { - links: $.extend(true,{},{v:link.target.links}).v - } - }) - link.source.changed = true; - link.target.changed = true; - link.target.links.splice(sourceIdIndex,1); - link.source.links.splice(targetIdIndex,1); - link.source.dirty = true; - link.target.dirty = true; - - } else { - RED.nodes.removeLink(link); - removedLinks.push(link); - } - }) - } - RED.nodes.dirty(true); - var historyEvent = { - t:"delete", - nodes:removedNodes, - links:removedLinks, - groups: removedGroups, - junctions: removedJunctions, - subflowOutputs:removedSubflowOutputs, - subflowInputs:removedSubflowInputs, - subflow: { - id: activeSubflow?activeSubflow.id:undefined, - instances: subflowInstances - }, - dirty:startDirty - }; - if (removedSubflowStatus) { - historyEvent.subflow.status = removedSubflowStatus; - } - if (historyEvents.length > 0) { - historyEvents.unshift(historyEvent); - RED.history.push({ - t:"multi", - events: historyEvents - }) - } else { - RED.history.push(historyEvent); - } - - selectedLinks.clear(); - updateActiveNodes(); - updateSelection(); - redraw(); - } - } - - function copySelection() { - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - var nodes = []; - var selection = RED.workspaces.selection(); - if (selection.length > 0) { - nodes = []; - selection.forEach(function(n) { - if (n.type === 'tab') { - nodes.push(n); - nodes = nodes.concat(RED.nodes.groups(n.id)); - nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); - } - }); - } else { - selection = RED.view.selection(); - if (selection.nodes) { - selection.nodes.forEach(function(n) { - nodes.push(n); - if (n.type === 'group') { - nodes = nodes.concat(RED.group.getNodes(n,true)); - } - }) - } - } - - if (nodes.length > 0) { - var nns = []; - var nodeCount = 0; - var groupCount = 0; - var junctionCount = 0; - var handled = {}; - for (var n=0;n 0) { - RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); - } else if (groupCount > 0) { - RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"}); - } - } - } - - - function detachSelectedNodes() { - var selection = RED.view.selection(); - if (selection.nodes) { - const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes); - if (removedLinks.length || newLinks.length) { - RED.history.push({ - t: "multi", - events: [ - { t:'delete', links: removedLinks }, - { t:'add', links: newLinks } - ], - dirty: RED.nodes.dirty() - }) - RED.nodes.dirty(true) - } - prepareDrag([selection.nodes[0].x,selection.nodes[0].y]); - mouse_mode = RED.state.DETACHED_DRAGGING; - RED.view.redraw(true); - } - } - - function calculateTextWidth(str, className) { - var result = convertLineBreakCharacter(str); - var width = 0; - for (var i=0;i 1) { - var i=0; - for (i=0;i 0) { - if (drag_lines[0].node === d) { - // Cannot quick-join to self - return - } - if (drag_lines[0].virtualLink && - ( - (drag_lines[0].node.type === 'link in' && d.type !== 'link out') || - (drag_lines[0].node.type === 'link out' && d.type !== 'link in') - ) - ) { - return - } - } - document.body.style.cursor = ""; - if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) { - if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) { - var found = false; - RED.nodes.eachNode(function(n) { - if (n.z == RED.workspaces.active()) { - var hw = n.w/2; - var hh = n.h/2; - if (n.x-hw mouse_position[0] && - n.y-hhmouse_position[1]) { - found = true; - mouseup_node = n; - portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT; - portIndex = 0; - } - } - }); - if (!found && activeSubflow) { - var subflowPorts = []; - if (activeSubflow.status) { - subflowPorts.push(activeSubflow.status) - } - if (activeSubflow.in) { - subflowPorts = subflowPorts.concat(activeSubflow.in) - } - if (activeSubflow.out) { - subflowPorts = subflowPorts.concat(activeSubflow.out) - } - for (var i=0;i mouse_position[0] && - n.y-hhmouse_position[1]) { - found = true; - mouseup_node = n; - portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT; - portIndex = 0; - break; - } - } - } - } else { - mouseup_node = d; - } - var addedLinks = []; - var removedLinks = []; - var modifiedNodes = []; // joining link nodes - - var select_link = null; - - for (i=0;i 0 || removedLinks.length > 0 || modifiedNodes.length > 0) { - // console.log(addedLinks); - // console.log(removedLinks); - // console.log(modifiedNodes); - var historyEvent; - if (modifiedNodes.length > 0) { - historyEvent = { - t:"multi", - events: linkEditEvents, - dirty:RED.nodes.dirty() - }; - } else { - historyEvent = { - t:"add", - links:addedLinks, - removedLinks: removedLinks, - dirty:RED.nodes.dirty() - }; - } - if (activeSubflow) { - var subflowRefresh = RED.subflow.refresh(true); - if (subflowRefresh) { - historyEvent.subflow = { - id:activeSubflow.id, - changed: activeSubflow.changed, - instances: subflowRefresh.instances - } - } - } - RED.history.push(historyEvent); - updateActiveNodes(); - RED.nodes.dirty(true); - } - if (mouse_mode === RED.state.QUICK_JOINING) { - if (addedLinks.length > 0 || modifiedNodes.length > 0) { - hideDragLines(); - if (portType === PORT_TYPE_INPUT && d.outputs > 0) { - showDragLines([{node:d,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (portType === PORT_TYPE_OUTPUT && d.inputs > 0) { - showDragLines([{node:d,port:0,portType:PORT_TYPE_INPUT}]); - } else { - resetMouseVars(); - } - mousedown_link = select_link; - if (select_link) { - selectedLinks.clear(); - selectedLinks.add(select_link); - updateSelection(); - } else { - selectedLinks.clear(); - } - } - redraw(); - return; - } - - resetMouseVars(); - hideDragLines(); - if (select_link) { - selectedLinks.clear(); - selectedLinks.add(select_link); - } - mousedown_link = select_link; - if (select_link) { - updateSelection(); - } - redraw(); - } - } - - var portLabelHoverTimeout = null; - var portLabelHover = null; - - - function getElementPosition(node) { - var d3Node = d3.select(node); - if (d3Node.attr('class') === 'red-ui-workspace-chart-event-layer') { - return [0,0]; - } - var result = []; - var localPos = [0,0]; - if (node.nodeName.toLowerCase() === 'g') { - var transform = d3Node.attr("transform"); - if (transform) { - localPos = d3.transform(transform).translate; - } - } else { - localPos = [d3Node.attr("x")||0,d3Node.attr("y")||0]; - } - var parentPos = getElementPosition(node.parentNode); - return [localPos[0]+parentPos[0],localPos[1]+parentPos[1]] - - } - - function getPortLabel(node,portType,portIndex) { - var result; - var nodePortLabels = (portType === PORT_TYPE_INPUT)?node.inputLabels:node.outputLabels; - if (nodePortLabels && nodePortLabels[portIndex]) { - return nodePortLabels[portIndex]; - } - var portLabels = (portType === PORT_TYPE_INPUT)?node._def.inputLabels:node._def.outputLabels; - if (typeof portLabels === 'string') { - result = portLabels; - } else if (typeof portLabels === 'function') { - try { - result = portLabels.call(node,portIndex); - } catch(err) { - console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err); - result = null; - } - } else if ($.isArray(portLabels)) { - result = portLabels[portIndex]; - } - return result; - } - function showTooltip(x,y,content,direction) { - var tooltip = eventLayer.append("g") - .attr("transform","translate("+x+","+y+")") - .attr("class","red-ui-flow-port-tooltip"); - - // First check for a user-provided newline - "\\n " - var newlineIndex = content.indexOf("\\n "); - if (newlineIndex > -1 && content[newlineIndex-1] !== '\\') { - content = content.substring(0,newlineIndex)+"..."; - } - - var lines = content.split("\n"); - var labelWidth = 6; - var labelHeight = 12; - var labelHeights = []; - var lineHeight = 0; - lines.forEach(function(l,i) { - var labelDimensions = calculateTextDimensions(l||" ", "red-ui-flow-port-tooltip-label"); - labelWidth = Math.max(labelWidth,labelDimensions[0] + 14); - labelHeights.push(labelDimensions[1]); - if (i === 0) { - lineHeight = labelDimensions[1]; - } - labelHeight += labelDimensions[1]; - }); - var labelWidth1 = (labelWidth/2)-5-2; - var labelWidth2 = labelWidth - 4; - - var labelHeight1 = (labelHeight/2)-5-2; - var labelHeight2 = labelHeight - 4; - var path; - var lx; - var ly = -labelHeight/2; - var anchor; - if (direction === "left") { - path = "M0 0 l -5 -5 v -"+(labelHeight1)+" q 0 -2 -2 -2 h -"+labelWidth+" q -2 0 -2 2 v "+(labelHeight2)+" q 0 2 2 2 h "+labelWidth+" q 2 0 2 -2 v -"+(labelHeight1)+" l 5 -5"; - lx = -14; - anchor = "end"; - } else if (direction === "right") { - path = "M0 0 l 5 -5 v -"+(labelHeight1)+" q 0 -2 2 -2 h "+labelWidth+" q 2 0 2 2 v "+(labelHeight2)+" q 0 2 -2 2 h -"+labelWidth+" q -2 0 -2 -2 v -"+(labelHeight1)+" l -5 -5" - lx = 14; - anchor = "start"; - } else if (direction === "top") { - path = "M0 0 l 5 -5 h "+(labelWidth1)+" q 2 0 2 -2 v -"+labelHeight+" q 0 -2 -2 -2 h -"+(labelWidth2)+" q -2 0 -2 2 v "+labelHeight+" q 0 2 2 2 h "+(labelWidth1)+" l 5 5" - lx = -labelWidth/2 + 6; - ly = -labelHeight-lineHeight+12; - anchor = "start"; - } - tooltip.append("path").attr("d",path); - lines.forEach(function(l,i) { - ly += labelHeights[i]; - // tooltip.append("path").attr("d","M "+(lx-10)+" "+ly+" l 20 0 m -10 -5 l 0 10 ").attr('r',2).attr("stroke","#f00").attr("stroke-width","1").attr("fill","none") - tooltip.append("svg:text").attr("class","red-ui-flow-port-tooltip-label") - .attr("x", lx) - .attr("y", ly) - .attr("text-anchor",anchor) - .text(l||" ") - }); - return tooltip; - } - - function portMouseOver(port,d,portType,portIndex) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - clearTimeout(portLabelHoverTimeout); - var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active - ( - drag_lines.length > 0 && // Currently joining - drag_lines[0].portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT - ( - !drag_lines[0].virtualLink || // Not a link wire - (drag_lines[0].node.type === 'link in' && d.type === 'link out') || - (drag_lines[0].node.type === 'link out' && d.type === 'link in') - ) - ) - - if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { - portLabelHoverTimeout = setTimeout(function() { - var tooltip = getPortLabel(d,portType,portIndex); - if (!tooltip) { - return; - } - var pos = getElementPosition(port.node()); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)), - (pos[1]+5), - tooltip, - portType===PORT_TYPE_INPUT?"left":"right" - ); - },500); - } - port.classed("red-ui-flow-port-hovered",active); - } - function portMouseOut(port,d,portType,portIndex) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - port.classed("red-ui-flow-port-hovered",false); - } - - function junctionMouseOver(junction, d, portType) { - var active = (portType === undefined) || - (mouse_mode !== RED.state.JOINING && mouse_mode !== RED.state.QUICK_JOINING) || - (drag_lines.length > 0 && drag_lines[0].portType !== portType && !drag_lines[0].virtualLink) - junction.classed("red-ui-flow-junction-hovered", active); - } - function junctionMouseOut(junction, d) { - junction.classed("red-ui-flow-junction-hovered",false); - } - - function prepareDrag(mouse) { - mouse_mode = RED.state.MOVING; - // Called when movingSet should be prepared to be dragged - for (i=0;i 0 && clickElapsed < dblClickInterval) { - mouse_mode = RED.state.DEFAULT; - if (d.type != "subflow") { - if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { - RED.workspaces.show(d.type.substring(8)); - } else { - RED.editor.edit(d); - } - } else { - RED.editor.editSubflow(activeSubflow); - } - clickElapsed = 0; - d3.event.stopPropagation(); - return; - } - if (mouse_mode === RED.state.MOVING) { - // Moving primed, but not active. - if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) { - clearSelection(); - - selectGroup(RED.nodes.group(d.g), false); - enterActiveGroup(RED.nodes.group(d.g)) - - mousedown_node.selected = true; - movingSet.add(mousedown_node); - var mouse = d3.touches(this)[0]||d3.mouse(this); - mouse[0] += d.x-d.w/2; - mouse[1] += d.y-d.h/2; - prepareDrag(mouse); - updateSelection(); - return; - } - } - - groupNodeSelectPrimed = false; - - var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1) - var wasJoining = false; - if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - wasJoining = true; - if (drag_lines.length > 0) { - if (drag_lines[0].virtualLink) { - if (d.type === 'link in') { - direction = 1; - } else if (d.type === 'link out') { - direction = 0; - } - } else { - if (drag_lines[0].portType === 1) { - direction = PORT_TYPE_OUTPUT; - } else { - direction = PORT_TYPE_INPUT; - } - } - } - } - - portMouseUp(d, direction, 0); - if (wasJoining) { - d3.selectAll(".red-ui-flow-port-hovered").classed("red-ui-flow-port-hovered",false); - } - } - - - document.addEventListener('pointerlockchange', changeCallback, false); - document.addEventListener('mozpointerlockchange', changeCallback, false); - document.addEventListener('webkitpointerlockchange', changeCallback, false); - document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; - function getPosition(canvas, event) { - var x, y; - - if (event.x != undefined && event.y != undefined) { - x = event.x; - y = event.y; - } else // Firefox method to get the position - { - x = event.clientX + document.body.scrollLeft + - document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + - document.documentElement.scrollTop; - } - - x -= canvas.offsetLeft == null ? canvas.clientLeft : canvas.offsetLeft; - y -= canvas.offsetTop == null ? canvas.clientTop : canvas.offsetTop; - - return {x:x, y:y}; - } - //temporary proxy for when mouse is locked - function canvasMouseMove_locked(e) { - - var canvas = $(eventLayer[0])[0]; - - - // if we enter this for the first time, get the initial position - if (entryCoordinates.x == -1) { - entryCoordinates = getPosition(canvas, e); - } - - - //get a reference to the canvas - var movementX = e.movementX || - e.mozMovementX || - e.webkitMovementX || - 0; - - var movementY = e.movementY || - e.mozMovementY || - e.webkitMovementY || - 0; - - - // calculate the new coordinates where we should draw the ship - entryCoordinates.x = entryCoordinates.x + movementX; - entryCoordinates.y = entryCoordinates.y + movementY; - - if (entryCoordinates.x > chart.width() -65) { - entryCoordinates.x = chart.width()-65; - } else if (entryCoordinates.x < 0) { - entryCoordinates.x = 0; - } - - if (entryCoordinates.y > chart.height() - 85) { - entryCoordinates.y = chart.height() - 85; - } else if (entryCoordinates.y < 0) { - entryCoordinates.y = 0; - } - - - // determine the direction - var direction = 0; - if (movementX > 0) { - direction = 1; - } else if (movementX < 0) { - direction = -1; - } - // console.log(entryCoordinates) - - - d3.event = e - canvasMouseMove.call(this,e); - //canvasMouseMove.call(document.querySelector("g.red-ui-workspace-chart-event-layer")); - } - function changeCallback(e) { - var canvas = $(eventLayer[0])[0] - // var workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0]; - // var workspaceChart = $("#red-ui-workspace-chart")[0]; - if (document.pointerLockElement === canvas || - document.mozPointerLockElement === canvas || - document.webkitPointerLockElement === canvas) { - - // we've got a pointerlock for our element, add a mouselistener - canvas.addEventListener("mousemove", canvasMouseMove_locked, false); - // outer.on("mousemove", canvasMouseMove_locked) - // document.addEventListener("mousemove", canvasMouseMove_locked, false); - } else { - - // pointer lock is no longer active, remove the callback - canvas.removeEventListener("mousemove", canvasMouseMove_locked, false); - // document.removeEventListener("mousemove", canvasMouseMove_locked, false); - // if(outer.off) {outer.off("mousemove", canvasMouseMove_locked)} - // if(outer.removeEventListener) {outer.removeEventListener("mousemove", canvasMouseMove_locked)} - // and reset the entry coordinates - entryCoordinates = {x:-1, y:-1}; - } - } - - function nodeMouseDown(d) { - if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } - try { - // workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0] - var canvas = $(eventLayer[0])[0] - canvas.requestPointerLock = canvas.requestPointerLock || - canvas.mozRequestPointerLock || - canvas.webkitRequestPointerLock; - canvas.requestPointerLock() - console.log("got pointer lock") - } catch (error) { - console.error(error) - } - - focusView(); - if (d3.event.button === 1) { - return; - } - //var touch0 = d3.event; - //var pos = [touch0.pageX,touch0.pageY]; - //RED.touch.radialMenu.show(d3.select(this),pos); - if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { - var historyEvent = RED.history.peek(); - if (activeSpliceLink) { - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp - var spliceLink = d3.select(activeSpliceLink).data()[0]; - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: movingSet.get(0).n - }; - var link2 = { - source:movingSet.get(0).n, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - - historyEvent.links = [link1,link2]; - historyEvent.removedLinks = [spliceLink]; - updateActiveNodes(); - } - - if (activeHoverGroup) { - for (var j=0;j 30 ? 25 : (mousedown_node.w > 0 ? 8 : 3); - if (edgeDelta < targetEdgeDelta) { - if (clickPosition < 0) { - cnodes = [mousedown_node].concat(RED.nodes.getAllUpstreamNodes(mousedown_node)); - } else { - cnodes = [mousedown_node].concat(RED.nodes.getAllDownstreamNodes(mousedown_node)); - } - } else { - cnodes = RED.nodes.getAllFlowNodes(mousedown_node); - } - for (var n=0;n 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - } - function nodeMouseOut(d) { - if (RED.view.DEBUG) { console.warn("nodeMouseOut", mouse_mode,d); } - this.parentNode.classList.remove("red-ui-flow-node-hovered"); - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - if (drag_lines.length > 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - } - - function portMouseDownProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); } - function portTouchStartProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } - function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } - function portTouchEndProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } - function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } - function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } - - function junctionMouseOverProxy(e) { junctionMouseOver(d3.select(this), this.__data__, this.__portType__) } - function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) } - - function linkMouseDown(d) { - if (RED.view.DEBUG) { - console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); - } - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - if (d3.event.button === 2) { - return - } - mousedown_link = d; - - if (!(d3.event.metaKey || d3.event.ctrlKey)) { - clearSelection(); - } - if (d3.event.metaKey || d3.event.ctrlKey) { - if (!selectedLinks.has(mousedown_link)) { - selectedLinks.add(mousedown_link); - } else { - if (selectedLinks.length() !== 1) { - selectedLinks.remove(mousedown_link); - } - } - } else { - selectedLinks.add(mousedown_link); - } - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); - if (!mousedown_link.link && movingSet.length() === 0 && (d3.event.touches || d3.event.button === 0) && selectedLinks.length() === 1 && selectedLinks.has(mousedown_link) && (d3.event.metaKey || d3.event.ctrlKey)) { - d3.select(this).classed("red-ui-flow-link-splice",true); - var point = d3.mouse(this); - var clickedGroup = getGroupAt(point[0],point[1]); - showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup}); - } - } - function linkTouchStart(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - mousedown_link = d; - clearSelection(); - selectedLinks.clear(); - selectedLinks.add(mousedown_link); - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); - - var obj = d3.select(document.body); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - touchStartTime = setTimeout(function() { - touchStartTime = null; - showTouchMenu(obj,pos); - },touchLongPressTimeout); - d3.event.preventDefault(); - } - - function groupMouseUp(g) { - if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { - mouse_mode = RED.state.DEFAULT; - RED.editor.editGroup(g); - d3.event.stopPropagation(); - return; - } - - } - - function groupMouseDown(g) { - var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); - // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { - // return - // } - - focusView(); - if (d3.event.button === 1) { - return; - } - - if (mouse_mode == RED.state.QUICK_JOINING) { - d3.event.stopPropagation(); - return; - } else if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - - mousedown_group = g; - - var now = Date.now(); - clickElapsed = now-clickTime; - clickTime = now; - - dblClickPrimed = ( - lastClickNode == g && - (d3.event.touches || d3.event.button === 0) && - !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && - clickElapsed < dblClickInterval - ); - lastClickNode = g; - - if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { - if (g === activeGroup) { - exitActiveGroup(); - } - deselectGroup(g); - d3.event.stopPropagation(); - } else { - if (!g.selected) { - if (!d3.event.ctrlKey && !d3.event.metaKey) { - var ag = activeGroup; - clearSelection(); - if (ag && g.g === ag.id) { - enterActiveGroup(ag); - activeGroup.selected = true; - } - } - if (activeGroup) { - if (!RED.group.contains(activeGroup,g)) { - // Clicked on a group that is outside the activeGroup - exitActiveGroup(); - } else { - } - } - selectGroup(g,true);//!wasSelected); - } else if (activeGroup && g.g !== activeGroup.id){ - exitActiveGroup(); - } - - - if (d3.event.button != 2) { - var d = g.nodes[0]; - prepareDrag(mouse); - mousedown_group.dx = mousedown_group.x - mouse[0]; - mousedown_group.dy = mousedown_group.y - mouse[1]; - } - } - - updateSelection(); - redraw(); - d3.event.stopPropagation(); - } - - function selectGroup(g, includeNodes, addToMovingSet) { - if (!g.selected) { - g.selected = true; - g.dirty = true; - } - if (addToMovingSet !== false) { - movingSet.add(g); - } - if (includeNodes) { - var currentSet = new Set(movingSet.nodes()); - var allNodes = RED.group.getNodes(g,true); - allNodes.forEach(function(n) { - if (!currentSet.has(n)) { - movingSet.add(n) - // n.selected = true; - } - n.dirty = true; - }) - } - } - function enterActiveGroup(group) { - if (activeGroup) { - exitActiveGroup(); - } - group.active = true; - group.dirty = true; - activeGroup = group; - movingSet.remove(group); - } - function exitActiveGroup() { - if (activeGroup) { - activeGroup.active = false; - activeGroup.dirty = true; - deselectGroup(activeGroup); - selectGroup(activeGroup,true); - activeGroup = null; - } - } - function deselectGroup(g) { - if (g.selected) { - g.selected = false; - g.dirty = true; - } - var nodeSet = new Set(g.nodes); - nodeSet.add(g); - for (var i = movingSet.length()-1; i >= 0; i -= 1) { - var msn = movingSet.get(i); - if (nodeSet.has(msn.n) || msn.n === g) { - msn.n.selected = false; - msn.n.dirty = true; - movingSet.remove(msn.n,i) - } - } - } - function getGroupAt(x,y) { - // x,y expected to be in node-co-ordinate space - var candidateGroups = {}; - for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { - candidateGroups[g.id] = g; - } - } - var ids = Object.keys(candidateGroups); - if (ids.length > 1) { - ids.forEach(function(id) { - if (candidateGroups[id] && candidateGroups[id].g) { - delete candidateGroups[candidateGroups[id].g] - } - }) - ids = Object.keys(candidateGroups); - } - if (ids.length === 0) { - return null; - } else { - return candidateGroups[ids[ids.length-1]] - } - } - - function isButtonEnabled(d) { - var buttonEnabled = true; - var ws = RED.nodes.workspace(RED.workspaces.active()); - if (ws && !ws.disabled && !d.d) { - if (d._def.button.hasOwnProperty('enabled')) { - if (typeof d._def.button.enabled === "function") { - buttonEnabled = d._def.button.enabled.call(d); - } else { - buttonEnabled = d._def.button.enabled; - } - } - } else { - buttonEnabled = false; - } - return buttonEnabled; - } - - function nodeButtonClicked(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - if (d3.event) { - d3.event.stopPropagation(); - } - return; - } - var activeWorkspace = RED.workspaces.active(); - var ws = RED.nodes.workspace(activeWorkspace); - if (ws && !ws.disabled && !d.d) { - if (d._def.button.toggle) { - d[d._def.button.toggle] = !d[d._def.button.toggle]; - d.dirty = true; - } - if (d._def.button.onclick) { - try { - d._def.button.onclick.call(d); - } catch(err) { - console.log("Definition error: "+d.type+".onclick",err); - } - } - if (d.dirty) { - redraw(); - } - } else { - if (activeSubflow) { - RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning"); - } else { - RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning"); - } - } - if (d3.event) { - d3.event.preventDefault(); - } - } - - function showTouchMenu(obj,pos) { - var mdn = mousedown_node; - var options = []; - options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); - options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); - options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); - options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); - options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); - options.push({name:"select",onselect:function() {selectAll();}}); - options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); - options.push({name:"add",onselect:function() { - chartPos = chart.offset(); - showQuickAddDialog({ - position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()], - touchTrigger:true - }) - }}); - - RED.touch.radialMenu.show(obj,pos,options); - resetMouseVars(); - } - - function createIconAttributes(iconUrl, icon_group, d) { - var fontAwesomeUnicode = null; - if (iconUrl.indexOf("font-awesome/") === 0) { - var iconName = iconUrl.substr(13); - var fontAwesomeUnicode = RED.nodes.fontAwesome.getIconUnicode(iconName); - if (!fontAwesomeUnicode) { - var iconPath = RED.utils.getDefaultNodeIcon(d._def, d); - iconUrl = RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file; - } - } - if (fontAwesomeUnicode) { - // Since Node-RED workspace uses SVG, i tag cannot be used for font-awesome icon. - // On SVG, use text tag as an alternative. - icon_group.append("text") - .attr("xlink:href",iconUrl) - .attr("class","fa-lg") - .attr("x",15) - .text(fontAwesomeUnicode); - } else { - var icon = icon_group.append("image") - .style("display","none") - .attr("xlink:href",iconUrl) - .attr("class","red-ui-flow-node-icon") - .attr("x",0) - .attr("width","30") - .attr("height","30"); - - var img = new Image(); - img.src = iconUrl; - img.onload = function() { - if (!iconUrl.match(/\.svg$/)) { - var largestEdge = Math.max(img.width,img.height); - var scaleFactor = 1; - if (largestEdge > 30) { - scaleFactor = 30/largestEdge; - } - var width = img.width * scaleFactor; - var height = img.height * scaleFactor; - icon.attr("width",width); - icon.attr("height",height); - icon.attr("x",15-width/2); - } - icon.attr("xlink:href",iconUrl); - icon.style("display",null); - //if ("right" == d._def.align) { - // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);}); - // icon_shade.attr("x",function(d){return d.w-30}); - // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);}); - //} - } - } - } - - function redrawStatus(d,nodeEl) { - if (d.z !== RED.workspaces.active()) { - return; - } - if (!nodeEl) { - nodeEl = document.getElementById(d.id); - } - if (nodeEl) { - // Do not show node status if: - // - global flag set - // - node has no status - // - node is disabled - if (!showStatus || !d.status || d.d === true) { - nodeEl.__statusGroup__.style.display = "none"; - } else { - nodeEl.__statusGroup__.style.display = "inline"; - var fill = status_colours[d.status.fill]; // Only allow our colours for now - if (d.status.shape == null && fill == null) { - nodeEl.__statusShape__.style.display = "none"; - nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); - } else { - nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); - var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; - nodeEl.__statusShape__.style.display = "inline"; - nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); - } - if (d.status.hasOwnProperty('text')) { - nodeEl.__statusLabel__.textContent = d.status.text; - } else { - nodeEl.__statusLabel__.textContent = ""; - } - } - delete d.dirtyStatus; - } - } - - var pendingRedraw; - - function redraw() { - if (RED.view.DEBUG_SYNC_REDRAW) { - _redraw(); - } else { - if (pendingRedraw) { - cancelAnimationFrame(pendingRedraw); - } - pendingRedraw = requestAnimationFrame(_redraw); - } - } - - function _redraw() { - eventLayer.attr("transform","scale("+scaleFactor+")"); - outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); - - // Don't bother redrawing nodes if we're drawing links - if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) { - - var dirtyNodes = {}; - - if (activeSubflow) { - var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); - subflowOutputs.exit().remove(); - var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output") - outGroup.each(function(d,i) { - var node = d3.select(this); - var nodeContents = document.createDocumentFragment(); - - d.h = 40; - d.resize = true; - d.dirty = true; - - var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); - mainRect.__data__ = d; - mainRect.setAttribute("class", "red-ui-flow-subflow-port"); - mainRect.setAttribute("rx", 8); - mainRect.setAttribute("ry", 8); - mainRect.setAttribute("width", 40); - mainRect.setAttribute("height", 40); - node[0][0].__mainRect__ = mainRect; - d3.select(mainRect) - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",nodeTouchStart) - .on("touchend",nodeTouchEnd) - nodeContents.appendChild(mainRect); - - var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g"); - output_groupEl.setAttribute("x",0); - output_groupEl.setAttribute("y",0); - node[0][0].__outputLabelGroup__ = output_groupEl; - - var output_output = document.createElementNS("http://www.w3.org/2000/svg","text"); - output_output.setAttribute("class","red-ui-flow-port-label"); - output_output.style["font-size"] = "10px"; - output_output.textContent = "output"; - output_groupEl.appendChild(output_output); - node[0][0].__outputOutput__ = output_output; - - var output_number = document.createElementNS("http://www.w3.org/2000/svg","text"); - output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index"); - output_number.setAttribute("x",0); - output_number.setAttribute("y",0); - output_number.textContent = d.i+1; - output_groupEl.appendChild(output_number); - node[0][0].__outputNumber__ = output_number; - - var output_border = document.createElementNS("http://www.w3.org/2000/svg","path"); - output_border.setAttribute("d","M 40 1 l 0 38") - output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border") - output_groupEl.appendChild(output_border); - node[0][0].__outputBorder__ = output_border; - - nodeContents.appendChild(output_groupEl); - - var text = document.createElementNS("http://www.w3.org/2000/svg","g"); - text.setAttribute("class","red-ui-flow-port-label"); - text.setAttribute("transform","translate(38,0)"); - text.setAttribute('style', 'fill : #888'); // hard coded here! - node[0][0].__textGroup__ = text; - nodeContents.append(text); - - var portEl = document.createElementNS("http://www.w3.org/2000/svg","g"); - portEl.setAttribute('transform','translate(-5,15)') - - var port = document.createElementNS("http://www.w3.org/2000/svg","rect"); - port.setAttribute("class","red-ui-flow-port"); - port.setAttribute("rx",3); - port.setAttribute("ry",3); - port.setAttribute("width",10); - port.setAttribute("height",10); - portEl.appendChild(port); - port.__data__ = d; - - d3.select(port) - .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) - .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); - - node[0][0].__port__ = portEl - nodeContents.appendChild(portEl); - node[0][0].appendChild(nodeContents); - }); - - var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); - subflowInputs.exit().remove(); - var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); - inGroup.each(function(d,i) { - d.w=40; - d.h=40; - }); - inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) - // TODO: This is exactly the same set of handlers used for regular nodes - DRY - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",nodeTouchStart) - .on("touchend", nodeTouchEnd); - - inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) - .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) - .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) - .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);}) - .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);}); - - inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input"); - - var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); - subflowStatus.exit().remove(); - - var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); - statusGroup.each(function(d,i) { - d.w=40; - d.h=40; - }); - statusGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) - // TODO: This is exactly the same set of handlers used for regular nodes - DRY - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",nodeTouchStart) - .on("touchend", nodeTouchEnd); - - statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) - .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) - .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); - - statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status"); - - subflowOutputs.each(function(d,i) { - if (d.dirty) { - - var port_height = 40; - - var self = this; - var thisNode = d3.select(this); - - dirtyNodes[d.id] = d; - - var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || ""; - var hideLabel = (label.length < 1) - - var labelParts; - if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) { - labelParts = getLabelParts(label, "red-ui-flow-node-label"); - if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) { - d.resize = true; - } - this.__label__ = label; - this.__labelLineCount__ = labelParts.lines.length; - - if (hideLabel) { - d.h = Math.max(port_height,(d.outputs || 0) * 15); - } else { - d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height); - } - this.__hideLabel__ = hideLabel; - } - - if (d.resize) { - var ow = d.w; - if (hideLabel) { - d.w = port_height; - } else { - d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) ); - } - if (ow !== undefined) { - d.x += (d.w-ow)/2; - } - d.resize = false; - } - - this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); - // This might be the first redraw after a node has been click-dragged to start a move. - // So its selected state might have changed since the last redraw. - this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) - if (mouse_mode != RED.state.MOVING_ACTIVE) { - this.classList.toggle("red-ui-flow-node-disabled", d.d === true); - this.__mainRect__.setAttribute("width", d.w) - this.__mainRect__.setAttribute("height", d.h) - this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); - - if (labelParts) { - // The label has changed - var sa = labelParts.lines; - var sn = labelParts.lines.length; - var textLines = this.__textGroup__.childNodes; - while(textLines.length > sn) { - textLines[textLines.length-1].remove(); - } - for (var i=0; i0?7:0))/20)) ); - } - if (ow !== undefined) { - d.x += (d.w-ow)/2; - } - d.resize = false; - } - if (d._colorChanged) { - var newColor = RED.utils.getNodeColor(d.type,d._def); - this.__mainRect__.setAttribute("fill",newColor); - if (this.__buttonGroupButton__) { - this.__buttonGroupButton__.settAttribute("fill",newColor); - } - delete d._colorChanged; - } - //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); - this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); - // This might be the first redraw after a node has been click-dragged to start a move. - // So its selected state might have changed since the last redraw. - this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) - if (mouse_mode != RED.state.MOVING_ACTIVE) { - this.classList.toggle("red-ui-flow-node-disabled", d.d === true); - this.__mainRect__.setAttribute("width", d.w) - this.__mainRect__.setAttribute("height", d.h) - this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); - - if (labelParts) { - // The label has changed - var sa = labelParts.lines; - var sn = labelParts.lines.length; - var textLines = this.__textGroup__.childNodes; - while(textLines.length > sn) { - textLines[textLines.length-1].remove(); - } - for (var i=0; i numOutputs) { - var port = this.__outputs__.pop(); - RED.hooks.trigger("viewRemovePort",{ - node:d, - el:this, - port:port, - portType: "output", - portIndex: this.__outputs__.length - }) - port.remove(); - } - for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { - var portGroup; - if (portIndex === this.__outputs__.length) { - portGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); - portGroup.setAttribute("class","red-ui-flow-port-output"); - var portPort; - if (d.type === "link out") { - portPort = document.createElementNS("http://www.w3.org/2000/svg","circle"); - portPort.setAttribute("cx",11); - portPort.setAttribute("cy",5); - portPort.setAttribute("r",5); - portPort.setAttribute("class","red-ui-flow-port red-ui-flow-link-port"); - } else { - portPort = document.createElementNS("http://www.w3.org/2000/svg","rect"); - portPort.setAttribute("rx",3); - portPort.setAttribute("ry",3); - portPort.setAttribute("width",10); - portPort.setAttribute("height",10); - portPort.setAttribute("class","red-ui-flow-port"); - } - portGroup.appendChild(portPort); - portGroup.__port__ = portPort; - portPort.__data__ = this.__data__; - portPort.__portType__ = PORT_TYPE_OUTPUT; - portPort.__portIndex__ = portIndex; - portPort.addEventListener("mousedown", portMouseDownProxy); - portPort.addEventListener("touchstart", portTouchStartProxy); - portPort.addEventListener("mouseup", portMouseUpProxy); - portPort.addEventListener("touchend", portTouchEndProxy); - portPort.addEventListener("mouseover", portMouseOverProxy); - portPort.addEventListener("mouseout", portMouseOutProxy); - - this.appendChild(portGroup); - this.__outputs__.push(portGroup); - RED.hooks.trigger("viewAddPort",{node:d,el: this, port: portGroup, portType: "output", portIndex: portIndex}) - } else { - portGroup = this.__outputs__[portIndex]; - } - var x = d.w - 5; - var y = (d.h/2)-((numOutputs-1)/2)*13; - portGroup.setAttribute("transform","translate("+x+","+((y+13*portIndex)-5)+")") - } - if (d._def.icon) { - var icon = thisNode.select(".red-ui-flow-node-icon"); - var faIcon = thisNode.select(".fa-lg"); - var current_url; - if (!icon.empty()) { - current_url = icon.attr("xlink:href"); - } else { - current_url = faIcon.attr("xlink:href"); - } - var new_url = RED.utils.getNodeIcon(d._def,d); - if (new_url !== current_url) { - if (!icon.empty()) { - icon.remove(); - } else { - faIcon.remove(); - } - var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); - createIconAttributes(new_url, iconGroup, d); - icon = thisNode.select(".red-ui-flow-node-icon"); - faIcon = thisNode.select(".fa-lg"); - } - - icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); - this.__iconShade__.setAttribute("height", d.h ); - this.__iconShadeBorder__.setAttribute("d", - "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) - ); - faIcon.attr("y",(d.h+13)/2); - } - // this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); - // this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); - // this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); - // this.__errorBadge__.classList.toggle("hide", d.valid); - - thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { - var port = d3.select(this); - port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) - }); - - if (d._def.button) { - var buttonEnabled = isButtonEnabled(d); - this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { - this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); - } - - var x = d._def.align == "right"?d.w-6:-25; - if (d._def.button.toggle && !d[d._def.button.toggle]) { - x = x - (d._def.align == "right"?8:-8); - } - this.__buttonGroup__.setAttribute("transform", "translate("+x+",2)"); - - if (d._def.button.toggle) { - this.__buttonGroupButton__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) - this.__buttonGroupBackground__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) - } - - if (typeof d._def.button.visible === "function") { // is defined and a function... - if (d._def.button.visible.call(d) === false) { - this.__buttonGroup__.style.display = "none"; - } - else { - this.__buttonGroup__.style.display = "inherit"; - } - } - } - // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); - // thisNode.selectAll("text.node_badge_label").text(function(d,i) { - // if (d._def.badge) { - // if (typeof d._def.badge == "function") { - // try { - // return d._def.badge.call(d); - // } catch(err) { - // console.log("Definition error: "+d.type+".badge",err); - // return ""; - // } - // } else { - // return d._def.badge; - // } - // } - // return ""; - // }); - } - - if (d.dirtyStatus) { - redrawStatus(d,this); - } - d.dirty = false; - if (d.g) { - if (!dirtyGroups[d.g]) { - var gg = d.g; - while (gg && !dirtyGroups[gg]) { - dirtyGroups[gg] = RED.nodes.group(gg); - gg = dirtyGroups[gg].g; - } - } - } - } - - RED.hooks.trigger("viewRedrawNode",{node:d,el:this}) - }); - - if (nodesReordered) { - node.sort(function(a,b) { - return a._index - b._index; - }) - } - - var junction = junctionLayer.selectAll(".red-ui-flow-junction").data( - activeJunctions, - d => d.id - ) - var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction") - junctionEnter.each(function(d,i) { - var junction = d3.select(this); - var contents = document.createDocumentFragment(); - // d.added = true; - var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect"); - junctionBack.setAttribute("class","red-ui-flow-junction-background"); - junctionBack.setAttribute("x",-5); - junctionBack.setAttribute("y",-5); - junctionBack.setAttribute("width",10); - junctionBack.setAttribute("height",10); - junctionBack.setAttribute("rx",3); - junctionBack.setAttribute("ry",3); - junctionBack.__data__ = d; - this.__junctionBack__ = junctionBack; - contents.appendChild(junctionBack); - - var junctionInput = document.createElementNS("http://www.w3.org/2000/svg","rect"); - junctionInput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-input"); - junctionInput.setAttribute("x",-5); - junctionInput.setAttribute("y",-5); - junctionInput.setAttribute("width",10); - junctionInput.setAttribute("height",10); - junctionInput.setAttribute("rx",3); - junctionInput.setAttribute("ry",3); - junctionInput.__data__ = d; - junctionInput.__portType__ = PORT_TYPE_INPUT; - junctionInput.__portIndex__ = 0; - this.__junctionInput__ = junctionOutput; - contents.appendChild(junctionInput); - junctionInput.addEventListener("mouseup", portMouseUpProxy); - junctionInput.addEventListener("mousedown", portMouseDownProxy); - - - this.__junctionInput__ = junctionInput; - contents.appendChild(junctionInput); - var junctionOutput = document.createElementNS("http://www.w3.org/2000/svg","rect"); - junctionOutput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-output"); - junctionOutput.setAttribute("x",-5); - junctionOutput.setAttribute("y",-5); - junctionOutput.setAttribute("width",10); - junctionOutput.setAttribute("height",10); - junctionOutput.setAttribute("rx",3); - junctionOutput.setAttribute("ry",3); - junctionOutput.__data__ = d; - junctionOutput.__portType__ = PORT_TYPE_OUTPUT; - junctionOutput.__portIndex__ = 0; - this.__junctionOutput__ = junctionOutput; - contents.appendChild(junctionOutput); - junctionOutput.addEventListener("mouseup", portMouseUpProxy); - junctionOutput.addEventListener("mousedown", portMouseDownProxy); - - junctionOutput.addEventListener("mouseover", junctionMouseOverProxy); - junctionOutput.addEventListener("mouseout", junctionMouseOutProxy); - junctionInput.addEventListener("mouseover", junctionMouseOverProxy); - junctionInput.addEventListener("mouseout", junctionMouseOutProxy); - junctionBack.addEventListener("mouseover", junctionMouseOverProxy); - junctionBack.addEventListener("mouseout", junctionMouseOutProxy); - - // These handlers expect to be registered as d3 events - d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp); - - junction[0][0].appendChild(contents); - }) - junction.exit().remove(); - junction.each(function(d) { - var junction = d3.select(this); - this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")"); - if (d.dirty) { - junction.classed("red-ui-flow-junction-dragging", mouse_mode === RED.state.MOVING_ACTIVE && movingSet.has(d)) - junction.classed("selected", !!d.selected) - dirtyNodes[d.id] = d; - - if (d.g) { - if (!dirtyGroups[d.g]) { - var gg = d.g; - while (gg && !dirtyGroups[gg]) { - dirtyGroups[gg] = RED.nodes.group(gg); - gg = dirtyGroups[gg].g; - } - } - } - - } - - }) - - var link = linkLayer.selectAll(".red-ui-flow-link").data( - activeLinks, - function(d) { - return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; - } - ); - var linkEnter = link.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link"); - - linkEnter.each(function(d,i) { - var l = d3.select(this); - var pathContents = document.createDocumentFragment(); - - d.added = true; - var pathBack = document.createElementNS("http://www.w3.org/2000/svg","path"); - pathBack.__data__ = d; - pathBack.setAttribute("class","red-ui-flow-link-background red-ui-flow-link-path"+(d.link?" red-ui-flow-link-link":"")); - this.__pathBack__ = pathBack; - pathContents.appendChild(pathBack); - d3.select(pathBack) - .on("mousedown",linkMouseDown) - .on("touchstart",linkTouchStart) - .on("mousemove", function(d) { - if (mouse_mode === RED.state.SLICING) { - - selectedLinks.add(d) - l.classed("red-ui-flow-link-splice",true) - redraw() - } else if (mouse_mode === RED.state.SLICING_JUNCTION && !d.link) { - if (!l.classed("red-ui-flow-link-splice")) { - // Find intersection point - var lineLength = pathLine.getTotalLength(); - var pos; - var delta = Infinity; - for (var i = 0; i < lineLength; i++) { - var linePos = pathLine.getPointAtLength(i); - var posDeltaX = Math.abs(linePos.x-d3.event.offsetX) - var posDeltaY = Math.abs(linePos.y-d3.event.offsetY) - var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY - if (posDelta < delta) { - pos = linePos - delta = posDelta - } - } - d._sliceLocation = pos - selectedLinks.add(d) - l.classed("red-ui-flow-link-splice",true) - redraw() - } - } - }) - - var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); - pathOutline.__data__ = d; - pathOutline.setAttribute("class","red-ui-flow-link-outline red-ui-flow-link-path"); - this.__pathOutline__ = pathOutline; - pathContents.appendChild(pathOutline); - - var pathLine = document.createElementNS("http://www.w3.org/2000/svg","path"); - pathLine.__data__ = d; - pathLine.setAttribute("class","red-ui-flow-link-line red-ui-flow-link-path"+ - (d.link?" red-ui-flow-link-link":(activeSubflow?" red-ui-flow-subflow-link":""))); - this.__pathLine__ = pathLine; - pathContents.appendChild(pathLine); - - l[0][0].appendChild(pathContents); - }); - - link.exit().remove(); - link.each(function(d) { - var link = d3.select(this); - if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { - var numOutputs = d.source.outputs || 1; - var sourcePort = d.sourcePort || 0; - var y = -((numOutputs-1)/2)*13 +13*sourcePort; - d.x1 = d.source.x+(d.source.w/2||0); - d.y1 = d.source.y+y; - d.x2 = d.target.x-(d.target.w/2||0); - d.y2 = d.target.y; - - // return "M "+d.x1+" "+d.y1+ - // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ - // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ - // d.x2+" "+d.y2; - var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); - if (/NaN/.test(path)) { - path = "" - } - this.__pathBack__.setAttribute("d",path); - this.__pathOutline__.setAttribute("d",path); - this.__pathLine__.setAttribute("d",path); - this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); - this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); - } - - this.classList.toggle("red-ui-flow-link-selected", !!d.selected); - - var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); - this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) - delete d.added; - }) - var offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow").data( - activeFlowLinks, - function(d) { - return d.node.id+":"+d.refresh - } - ); - - var offLinksEnter = offLinks.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link-off-flow"); - offLinksEnter.each(function(d,i) { - var g = d3.select(this); - var s = 1; - var labelAnchor = "start"; - if (d.node.type === "link in") { - s = -1; - labelAnchor = "end"; - } - var stemLength = s*30; - var branchLength = s*20; - var l = g.append("svg:path").attr("class","red-ui-flow-link-link").attr("d","M 0 0 h "+stemLength); - var links = d.links; - var flows = Object.keys(links); - var tabOrder = RED.nodes.getWorkspaceOrder(); - flows.sort(function(A,B) { - return tabOrder.indexOf(A) - tabOrder.indexOf(B); - }); - var linkWidth = 10; - var h = node_height; - var y = -(flows.length-1)*h/2; - var linkGroups = g.selectAll(".red-ui-flow-link-group").data(flows); - var enterLinkGroups = linkGroups.enter().append("g").attr("class","red-ui-flow-link-group") - .on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',true)}) - .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',false)}) - .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) - .on('mouseup', function(f) { - if (mouse_mode !== 0) { - return - } - d3.event.stopPropagation(); - var targets = d.links[f]; - RED.workspaces.show(f); - targets.forEach(function(n) { - n.selected = true; - n.dirty = true; - movingSet.add(n); - if (targets.length === 1) { - RED.view.reveal(n.id); - } - }); - updateSelection(); - redraw(); - }); - enterLinkGroups.each(function(f) { - var linkG = d3.select(this); - linkG.append("svg:path") - .attr("class","red-ui-flow-link-link") - .attr("d", - "M "+stemLength+" 0 "+ - "C "+(stemLength+(1.7*branchLength))+" "+0+ - " "+(stemLength+(0.1*branchLength))+" "+y+" "+ - (stemLength+branchLength*1.5)+" "+y+" " - ); - linkG.append("svg:path") - .attr("class","red-ui-flow-link-port") - .attr("d", - "M "+(stemLength+branchLength*1.5+s*(linkWidth+7))+" "+(y-12)+" "+ - "h "+(-s*linkWidth)+" "+ - "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*-3)+" 3 "+ - "v 18 "+ - "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*3)+" 3 "+ - "h "+(s*linkWidth) - ); - linkG.append("svg:path") - .attr("class","red-ui-flow-link-port") - .attr("d", - "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y-12)+" "+ - "h "+(s*(linkWidth*3))+" "+ - "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y+12)+" "+ - "h "+(s*(linkWidth*3)) - ).style("stroke-dasharray","12 3 8 4 3"); - linkG.append("rect").attr("class","red-ui-flow-port red-ui-flow-link-port") - .attr("x",stemLength+branchLength*1.5-4+(s*4)) - .attr("y",y-4) - .attr("rx",2) - .attr("ry",2) - .attr("width",8) - .attr("height",8); - linkG.append("rect") - .attr("x",stemLength+branchLength*1.5-(s===-1?node_width:0)) - .attr("y",y-12) - .attr("width",node_width) - .attr("height",24) - .style("stroke","none") - .style("fill","transparent") - var tab = RED.nodes.workspace(f); - var label; - if (tab) { - label = tab.label || tab.id; - } - linkG.append("svg:text") - .attr("class","red-ui-flow-port-label") - .attr("x",stemLength+branchLength*1.5+(s*15)) - .attr("y",y+1) - .style("font-size","10px") - .style("text-anchor",labelAnchor) - .text(label); - - y += h; - }); - linkGroups.exit().remove(); - }); - offLinks.exit().remove(); - offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow"); - offLinks.each(function(d) { - var s = 1; - if (d.node.type === "link in") { - s = -1; - } - var link = d3.select(this); - link.attr("transform", function(d) { return "translate(" + (d.node.x+(s*d.node.w/2)) + "," + (d.node.y) + ")"; }); - - }) - - var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); - group.exit().each(function(d,i) { - document.getElementById("group_select_"+d.id).remove() - }).remove(); - var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") - var addedGroups = false; - groupEnter.each(function(d,i) { - addedGroups = true; - var g = d3.select(this); - g.attr("id",d.id); - - var groupBorderRadius = 4; - - var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); - selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) - .classed("red-ui-flow-group-outline-select-background",true) - .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) - .attr("x",-4) - .attr("y",-4); - - - selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) - .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) - .attr("x",-4) - .attr("y",-4) - selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); - selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); - selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); - selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); - - g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); - - g.append('rect').classed("red-ui-flow-group-body",true) - .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({ - "fill":d.fill||"none", - "stroke": d.stroke||"none", - }) - g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) - g.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); - g.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); - - g.append('svg:text').attr("class","red-ui-flow-group-label"); - d.dirty = true; - }); - if (addedGroups) { - group.sort(function(a,b) { - if (a._root === b._root) { - return a._depth - b._depth; - } else { - return a._index - b._index; - } - }) - } - group[0].reverse(); - var groupOpCount=0; - group.each(function(d,i) { - groupOpCount++ - if (d.resize) { - d.minWidth = 0; - delete d.resize; - } - if (d.dirty || dirtyGroups[d.id]) { - var g = d3.select(this); - var recalculateLabelOffsets = false; - if (d.nodes.length > 0) { - // If the group was just moved, all of its contents was - // also moved - so no need to recalculate its bounding box - if (!d.groupMoved) { - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = 0; - var maxY = 0; - var margin = 26; - d.nodes.forEach(function(n) { - groupOpCount++ - if (n.type !== "group") { - minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-margin); - maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+margin); - } else { - minX = Math.min(minX,n.x-margin) - minY = Math.min(minY,n.y-margin) - maxX = Math.max(maxX,n.x+n.w+margin) - maxY = Math.max(maxY,n.y+n.h+margin) - } - }); - - d.x = minX; - d.y = minY; - d.w = maxX - minX; - d.h = maxY - minY; - recalculateLabelOffsets = true; - // if set explicitly to false, this group has just been - // imported so needed this initial resize calculation. - // Now that's done, delete the flag so the normal - // logic kicks in. - if (d.groupMoved === false) { - delete d.groupMoved; - } - } else { - delete d.groupMoved; - } - } else { - d.w = 40; - d.h = 40; - recalculateLabelOffsets = true; - } - if (recalculateLabelOffsets) { - if (!d.minWidth) { - if (d.style.label && d.name) { - var labelParts = getLabelParts(d.name||"","red-ui-flow-group-label"); - d.minWidth = labelParts.width + 8; - d.labels = labelParts.lines; - } else { - d.minWidth = 40; - d.labels = []; - } - } - d.w = Math.max(d.minWidth,d.w); - if (d.style.label && d.labels.length > 0) { - var labelPos = d.style["label-position"] || "nw"; - var h = (d.labels.length-1) * 16; - if (labelPos[0] === "s") { - h += 8; - } - d.h += h; - if (labelPos[0] === "n") { - if (d.nodes.length > 0) { - d.y -= h; - } - } - } - } - - g.attr("transform","translate("+d.x+","+d.y+")") - g.selectAll(".red-ui-flow-group-outline") - .attr("width",d.w) - .attr("height",d.h) - - - var selectGroup = document.getElementById("group_select_"+d.id); - selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); - if (d.hovered) { - selectGroup.classList.add("red-ui-flow-group-hovered") - } else { - selectGroup.classList.remove("red-ui-flow-group-hovered") - } - var selectGroupRect = selectGroup.children[0]; - selectGroupRect.setAttribute("width",d.w+8) - selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; - selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; - selectGroupRect = selectGroup.children[1]; - selectGroupRect.setAttribute("width",d.w+8) - selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; - selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; - - if (d.highlighted) { - selectGroup.classList.add("red-ui-flow-node-highlighted"); - } else { - selectGroup.classList.remove("red-ui-flow-node-highlighted"); - } - - - g.selectAll(".red-ui-flow-group-body") - .attr("width",d.w) - .attr("height",d.h) - .style("stroke", d.style.stroke || "") - .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") - .style("fill", d.style.fill || "") - .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") - - var label = g.selectAll(".red-ui-flow-group-label"); - label.classed("hide",!!!d.style.label) - if (d.style.label) { - var labelPos = d.style["label-position"] || "nw"; - var labelX = 0; - var labelY = 0; - - if (labelPos[0] === 'n') { - labelY = 0+15; // Allow for font-height - } else { - labelY = d.h - 5 -(d.labels.length -1) * 16; - } - if (labelPos[1] === 'w') { - labelX = 5; - labelAnchor = "start" - } else if (labelPos[1] === 'e') { - labelX = d.w-5; - labelAnchor = "end" - } else { - labelX = d.w/2; - labelAnchor = "middle" - } - if (d.style.hasOwnProperty('color')) { - label.style("fill",d.style.color) - } else { - label.style("fill",null) - } - label.attr("transform","translate("+labelX+","+labelY+")") - .attr("text-anchor",labelAnchor); - if (d.labels) { - var ypos = 0; - g.selectAll(".red-ui-flow-group-label-text").remove(); - d.labels.forEach(function (name) { - label.append("tspan") - .classed("red-ui-flow-group-label-text", true) - .text(name) - .attr("x", 0) - .attr("y", ypos); - ypos += 16; - }); - } else { - g.selectAll(".red-ui-flow-group-label-text").remove(); - } - } - - delete dirtyGroups[d.id]; - delete d.dirty; - } - }) - } else { - // JOINING - unselect any selected links - linkLayer.selectAll(".red-ui-flow-link-selected").data( - activeLinks, - function(d) { - return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; - } - ).classed("red-ui-flow-link-selected", false); - } - RED.view.navigator.refresh(); - if (d3.event) { - d3.event.preventDefault(); - } - } - - function focusView() { - try { - // Workaround for browser unexpectedly scrolling iframe into full - // view - record the parent scroll position and restore it after - // setting the focus - var scrollX = window.parent.window.scrollX; - var scrollY = window.parent.window.scrollY; - chart.trigger("focus"); - window.parent.window.scrollTo(scrollX,scrollY); - } catch(err) { - // In case we're iframed into a page of a different origin, just focus - // the view following the inevitable DOMException - chart.trigger("focus"); - } - } - - - /** - * Imports a new collection of nodes from a JSON String. - * - * - all get new IDs assigned - * - all "selected" - * - attached to mouse for placing - "IMPORT_DRAGGING" - * @param {String/Array} newNodesObj nodes to import - * @param {Object} options options object - * - * Options: - * - addFlow - whether to import nodes to a new tab - * - touchImport - whether this is a touch import. If not, imported nodes are - * attachedto mouse for placing - "IMPORT_DRAGGING" state - * - generateIds - whether to automatically generate new ids for all imported nodes - * - generateDefaultNames - whether to automatically update any nodes with clashing - * default names - */ - function importNodes(newNodesObj,options) { - options = options || { - addFlow: false, - touchImport: false, - generateIds: false, - generateDefaultNames: false - } - var addNewFlow = options.addFlow - var touchImport = options.touchImport; - - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - - var nodesToImport; - if (typeof newNodesObj === "string") { - if (newNodesObj === "") { - return; - } - try { - nodesToImport = JSON.parse(newNodesObj); - } catch(err) { - var e = new Error(RED._("clipboard.invalidFlow",{message:err.message})); - e.code = "NODE_RED"; - throw e; - } - } else { - nodesToImport = newNodesObj; - } - - if (!$.isArray(nodesToImport)) { - nodesToImport = [nodesToImport]; - } - if (options.generateDefaultNames) { - RED.actions.invoke("core:generate-node-names", nodesToImport, { - renameBlank: false, - renameClash: true, - generateHistory: false - }) - } - - try { - var activeSubflowChanged; - if (activeSubflow) { - activeSubflowChanged = activeSubflow.changed; - } - var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap}); - if (result) { - var new_nodes = result.nodes; - var new_links = result.links; - var new_groups = result.groups; - var new_junctions = result.junctions; - var new_workspaces = result.workspaces; - var new_subflows = result.subflows; - var removedNodes = result.removedNodes; - var new_default_workspace = result.missingWorkspace; - if (addNewFlow && new_default_workspace) { - RED.workspaces.show(new_default_workspace.id); - } - var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); - new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) - new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()})) - var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); - - clearSelection(); - movingSet.clear(); - movingSet.add(new_ms); - - - // TODO: pick a more sensible root node - if (movingSet.length() > 0) { - if (mouse_position == null) { - mouse_position = [0,0]; - } - - var dx = mouse_position[0]; - var dy = mouse_position[1]; - if (movingSet.length() > 0) { - var root_node = movingSet.get(0).n; - dx = root_node.x; - dy = root_node.y; - } - - var minX = 0; - var minY = 0; - var i; - var node,group; - var l =movingSet.length(); - for (i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && - ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) - - - } - } - - } - - var historyEvent = { - t:"add", - nodes:new_node_ids, - links:new_links, - groups:new_groups, - junctions: new_junctions, - workspaces:new_workspaces, - subflows:new_subflows, - dirty:RED.nodes.dirty() - }; - if (movingSet.length() === 0) { - RED.nodes.dirty(true); - } - if (activeSubflow) { - var subflowRefresh = RED.subflow.refresh(true); - if (subflowRefresh) { - historyEvent.subflow = { - id:activeSubflow.id, - changed: activeSubflowChanged, - instances: subflowRefresh.instances - } - } - } - if (removedNodes) { - var replaceEvent = { - t: "replace", - config: removedNodes - } - historyEvent = { - t:"multi", - events: [ - replaceEvent, - historyEvent - ] - } - } - - RED.history.push(historyEvent); - - updateActiveNodes(); - redraw(); - - var counts = []; - var newNodeCount = 0; - var newConfigNodeCount = 0; - new_nodes.forEach(function(n) { - if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) { - newNodeCount++; - } else { - newConfigNodeCount++; - } - }) - var newGroupCount = new_groups.length; - var newJunctionCount = new_junctions.length; - if (new_workspaces.length > 0) { - counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); - } - if (newNodeCount > 0) { - counts.push(RED._("clipboard.node",{count:newNodeCount})); - } - if (newGroupCount > 0) { - counts.push(RED._("clipboard.group",{count:newGroupCount})); - } - if (newConfigNodeCount > 0) { - counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount})); - } - if (new_subflows.length > 0) { - counts.push(RED._("clipboard.subflow",{count:new_subflows.length})); - } - if (removedNodes && removedNodes.length > 0) { - counts.push(RED._("clipboard.replacedNodes",{count:removedNodes.length})); - } - if (counts.length > 0) { - var countList = "
        • "+counts.join("
        • ")+"
        "; - RED.notify("

        "+RED._("clipboard.nodesImported")+"

        "+countList,{id:"clipboard"}); - } - - } - } catch(error) { - if (error.code === "import_conflict") { - // Pass this up for the called to resolve - throw error; - } else if (error.code != "NODE_RED") { - console.log(error.stack); - RED.notify(RED._("notification.error",{message:error.toString()}),"error"); - } else { - RED.notify(RED._("notification.error",{message:error.message}),"error"); - } - } - } - - function toggleShowGrid(state) { - if (state) { - gridLayer.style("visibility","visible"); - } else { - gridLayer.style("visibility","hidden"); - } - } - function toggleSnapGrid(state) { - snapGrid = state; - redraw(); - } - function toggleStatus(s) { - showStatus = s; - RED.nodes.eachNode(function(n) { n.dirtyStatus = true; n.dirty = true;}); - //TODO: subscribe/unsubscribe here - redraw(); - } - function setSelectedNodeState(isDisabled) { - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - var workspaceSelection = RED.workspaces.selection(); - var changed = false; - if (workspaceSelection.length > 0) { - // TODO: toggle workspace state - } else if (movingSet.length() > 0) { - var historyEvents = []; - for (var i=0;i 0) { - RED.history.push({ - t:"multi", - events: historyEvents, - dirty:RED.nodes.dirty() - }) - RED.nodes.dirty(true) - } - } - RED.view.redraw(); - - } - function getSelection() { - var selection = {}; - - var allNodes = new Set(); - - if (movingSet.length() > 0) { - movingSet.forEach(function(n) { - if (n.n.type !== 'group') { - allNodes.add(n.n); - } - }); - } - var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); - if (selectedGroups.length > 0) { - if (selectedGroups.length === 1 && selectedGroups[0].active) { - // Let nodes be nodes - } else { - selectedGroups.forEach(function(g) { - var groupNodes = RED.group.getNodes(g,true); - groupNodes.forEach(function(n) { - allNodes.delete(n); - }); - allNodes.add(g); - }); - } - } - if (allNodes.size > 0) { - selection.nodes = Array.from(allNodes); - } - if (selectedLinks.length() > 0) { - selection.links = selectedLinks.toArray(); - selection.link = selection.links[0]; - } - return selection; - } - - /** - * Create a node from a type string. - * **NOTE:** Can throw on error - use `try` `catch` block when calling - * @param {string} type The node type to create - * @param {number} [x] (optional) The horizontal position on the workspace - * @param {number} [y] (optional)The vertical on the workspace - * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace. - * @returns An object containing the `node` and a `historyEvent` - * @private - */ - function createNode(type, x, y, z) { - var m = /^subflow:(.+)$/.exec(type); - var activeSubflow = z ? RED.nodes.subflow(z) : null; - if (activeSubflow && m) { - var subflowId = m[1]; - if (subflowId === activeSubflow.id) { - throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) - } - if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { - throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) - } - } - - var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() }; - - nn.type = type; - nn._def = RED.nodes.getType(nn.type); - - if (!m) { - nn.inputs = nn._def.inputs || 0; - nn.outputs = nn._def.outputs; - - for (var d in nn._def.defaults) { - if (nn._def.defaults.hasOwnProperty(d)) { - if (nn._def.defaults[d].value !== undefined) { - nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); - } - } - } - - if (nn._def.onadd) { - try { - nn._def.onadd.call(nn); - } catch (err) { - console.log("Definition error: " + nn.type + ".onadd:", err); - } - } - } else { - var subflow = RED.nodes.subflow(m[1]); - nn.name = ""; - nn.inputs = subflow.in.length; - nn.outputs = subflow.out.length; - } - - nn.changed = true; - nn.moved = true; - - nn.w = RED.view.node_width; - nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15); - nn.resize = true; - if (x != null && typeof x == "number" && x >= 0) { - nn.x = x; - } - if (y != null && typeof y == "number" && y >= 0) { - nn.y = y; - } - var historyEvent = { - t: "add", - nodes: [nn.id], - dirty: RED.nodes.dirty() - } - if (activeSubflow) { - var subflowRefresh = RED.subflow.refresh(true); - if (subflowRefresh) { - historyEvent.subflow = { - id: activeSubflow.id, - changed: activeSubflow.changed, - instances: subflowRefresh.instances - } - } - } - return { - node: nn, - historyEvent: historyEvent - } - } - - function calculateNodeDimensions(node) { - var result = [node_width,node_height]; - try { - var isLink = (node.type === "link in" || node.type === "link out") - var hideLabel = node.hasOwnProperty('l')?!node.l : isLink; - var label = RED.utils.getNodeLabel(node, node.type); - var labelParts = getLabelParts(label, "red-ui-flow-node-label"); - if (hideLabel) { - result[1] = Math.max(node_height,(node.outputs || 0) * 15); - } else { - result[1] = Math.max(6+24*labelParts.lines.length,(node.outputs || 0) * 15, 30); - } - if (hideLabel) { - result[0] = node_height; - } else { - result[0] = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(node._def.inputs>0?7:0))/20)) ); - } - }catch(err) { - console.log("Error",node); - } - return result; - } - - - function flashNode(n) { - let node = n; - if(typeof node === "string") { node = RED.nodes.node(n); } - if(!node) { return; } - - const flashingNode = flashingNodeId && RED.nodes.node(flashingNodeId); - if(flashingNode) { - //cancel current flashing node before flashing new node - clearInterval(flashingNode.__flashTimer); - delete flashingNode.__flashTimer; - flashingNode.dirty = true; - flashingNode.highlighted = false; - } - node.__flashTimer = setInterval(function(flashEndTime, n) { - n.dirty = true; - if (flashEndTime >= Date.now()) { - n.highlighted = !n.highlighted; - } else { - clearInterval(n.__flashTimer); - delete n.__flashTimer; - flashingNodeId = null; - n.highlighted = false; - } - RED.view.redraw(); - }, 100, Date.now() + 2200, node) - flashingNodeId = node.id; - node.highlighted = true; - RED.view.redraw(); - } - return { - init: init, - state:function(state) { - if (state == null) { - return mouse_mode - } else { - mouse_mode = state; - } - }, - - updateActive: updateActiveNodes, - redraw: function(updateActive, syncRedraw) { - if (updateActive) { - updateActiveNodes(); - updateSelection(); - } - if (syncRedraw) { - _redraw(); - } else { - redraw(); - } - }, - focus: focusView, - importNodes: importNodes, - calculateTextWidth: calculateTextWidth, - select: function(selection) { - if (typeof selection !== "undefined") { - clearSelection(); - if (typeof selection == "string") { - var selectedNode = RED.nodes.node(selection); - if (selectedNode) { - selectedNode.selected = true; - selectedNode.dirty = true; - movingSet.clear(); - movingSet.add(selectedNode); - } - } else if (selection) { - if (selection.nodes) { - updateActiveNodes(); - movingSet.clear(); - // TODO: this selection group span groups - // - if all in one group -> activate the group - // - if in multiple groups (or group/no-group) - // -> select the first 'set' of things in the same group/no-group - selection.nodes.forEach(function(n) { - if (n.type !== "group") { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } else { - selectGroup(n,true); - } - }) - } - } - } - updateSelection(); - redraw(true); - }, - selection: getSelection, - clearSelection: clearSelection, - createNode: createNode, - /** default node width */ - get node_width() { - return node_width; - }, - /** default node height */ - get node_height() { - return node_height; - }, - /** snap to grid option state */ - get snapGrid() { - return snapGrid; - }, - /** gets the current scale factor */ - scale: function() { - return scaleFactor; - }, - getLinksAtPoint: function(x,y) { - // x,y must be in SVG co-ordinate space - // if they come from a node.x/y, they will need to be scaled using - // scaleFactor first. - var result = []; - var links = outer.selectAll(".red-ui-flow-link-background")[0]; - for (var i=0;i= bb.x && y >= bb.y && x <= bb.x+bb.width && y <= bb.y+bb.height) { - result.push(links[i]) - } - } - return result; - }, - getGroupAtPoint: getGroupAt, - getActiveGroup: function() { return activeGroup }, - reveal: function(id,triggerHighlight) { - if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { - RED.workspaces.show(id, null, null, true); - } else { - var node = RED.nodes.node(id) || RED.nodes.group(id); - if (node) { - if (node.z && (node.type === "group" || node._def.category !== 'config')) { - node.dirty = true; - RED.workspaces.show(node.z); - - var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor]; - var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; - var cx = node.x; - var cy = node.y; - if (node.type === "group") { - cx += node.w/2; - cy += node.h/2; - } - if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) { - var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor); - var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor); - chart.animate({ - scrollLeft: deltaX, - scrollTop: deltaY - },200); - } - if (triggerHighlight !== false) { - flashNode(node); - } - } else if (node._def.category === 'config') { - RED.sidebar.config.show(id); - } - } - } - }, - gridSize: function(v) { - if (v === undefined) { - return gridSize; - } else { - gridSize = Math.max(5,v); - updateGrid(); - } - }, - getActiveNodes: function() { - return activeNodes; - }, - getSubflowPorts: function() { - var result = []; - if (activeSubflow) { - var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); - subflowOutputs.each(function(d,i) { result.push(d) }) - var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); - subflowInputs.each(function(d,i) { result.push(d) }) - var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); - subflowStatus.each(function(d,i) { result.push(d) }) - } - return result; - }, - selectNodes: function(options) { - $("#red-ui-workspace-tabs-shade").show(); - $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); - $("#red-ui-header-shade").show(); - $("#red-ui-workspace").addClass("red-ui-workspace-select-mode"); - - mouse_mode = RED.state.SELECTING_NODE; - clearSelection(); - if (options.selected) { - options.selected.forEach(function(id) { - var n = RED.nodes.node(id); - if (n) { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } - }) - } - redraw(); - selectNodesOptions = options||{}; - var closeNotification = function() { - clearSelection(); - $("#red-ui-workspace-tabs-shade").hide(); - $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); - $("#red-ui-header-shade").hide(); - $("#red-ui-workspace").removeClass("red-ui-workspace-select-mode"); - resetMouseVars(); - notification.close(); - } - selectNodesOptions.done = function(selection) { - closeNotification(); - if (selectNodesOptions.onselect) { - selectNodesOptions.onselect(selection); - } - } - var buttons = [{ - text: RED._("common.label.cancel"), - click: function(e) { - closeNotification(); - if (selectNodesOptions.oncancel) { - selectNodesOptions.oncancel(); - } - } - }]; - if (!selectNodesOptions.single) { - buttons.push({ - text: RED._("common.label.done"), - class: "primary", - click: function(e) { - var selection = movingSet.nodes() - selectNodesOptions.done(selection); - } - }); - } - var notification = RED.notify(selectNodesOptions.prompt || RED._("workspace.selectNodes"),{ - modal: false, - fixed: true, - type: "compact", - buttons: buttons - }) - }, - scroll: function(x,y) { - chart.scrollLeft(chart.scrollLeft()+x); - chart.scrollTop(chart.scrollTop()+y) - }, - clickNodeButton: function(n) { - if (n._def.button) { - nodeButtonClicked(n); - } - }, - clipboard: function() { - return clipboard - }, - redrawStatus: redrawStatus, - showQuickAddDialog:showQuickAddDialog, - calculateNodeDimensions: calculateNodeDimensions, - getElementPosition:getElementPosition, - showTooltip:showTooltip - }; -})(); From 7af3acde9ee0edf836422857aeea627c4f6073e0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 27 Jun 2022 20:30:14 +0100 Subject: [PATCH 127/195] Ensure 'hidden flow' count doesn't include subflows Fixes #3707 --- .../editor-client/src/js/ui/workspaces.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 673607229..1f5cdf0f1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -284,9 +284,22 @@ RED.workspaces = (function() { onselect: "core:show-last-hidden-flow" } ] - if (hideStack.length > 0) { + let hiddenFlows = new Set() + for (let i = 0; i < hideStack.length; i++) { + let ids = hideStack[i] + if (!Array.isArray(ids)) { + ids = [ids] + } + ids.forEach(id => { + if (RED.nodes.workspace(id)) { + hiddenFlows.add(id) + } + }) + } + const flowCount = hiddenFlows.size; + if (flowCount > 0) { menuItems.unshift({ - label: RED._("workspace.hiddenFlows",{count: hideStack.length}), + label: RED._("workspace.hiddenFlows",{count: flowCount}), onselect: "core:list-hidden-flows" }) } From b9649aed328275186b93b15245f592fc1fae6b63 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Mon, 27 Jun 2022 16:59:56 -0400 Subject: [PATCH 128/195] Fix typo --- .../@node-red/editor-client/src/sass/tab-config.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index c8e44e26e..a8e2e4971 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -44,7 +44,7 @@ ul.red-ui-sidebar-node-config-list { } &.highlighted { border-color: transparent; - outline: dashed var(--red-ui-node-selected-color 4px); + outline: dashed var(--red-ui-node-selected-color) 4px; } } .red-ui-palette-label { From f7e6d7d143228b17dafd73b61d17a595a07cd320 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 28 Jun 2022 09:16:30 +0900 Subject: [PATCH 129/195] fix type selector of subflow environment variable --- .../editor-client/src/js/ui/editors/envVarList.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index 41a528e21..1094cc464 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -41,8 +41,14 @@ RED.editor.envVarList = (function() { style: "width:100%", class: "node-input-env-value", type: "text", - }).attr("autocomplete","disable").appendTo(envRow) - valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED}); + }).attr("autocomplete","disable").appendTo(envRow); + var types = (opt.ui && opt.ui.opts && opt.ui.opts.types); + if (!types) { + types = isTemplateNode + ? DEFAULT_ENV_TYPE_LIST + : DEFAULT_ENV_TYPE_LIST_INC_CRED; + } + valueField.typedInput({default:'str',types:types}); valueField.typedInput('type', opt.type); if (opt.type === "cred") { if (opt.value) { From 53017986544b0f4ffe83ce7cb83d40df755d72e8 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Tue, 28 Jun 2022 08:06:16 +0200 Subject: [PATCH 130/195] Update german translation based on PR review --- .../editor-client/locales/de/editor.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index f47ebf5cf..41fe1459a 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -96,7 +96,7 @@ }, "edit": "Bearbeiten", "settings": "Einstellungen", - "userSettings": "Benutzereinstellungen", + "userSettings": "Einstellungen", "nodes": "Nodes", "displayStatus": "Node-Status anzeigen", "displayConfig": "Konfigurations-Nodes", @@ -105,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Subflow erstellen", + "createSubflow": "Subflow", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -122,7 +122,7 @@ "projects": "Projekte", "projects-new": "Neu", "projects-open": "Öffnen", - "projects-settings": "Projekteinstellungen", + "projects-settings": "Einstellungen", "showNodeLabelDefault": "Zeige Namen von neu hinzugefügten Nodes", "codeEditor": "Code-Editor", "groups": "Gruppen", @@ -192,7 +192,7 @@ "lostConnectionTry": "Jetzt versuchen", "cannotAddSubflowToItself": "Subflow kann nicht zu sich selbst hinzugefügt werden", "cannotAddCircularReference": "Subflow kann nicht hinzugefügt werden, da ein zirkulärer Bezug erkannt wurde", - "unsupportedVersion": "

        Nicht unterstützte Version von Node.js erkannt.

        Es sollte ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

        ", + "unsupportedVersion": "

        Nicht unterstützte Version von Node.js erkannt.

        Es muss ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

        ", "failedToAppendNode": "

        Fehler beim Laden von '__module__'.

        __error__

        " }, "project": { @@ -237,12 +237,12 @@ "replacedNodes_plural": "__count__ Nodes ersetzt", "pasteNodes": "Flow-JSON einfügen oder", "selectFile": "Datei für Import auswählen", - "importNodes": "Importiere Nodes", - "exportNodes": "Exportiere Nodes", + "importNodes": "Import", + "exportNodes": "Export", "download": "Download", - "importUnrecognised": "Nicht erkannter Typ importiert:", + "importUnrecognised": "Nicht erkannten Typ importiert:", "importUnrecognised_plural": "Nicht erkannte Typen importiert:", - "importDuplicate": "Doppelte Node importiert:", + "importDuplicate": "Doppelten Node importiert:", "importDuplicate_plural": "Doppelte Nodes importiert:", "nodesExported": "Nodes in die Zwischenablage exportiert", "nodesImported": "Importiert:", @@ -543,8 +543,8 @@ "advanced": "Fortgeschritten" }, "actions": { - "collapse-all": "Alle Kategorien einklappen", - "expand-all": "Alle Kategorien ausklappen" + "collapse-all": "Kategorien einklappen", + "expand-all": "Kategorien ausklappen" }, "event": { "nodeAdded": "Node zur Palette hinzugefügt:", From b60fd36c6ef67e279beb60a3fa48781dfaf88084 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 28 Jun 2022 10:14:12 +0100 Subject: [PATCH 131/195] Fix CSV node to handle \n when outputting text fields and add tests --- .../@node-red/nodes/core/parsers/70-CSV.js | 7 ++++-- test/nodes/core/parsers/70-CSV_spec.js | 24 +++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 184f40bdd..9c55fa2b6 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -89,6 +89,9 @@ module.exports = function(RED) { else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas" msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; } + else if (msg.payload[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n" + msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; + } } ou += msg.payload[s].join(node.sep) + node.ret; } @@ -112,7 +115,7 @@ module.exports = function(RED) { q = q.replace(/"/g, '""'); ou += node.quo + q + node.quo + node.sep; } - else if (q.indexOf(node.sep) !== -1) { // add quotes if any "commas" + else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n" ou += node.quo + q + node.quo + node.sep; } else { ou += q + node.sep; } // otherwise just add @@ -134,7 +137,7 @@ module.exports = function(RED) { p = p.replace(/"/g, '""'); ou += node.quo + p + node.quo + node.sep; } - else if (p.indexOf(node.sep) !== -1) { // add quotes if any "commas" + else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n" ou += node.quo + p + node.quo + node.sep; } else { ou += p + node.sep; } // otherwise just add diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index cb8d7ca09..93d59a171 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -693,19 +693,19 @@ describe('CSV node', function() { describe('json object to csv', function() { it('should convert a simple object back to a csv', function(done) { - var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] }, + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', '4,foo,true,,0\n'); + msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld"\n'); done(); } catch(e) { done(e); } }); - var testJson = { e:0, d:1, b:"foo", c:true, a:4 }; + var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld" }; n1.emit("input", {payload:testJson}); }); }); @@ -777,7 +777,7 @@ describe('CSV node', function() { } catch(e) { done(e); } }); - var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}]; + var testJson = [{ d:1, b:3, c:2, a:4 },{d:4, a:1, c:3, b:2}]; n1.emit("input", {payload:testJson}); }); }); @@ -790,12 +790,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', 'a,b,c,d\n4,3,2,1\n1,2,3,4\n'); + msg.should.have.property('payload', 'a,b,c,d\n4,3,2,1\n1,2,3,"a\nb"\n'); done(); } catch(e) { done(e); } }); - var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}]; + var testJson = [{ d:1, b:3, c:2, a:4 },{d:"a\nb", a:1, c:3, b:2}]; n1.emit("input", {payload:testJson}); }); }); @@ -826,12 +826,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,2,3,1\n'); + msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n'); done(); } catch(e) { done(e); } }); - var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}]; + var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:"f\ng"}]; n1.emit("input", {payload:testJson}); }); }); @@ -844,12 +844,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', ',0,1,foo,"ba""r","di,ng"\n'); + msg.should.have.property('payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n'); done(); } catch(e) { done(e); } }); - var testJson = ["",0,1,"foo",'ba"r','di,ng']; + var testJson = ["",0,1,"foo",'ba"r','di,ng',"fa\nba"]; n1.emit("input", {payload:testJson}); }); }); @@ -898,12 +898,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', 'col1,col2,col3,col4\r\nH1,H2,H3,H4\r\nA,B,,\r\nA,,C,\r\nA,,,D\r\n'); + msg.should.have.property('payload', 'col1,col2,col3,col4\r\nH1,H2,H3,H4\r\nA,B,,\r\nA,,C,\r\nA,,,"D\nE"\r\n'); done(); } catch(e) { done(e); } }); - var testJson = [{"col1":"H1","col2":"H2","col3":"H3","col4":"H4"},{"col1":"A","col2":"B"},{"col1":"A","col3":"C"},{"col1":"A","col4":"D"}]; + var testJson = [{"col1":"H1","col2":"H2","col3":"H3","col4":"H4"},{"col1":"A","col2":"B"},{"col1":"A","col3":"C"},{"col1":"A","col4":"D\nE"}]; n1.emit("input", {payload:testJson}); }); }); From a7e3548f22d2c64229363b3203d980606173b300 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 28 Jun 2022 11:54:51 +0100 Subject: [PATCH 132/195] List welcome tours in help sidebar under node-red section --- .../editor-client/locales/en-US/editor.json | 5 ++-- .../editor-client/src/js/ui/tab-help.js | 28 ++++++++++--------- .../editor-client/src/js/ui/tour/tourGuide.js | 9 ++---- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index e6fe4a4af..7ecd6f640 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -685,7 +685,8 @@ "showHelp": "Show help", "showInOutline": "Show in outline", "showTopics": "Show topics", - "noHelp": "No help topic selected" + "noHelp": "No help topic selected", + "changeLog": "Change Log" }, "config": { "name": "Configuration nodes", @@ -1159,7 +1160,7 @@ "takeATour": "Take a tour", "start": "Start", "next": "Next", - "tours": "Tours" + "welcomeTours": "Welcome Tours" }, "diagnostics": { "title": "System Info" diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index 09a67e338..e5199b5bc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -20,10 +20,8 @@ RED.sidebar.help = (function() { var helpSection; var panels; var panelRatio; - var helpTopics = []; var treeList; var tocPanel; - var helpIndex = {}; function resizeStack() { var h = $(content).parent().height() - toolbar.outerHeight(); @@ -192,7 +190,6 @@ RED.sidebar.help = (function() { } function refreshHelpIndex() { - helpTopics = []; var modules = RED.nodes.registry.getModuleList(); var moduleNames = Object.keys(modules); moduleNames.sort(); @@ -204,23 +201,28 @@ RED.sidebar.help = (function() { }; var tours = RED.tourGuide.list().map(function (item) { return { - icon: "fa fa-repeat", + icon: "fa fa-play-circle-o", label: item.label, tour: item.path, }; }); var helpData = [ { - id: 'changelog', - label: "Node-RED v"+RED.settings.version, - content: getChangelog - }, - nodeHelp, - { - id: "tours", - label: RED._("tourGuide.tours"), - children: tours + label: "Node-RED", + children: [ + { + id: 'changelog', + label: RED._("sidebar.help.changeLog"), + content: getChangelog + }, + { + label: RED._("tourGuide.welcomeTours"), + children: tours + } + + ] }, + nodeHelp ]; var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)}); if (subflows.length > 0) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 49b960402..adf7f4212 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -437,7 +437,7 @@ RED.tourGuide = (function() { return [ { id: "2_3", - label: "3.0.0-beta.3 (latest)", + label: "3.0.0-beta.3", path: "./tours/welcome.js" }, { @@ -449,12 +449,7 @@ RED.tourGuide = (function() { id: "2_1", label: "2.1.0", path: "./tours/2.1/welcome.js" - }, - { - id: "first_flow", - label: "First flow", - path: "./tours/first-flow.js" - }, + } ]; } From 3578f1b254d38ff1a225757ccfbc809a9ead8e7f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 28 Jun 2022 20:35:39 +0900 Subject: [PATCH 133/195] remove line break to prevent jshint error --- .../@node-red/editor-client/src/js/ui/editors/envVarList.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index 1094cc464..209e953e0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -44,9 +44,7 @@ RED.editor.envVarList = (function() { }).attr("autocomplete","disable").appendTo(envRow); var types = (opt.ui && opt.ui.opts && opt.ui.opts.types); if (!types) { - types = isTemplateNode - ? DEFAULT_ENV_TYPE_LIST - : DEFAULT_ENV_TYPE_LIST_INC_CRED; + types = isTemplateNode ? DEFAULT_ENV_TYPE_LIST : DEFAULT_ENV_TYPE_LIST_INC_CRED; } valueField.typedInput({default:'str',types:types}); valueField.typedInput('type', opt.type); From c120dffbf07fbc49b642d6d0cdf9ac10d2503945 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Tue, 28 Jun 2022 16:22:24 +0100 Subject: [PATCH 134/195] Add option flag `reimport` to `importNodes` fixes #3699 --- .../@node-red/editor-client/src/js/nodes.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 052fad558..45804c29e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1654,21 +1654,19 @@ RED.nodes = (function() { * Options: * - generateIds - whether to replace all node ids * - addFlow - whether to import nodes to a new tab - * - importToCurrent + * - reimport - if node has a .z property, dont overwrite it + * Only applicible when `generateIds` is false * - importMap - how to resolve any conflicts. * - id:import - import as-is * - id:copy - import with new id * - id:replace - import over the top of existing */ function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { - options = options || { - generateIds: false, - addFlow: false, - } - options.importMap = options.importMap || {}; - - var createNewIds = options.generateIds; - var createMissingWorkspace = options.addFlow; + const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} } + options = Object.assign({}, defOpts, options) + const createNewIds = options.generateIds; + const reimport = (!createNewIds && !!options.reimport) + const createMissingWorkspace = options.addFlow; var i; var n; var newNodes; @@ -1969,7 +1967,8 @@ RED.nodes = (function() { } } } else { - if (n.z && !workspace_map[n.z] && !subflow_map[n.z]) { + const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z) + if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) { n.z = activeWorkspace; } } @@ -2070,7 +2069,8 @@ RED.nodes = (function() { node.id = getID(); } else { node.id = n.id; - if (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z])) { + const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z) + if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) { if (createMissingWorkspace) { if (missingWorkspace === null) { missingWorkspace = RED.workspaces.add(null,true); @@ -2769,7 +2769,7 @@ RED.nodes = (function() { // Force the redraw to be synchronous so the view updates // *now* and removes the unknown node RED.view.redraw(true, true); - var result = importNodes(reimportList,{generateIds:false}); + var result = importNodes(reimportList,{generateIds:false, reimport: true}); var newNodeMap = {}; result.nodes.forEach(function(n) { newNodeMap[n.id] = n; From 7b06d4273aaa4c4338e1a369ec4a4b93cb4cd7ba Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 28 Jun 2022 16:22:31 -0400 Subject: [PATCH 135/195] Use the correct variable for alpha values --- .../@node-red/editor-client/src/sass/colors.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index 66b3bd48e..b311b06b5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -112,13 +112,13 @@ $tab-text-color-disabled-inactive: $secondary-text-color-disabled-inactive; $tab-badge-color: $tertiary-text-color; $tab-background: $secondary-background; $tab-background-active: $secondary-background; -$tab-background-active-alpha: rgba($secondary-background, 0.001); +$tab-background-active-alpha: rgba($tab-background-active, 0.001); $tab-background-selected: $secondary-background-selected; -$tab-background-selected-alpha: rgba($secondary-background-selected, 0.001); +$tab-background-selected-alpha: rgba($tab-background-selected, 0.001); $tab-background-inactive: $secondary-background-inactive; -$tab-background-inactive-alpha: rgba($secondary-background-inactive, 0.001); +$tab-background-inactive-alpha: rgba($tab-background-inactive, 0.001); $tab-background-hover: $secondary-background-hover; -$tab-background-hover-alpha: rgba($secondary-background-hover, 0.001); +$tab-background-hover-alpha: rgba($tab-background-hover, 0.001); $palette-header-background: $primary-background; $palette-header-color: $header-text-color; From f33848e16b6fa0ebaf0432e1dbd7fa3d1cded965 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 10:27:44 +0100 Subject: [PATCH 136/195] Rework start/stop api to use runtime-event notification message --- .../@node-red/editor-api/lib/admin/flows.js | 2 +- .../editor-client/locales/en-US/editor.json | 10 +- .../@node-red/editor-client/src/js/nodes.js | 2 +- .../@node-red/editor-client/src/js/red.js | 7 +- .../@node-red/editor-client/src/js/runtime.js | 67 +++++-------- .../editor-client/src/js/ui/deploy.js | 23 ++--- .../@node-red/editor-client/src/js/ui/view.js | 2 +- .../@node-red/runtime/lib/api/flows.js | 31 +++--- .../@node-red/runtime/lib/flows/index.js | 96 ++++++++++--------- .../@node-red/runtime/lib/index.js | 6 +- .../runtime/locales/en-US/runtime.json | 1 + 11 files changed, 107 insertions(+), 140 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js index 2ad233f8f..4d8679aac 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js @@ -83,7 +83,7 @@ module.exports = { postState: function(req,res) { const opts = { user: req.user, - requestedState: req.body.state||"", + state: req.body.state || "", req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.setState(opts).then(function(result) { diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index b25885e21..ab2fba982 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -169,6 +169,10 @@ } }, "notification": { + "state": { + "flowsStopped": "Flows stopped", + "flowsStarted": "Flows started" + }, "warning": "Warning: __message__", "warnings": { "undeployedChanges": "node has undeployed changes", @@ -291,12 +295,6 @@ "stopstart":{ "status": { "state_changed": "Flows runtime has been changed to '__state__' state" - }, - "errors": { - "notAllowed": "Method not allowed", - "notAuthorized": "Not authorized", - "notFound": "Not found", - "noResponse": "No response from server" } }, "deploy": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 052fad558..b390dab53 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -14,7 +14,7 @@ * limitations under the License. **/ -/** +/** * An Interface to nodes and utility functions for creating/adding/deleting nodes and links * @namespace RED.nodes */ diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 939c32f2e..55446418b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -336,7 +336,6 @@ var RED = (function() { id: notificationId } if (notificationId === "runtime-state") { - RED.events.emit("runtime-state",msg); if (msg.error === "safe-mode") { options.buttons = [ { @@ -477,9 +476,9 @@ var RED = (function() { } else if (persistentNotifications.hasOwnProperty(notificationId)) { persistentNotifications[notificationId].close(); delete persistentNotifications[notificationId]; - if (notificationId === 'runtime-state') { - RED.events.emit("runtime-state",msg); - } + } + if (notificationId === 'runtime-state') { + RED.events.emit("runtime-state",msg); } }); RED.comms.subscribe("status/#",function(topic,msg) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/runtime.js b/packages/node_modules/@node-red/editor-client/src/js/runtime.js index 49960e382..eac985467 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/runtime.js +++ b/packages/node_modules/@node-red/editor-client/src/js/runtime.js @@ -1,53 +1,36 @@ RED.runtime = (function() { let state = "" - let settings = {ui: false, enabled: false}; - const STOPPED = "stopped" - const STARTED = "started" + let settings = { ui: false, enabled: false }; + const STOPPED = "stop" + const STARTED = "start" + const SAFE = "safe" + return { init: function() { // refresh the current runtime status from server settings = Object.assign({}, settings, RED.settings.runtimeState); - RED.runtime.requestState() - - // {id:"flows-run-state", started: false, state: "stopped", retain:true} - RED.comms.subscribe("notification/flows-run-state",function(topic,msg) { - RED.events.emit("flows-run-state",msg); - RED.runtime.updateState(msg.state); - }); - }, - get state() { - return state - }, - get started() { - return state === STARTED - }, - get states() { - return { STOPPED, STARTED } - }, - updateState: function(newState) { - state = newState; - // disable pointer events on node buttons (e.g. inject/debug nodes) - $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state === STOPPED) - // show/hide Start/Stop based on current state - if(settings.enabled === true && settings.ui === true) { - RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) - RED.menu.setVisible("deploymenu-item-runtime-start", state === STOPPED) - } - }, - requestState: function(callback) { - $.ajax({ - headers: { - "Accept":"application/json" - }, - cache: false, - url: 'flows/state', - success: function(data) { - RED.runtime.updateState(data.state) - if(callback) { - callback(data.state) + RED.events.on("runtime-state", function(msg) { + if (msg.state) { + const currentState = state + state = msg.state + $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state !== STARTED) + if(settings.enabled === true && settings.ui === true) { + RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) + RED.menu.setVisible("deploymenu-item-runtime-start", state !== STARTED) + } + // Do not notify the user about this event if: + // - This is the very first event we've received after loading the editor (currentState = '') + // - The state matches what we already thought was the case (state === currentState) + // - The event was triggered by a deploy (msg.deploy === true) + // - The event is a safe mode event - that gets notified separately + if (currentState !== '' && state !== currentState && !msg.deploy && state !== SAFE) { + RED.notify(RED._("notification.state.flows"+(state === STOPPED?'Stopped':'Started'), msg), "success") } } }); + }, + get started() { + return state === STARTED } } -})() \ No newline at end of file +})() diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 46408cb6c..809202c99 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -69,7 +69,7 @@ RED.deploy = (function() { {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, null ] - if(RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { + if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) } @@ -302,7 +302,6 @@ RED.deploy = (function() { deployInflight = true deployButtonSetBusy() shadeShow() - RED.runtime.updateState(state) $.ajax({ url:"flows/state", type: "POST", @@ -311,30 +310,23 @@ RED.deploy = (function() { if (deployWasEnabled) { $("#red-ui-header-button-deploy").removeClass("disabled") } - RED.runtime.updateState((data && data.state) || "unknown") - RED.notify(RED._("stopstart.status.state_changed", data), "success") }).fail(function(xhr,textStatus,err) { if (deployWasEnabled) { $("#red-ui-header-button-deploy").removeClass("disabled") } if (xhr.status === 401) { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAuthorized") }), "error") - } else if (xhr.status === 404) { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notFound") }), "error") - } else if (xhr.status === 405) { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAllowed") }), "error") + RED.notify(RED._("notification.error", { message: RED._("user.notAuthorized") }), "error") } else if (xhr.responseText) { const errorDetail = { message: err ? (err + "") : "" } try { errorDetail.message = JSON.parse(xhr.responseText).message - } finally { + } finally { errorDetail.message = errorDetail.message || xhr.responseText } RED.notify(RED._("notification.error", errorDetail), "error") } else { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.noResponse") }), "error") + RED.notify(RED._("notification.error", { message: RED._("deploy.errors.noResponse") }), "error") } - RED.runtime.requestState() }).always(function() { const delta = Math.max(0, 300 - (Date.now() - startTime)) setTimeout(function () { @@ -514,13 +506,10 @@ RED.deploy = (function() { deployButtonSetBusy(); const data = { flows: nns }; - data.runtimeState = RED.runtime.state; - if (data.runtimeState === RED.runtime.states.STOPPED || force) { - data._rev = RED.nodes.version(); - } else { + if (!force) { data.rev = RED.nodes.version(); } - + deployInflight = true; shadeShow(); $.ajax({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index eca5c01ef..02b8df5d8 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4877,7 +4877,7 @@ RED.view = (function() { if (d._def.button) { var buttonEnabled = isButtonEnabled(d); this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { + if (RED.runtime && Object.hasOwn(RED.runtime,'started')) { this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 2e71cbdca..b91635201 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -73,10 +73,6 @@ var api = module.exports = { if (deploymentType === 'reload') { apiPromise = runtime.flows.loadFlows(true); } else { - //ensure the runtime running/stopped state matches the deploying editor. If not, then copy the _rev number to flows.rev - if(flows.hasOwnProperty('_rev') && !flows.hasOwnProperty('rev') && (flows.runtimeState !== "stopped" || runtime.flows.started)) { - flows.rev = flows._rev - } if (flows.hasOwnProperty('rev')) { var currentVersion = runtime.flows.getFlows().rev; if (currentVersion !== flows.rev) { @@ -271,9 +267,7 @@ var api = module.exports = { getState: async function(opts) { runtime.log.audit({event: "flows.getState"}, opts.req); const result = { - state: runtime.flows.started ? "started" : "stopped", - started: !!runtime.flows.started, - rev: runtime.flows.getFlows().rev + state: runtime.flows.state() } return result; }, @@ -282,7 +276,7 @@ var api = module.exports = { * @param {Object} opts * @param {Object} opts.req - the request to log (optional) * @param {User} opts.user - the user calling the api - * @param {string} opts.requestedState - the requested state. Valid values are "start" and "stop". + * @param {string} opts.state - the requested state. Valid values are "start" and "stop". * @return {Promise} - the active flow configuration * @memberof @node-red/runtime_flows */ @@ -295,7 +289,7 @@ var api = module.exports = { err.code = err.code || errcode || "unexpected_error" runtime.log.audit({ event: "flows.setState", - state: opts.requestedState || "", + state: opts.state || "", error: errcode || "unexpected_error", message: err.code }, opts.req); @@ -304,21 +298,22 @@ var api = module.exports = { const getState = () => { return { - state: runtime.flows.started ? "started" : "stopped", - started: !!runtime.flows.started, - rev: runtime.flows.getFlows().rev, + state: runtime.flows.state() } } if(!runtime.settings.runtimeState || runtime.settings.runtimeState.enabled !== true) { throw (makeError("Method Not Allowed", "not_allowed", 405)) } - switch (opts.requestedState) { + switch (opts.state) { case "start": try { try { - runtime.settings.set('flowsRunStateRequested', opts.requestedState); - } catch(err) { } + runtime.settings.set('runtimeFlowState', opts.state); + } catch(err) {} + if (runtime.settings.safeMode) { + delete runtime.settings.safeMode + } await runtime.flows.startFlows("full") return getState() } catch (err) { @@ -327,15 +322,15 @@ var api = module.exports = { case "stop": try { try { - runtime.settings.set('flowsRunStateRequested', opts.requestedState); - } catch(err) { } + runtime.settings.set('runtimeFlowState', opts.state); + } catch(err) {} await runtime.flows.stopFlows("full") return getState() } catch (err) { throw (makeError(err, err.code, 500)) } default: - throw (makeError(`Cannot change flows runtime state to '${opts.requestedState}'}`, "invalid_run_state", 400)) + throw (makeError(`Cannot change flows runtime state to '${opts.state}'}`, "invalid_run_state", 400)) } }, } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index b707b4aaf..b2de8ea73 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -36,6 +36,8 @@ var activeFlowConfig = null; var activeFlows = {}; var started = false; +var state = 'stop' + var credentialsPendingReset = false; var activeNodesToFlow = {}; @@ -50,6 +52,7 @@ function init(runtime) { storage = runtime.storage; log = runtime.log; started = false; + state = 'stop'; if (!typeEventRegistered) { events.on('type-registered',function(type) { if (activeFlowConfig && activeFlowConfig.missingTypes.length > 0) { @@ -214,19 +217,26 @@ function setFlows(_config,_credentials,type,muteLog,forceStart,user) { // Flows are running (or should be) // Stop the active flows (according to deploy type and the diff) - return stop(type,diff,muteLog).then(() => { + return stop(type,diff,muteLog,true).then(() => { // Once stopped, allow context to remove anything no longer needed return context.clean(activeFlowConfig) }).then(() => { + if (!isLoad) { + log.info(log._("nodes.flows.updated-flows")); + } // Start the active flows - start(type,diff,muteLog).then(() => { + start(type,diff,muteLog,true).then(() => { events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); }); // Return the new revision asynchronously to the actual start return flowRevision; }).catch(function(err) { }) } else { + if (!isLoad) { + log.info(log._("nodes.flows.updated-flows")); + } events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + return flowRevision; } }); } @@ -259,10 +269,10 @@ function getFlows() { return activeConfig; } -async function start(type,diff,muteLog) { - type = type||"full"; - let reallyStarted = started +async function start(type,diff,muteLog,isDeploy) { + type = type || "full"; started = true; + state = 'start' var i; // If there are missing types, report them, emit the necessary runtime event and return if (activeFlowConfig.missingTypes.length > 0) { @@ -284,7 +294,7 @@ async function start(type,diff,muteLog) { log.info(log._("nodes.flows.missing-type-install-2")); log.info(" "+settings.userDir); } - events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true}); + events.emit("runtime-event",{id:"runtime-state",payload:{state: 'stop', error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true}); return; } @@ -298,7 +308,7 @@ async function start(type,diff,muteLog) { missingModules.push({module:err[i].module.module, error: err[i].error.code || err[i].error.toString()}) log.info(` - ${err[i].module.spec} [${err[i].error.code || "unknown_error"}]`); } - events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-modules", type:"warning",text:"notification.warnings.missing-modules",modules:missingModules},retain:true}); + events.emit("runtime-event",{id:"runtime-state",payload:{state: 'stop', error:"missing-modules", type:"warning",text:"notification.warnings.missing-modules",modules:missingModules},retain:true}); return; } @@ -307,10 +317,20 @@ async function start(type,diff,muteLog) { log.info("*****************************************************************") log.info(log._("nodes.flows.safe-mode")); log.info("*****************************************************************") - events.emit("runtime-event",{id:"runtime-state",payload:{error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true}); + state = 'safe' + events.emit("runtime-event",{id:"runtime-state",payload:{state: 'safe', error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true}); return; } + const runtimeState = settings.get('runtimeFlowState') || 'start' + if (runtimeState === 'stop') { + log.info(log._("nodes.flows.stopped-flows")); + events.emit("runtime-event",{id:"runtime-state",payload:{ state: 'stop', deploy:isDeploy },retain:true}); + state = 'stop' + started = false + return + } + if (!muteLog) { if (type !== "full") { log.info(log._("nodes.flows.starting-modified-"+type)); @@ -365,51 +385,31 @@ async function start(type,diff,muteLog) { } } } - // Having created or updated all flows, now start them. - let startFlows = true - try { - startFlows = settings.get('flowsRunStateRequested'); - } catch(err) { - } - startFlows = (startFlows !== "stop"); - - if (startFlows) { - for (id in activeFlows) { - if (activeFlows.hasOwnProperty(id)) { - try { - activeFlows[id].start(diff); - // Create a map of node id to flow id and also a subflowInstance lookup map - var activeNodes = activeFlows[id].getActiveNodes(); - Object.keys(activeNodes).forEach(function(nid) { - activeNodesToFlow[nid] = id; - }); - } catch(err) { - console.log(err.stack); - } + for (id in activeFlows) { + if (activeFlows.hasOwnProperty(id)) { + try { + activeFlows[id].start(diff); + // Create a map of node id to flow id and also a subflowInstance lookup map + var activeNodes = activeFlows[id].getActiveNodes(); + Object.keys(activeNodes).forEach(function(nid) { + activeNodesToFlow[nid] = id; + }); + } catch(err) { + console.log(err.stack); } } - reallyStarted = true; - events.emit("flows:started", {config: activeConfig, type: type, diff: diff}); - // Deprecated event - events.emit("nodes-started"); - } else { - started = false; } - - const state = { - started: reallyStarted, - state: reallyStarted ? "started" : "stopped", - } - events.emit("runtime-event",{id:"flows-run-state", payload: state, retain:true}); - + events.emit("flows:started", {config: activeConfig, type: type, diff: diff}); + // Deprecated event + events.emit("nodes-started"); if (credentialsPendingReset === true) { credentialsPendingReset = false; } else { - events.emit("runtime-event",{id:"runtime-state",retain:true}); + events.emit("runtime-event",{id:"runtime-state", payload:{ state: 'start', deploy:isDeploy}, retain:true}); } - if (!muteLog && reallyStarted) { + if (!muteLog) { if (type !== "full") { log.info(log._("nodes.flows.started-modified-"+type)); } else { @@ -419,7 +419,7 @@ async function start(type,diff,muteLog) { return; } -function stop(type,diff,muteLog) { +function stop(type,diff,muteLog,isDeploy) { if (!started) { return Promise.resolve(); } @@ -439,6 +439,7 @@ function stop(type,diff,muteLog) { } } started = false; + state = 'stop' var promises = []; var stopList; var removedList = diff.removed; @@ -490,7 +491,8 @@ function stop(type,diff,muteLog) { } } events.emit("flows:stopped",{config: activeConfig, type: type, diff: diff}); - events.emit("runtime-event",{id:"flows-run-state", payload: {started: false, state: "stopped"}, retain:true}); + + events.emit("runtime-event",{ id:"runtime-state", payload:{ state: 'stop', deploy:isDeploy }, retain:true }); // Deprecated event events.emit("nodes-stopped"); }); @@ -810,7 +812,7 @@ module.exports = { stopFlows: stop, get started() { return started }, - + state: () => { return state }, // handleError: handleError, // handleStatus: handleStatus, diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 8e1d2b487..88b3b8293 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -215,7 +215,7 @@ function start() { } } return redNodes.loadContextsPlugin().then(function () { - redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {}); + redNodes.loadFlows().then(() => { redNodes.startFlows() }).catch(function(err) {}); started = true; }); }); @@ -399,12 +399,12 @@ module.exports = { * @memberof @node-red/runtime */ version: externalAPI.version, - + /** * @memberof @node-red/diagnostics */ diagnostics:externalAPI.diagnostics, - + storage: storage, events: events, hooks: hooks, diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index 3b46d0c9f..ecd010abb 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -122,6 +122,7 @@ "stopped-flows": "Stopped flows", "stopped": "Stopped", "stopping-error": "Error stopping node: __message__", + "updated-flows": "Updated flows", "added-flow": "Adding flow: __label__", "updated-flow": "Updated flow: __label__", "removed-flow": "Removed flow: __label__", From 7580f7491a02aba9c9f60076fdb68a811af93dd5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 10:45:06 +0100 Subject: [PATCH 137/195] Update flow state tests to match changes in api --- .../@node-red/runtime/lib/api/flows_spec.js | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js index 0f560e3f8..deed6a9b6 100644 --- a/test/unit/@node-red/runtime/lib/api/flows_spec.js +++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js @@ -431,7 +431,7 @@ describe("runtime-api/flows", function() { var startFlows, stopFlows, runtime; beforeEach(function() { let flowsStarted = true; - let flowsState = "started"; + let flowsState = "start"; startFlows = sinon.spy(function(type) { if (type !== "full") { var err = new Error(); @@ -442,7 +442,7 @@ describe("runtime-api/flows", function() { return p; } flowsStarted = true; - flowsState = "started"; + flowsState = "start"; return Promise.resolve(); }); stopFlows = sinon.spy(function(type) { @@ -455,7 +455,7 @@ describe("runtime-api/flows", function() { return p; } flowsStarted = false; - flowsState = "stopped"; + flowsState = "stop"; return Promise.resolve(); }); runtime = { @@ -473,6 +473,7 @@ describe("runtime-api/flows", function() { startFlows, stopFlows, getFlows: function() { return {rev:"currentRev",flows:[]} }, + state: function() { return flowsState} } } }) @@ -480,29 +481,25 @@ describe("runtime-api/flows", function() { it("gets flows run state", async function() { flows.init(runtime); const state = await flows.getState({}) - state.should.have.property("started", true) - state.should.have.property("state", "started") + state.should.have.property("state", "start") }); it("permits getting flows run state when setting disabled", async function() { runtime.settings.runtimeState.enabled = false; flows.init(runtime); const state = await flows.getState({}) - state.should.have.property("started", true) - state.should.have.property("state", "started") + state.should.have.property("state", "start") }); it("start flows", async function() { flows.init(runtime); - const state = await flows.setState({requestedState:"start"}) - state.should.have.property("started", true) - state.should.have.property("state", "started") + const state = await flows.setState({state:"start"}) + state.should.have.property("state", "start") stopFlows.called.should.not.be.true(); startFlows.called.should.be.true(); }); it("stop flows", async function() { flows.init(runtime); - const state = await flows.setState({requestedState:"stop"}) - state.should.have.property("started", false) - state.should.have.property("state", "stopped") + const state = await flows.setState({state:"stop"}) + state.should.have.property("state", "stop") stopFlows.called.should.be.true(); startFlows.called.should.not.be.true(); }); @@ -511,7 +508,7 @@ describe("runtime-api/flows", function() { runtime.settings.runtimeState.enabled = false; flows.init(runtime); try { - await flows.setState({requestedState:"start"}) + await flows.setState({state:"start"}) } catch (error) { err = error } @@ -525,7 +522,7 @@ describe("runtime-api/flows", function() { runtime.settings.runtimeState.enabled = false; flows.init(runtime); try { - await flows.setState({requestedState:"stop"}) + await flows.setState({state:"stop"}) } catch (error) { err = error } @@ -538,7 +535,7 @@ describe("runtime-api/flows", function() { let err; flows.init(runtime); try { - await flows.setState({requestedState:"bad-state"}) + await flows.setState({state:"bad-state"}) } catch (error) { err = error } From b59a3b15f361283d97ddd14bba27fac3841aece1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 11:41:19 +0100 Subject: [PATCH 138/195] Fix flow unit tests --- .../@node-red/runtime/lib/flows/index.js | 5 +- .../@node-red/runtime/lib/flows/index_spec.js | 66 ++----------------- 2 files changed, 11 insertions(+), 60 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index b2de8ea73..1b5476a3f 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -322,7 +322,10 @@ async function start(type,diff,muteLog,isDeploy) { return; } - const runtimeState = settings.get('runtimeFlowState') || 'start' + let runtimeState + try { + runtimeState = settings.get('runtimeFlowState') || 'start' + } catch (err) {} if (runtimeState === 'stop') { log.info(log._("nodes.flows.stopped-flows")); events.emit("runtime-event",{id:"runtime-state",payload:{ state: 'stop', deploy:isDeploy },retain:true}); diff --git a/test/unit/@node-red/runtime/lib/flows/index_spec.js b/test/unit/@node-red/runtime/lib/flows/index_spec.js index 5e8f8a46e..1a0f2a73c 100644 --- a/test/unit/@node-red/runtime/lib/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/index_spec.js @@ -321,59 +321,6 @@ describe('flows/index', function() { return flows.startFlows(); }); }); - it('emits runtime-event "flows-run-state" "started"', async function () { - var originalConfig = [ - { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, - { id: "t1", type: "tab" } - ]; - storage.getFlows = function () { - return Promise.resolve({ flows: originalConfig }); - } - let receivedEvent = null; - const handleEvent = (data) => { - if(data && data.id === 'flows-run-state') { - receivedEvent = data; - } - } - events.on('runtime-event', handleEvent); - flows.init({ log: mockLog, settings: {}, storage: storage }); - await flows.load() - await flows.startFlows() - events.removeListener("runtime-event", handleEvent); - - //{id:"flows-run-state", payload: {started: true, state: "started"} - should(receivedEvent).not.be.null() - receivedEvent.should.have.property("id", "flows-run-state") - receivedEvent.should.have.property("payload", { started: true, state: "started" }) - receivedEvent.should.have.property("retain", true) - }); - it('emits runtime-event "flows-run-state" "stopped"', async function () { - const originalConfig = [ - { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, - { id: "t1", type: "tab" } - ]; - storage.getFlows = function () { - return Promise.resolve({ flows: originalConfig }); - } - let receivedEvent = null; - const handleEvent = (data) => { - if(data && data.id === 'flows-run-state') { - receivedEvent = data; - } - } - events.on('runtime-event', handleEvent); - flows.init({ log: mockLog, settings: {}, storage: storage }); - await flows.load() - await flows.startFlows() - await flows.stopFlows() - events.removeListener("runtime-event", handleEvent); - - //{id:"flows-run-state", payload: {started: true, state: "started"} - should(receivedEvent).not.be.null() - receivedEvent.should.have.property("id", "flows-run-state") - receivedEvent.should.have.property("payload", { started: false, state: "stopped" }) - receivedEvent.should.have.property("retain", true) - }); it('does not start if nodes missing', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -449,12 +396,13 @@ describe('flows/index', function() { try { flowCreate.called.should.be.false(); receivedEvent.should.have.property('id','runtime-state'); - receivedEvent.should.have.property('payload', - { error: 'missing-modules', - type: 'warning', - text: 'notification.warnings.missing-modules', - modules: [] } - ); + receivedEvent.should.have.property('payload', { + state: 'stop', + error: 'missing-modules', + type: 'warning', + text: 'notification.warnings.missing-modules', + modules: [] + }); done(); }catch(err) { From b7bdcc4e57d8f8eca619a63d527578768dcb1b03 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:37:53 +0100 Subject: [PATCH 139/195] Update packages/node_modules/@node-red/editor-client/locales/en-US/editor.json Remove unused i18n entries (these are safe to remove - were added in the original PR) --- .../@node-red/editor-client/locales/en-US/editor.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index ab2fba982..d09e6e977 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -292,11 +292,6 @@ "copyMessageValue": "Value copied", "copyMessageValue_truncated": "Truncated value copied" }, - "stopstart":{ - "status": { - "state_changed": "Flows runtime has been changed to '__state__' state" - } - }, "deploy": { "deploy": "Deploy", "full": "Full", From 1839c1972e7d335e59e8ea8e13eebecb46599be5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 20:21:27 +0100 Subject: [PATCH 140/195] Update packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js Co-authored-by: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> --- .../@node-red/editor-client/src/js/ui/deploy.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 809202c99..f0d4754f4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -102,8 +102,10 @@ RED.deploy = (function() { RED.actions.add("core:deploy-flows",save); if (type === "default") { - RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") }); - RED.actions.add("core:start-flows",function() { stopStartFlows("start") }); + if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { + RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") }); + RED.actions.add("core:start-flows",function() { stopStartFlows("start") }); + } RED.actions.add("core:restart-flows",restart); RED.actions.add("core:set-deploy-type-to-full",function() { RED.menu.setSelected("deploymenu-item-full",true);}); RED.actions.add("core:set-deploy-type-to-modified-flows",function() { RED.menu.setSelected("deploymenu-item-flow",true); }); From f438cbf63317d1913a441ed273ac978159adc448 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 21:04:28 +0100 Subject: [PATCH 141/195] Update for 3 beta 4 --- CHANGELOG.md | 35 +++++++++++++++++++ package.json | 4 +-- .../@node-red/editor-api/package.json | 6 ++-- .../@node-red/editor-client/package.json | 2 +- .../editor-client/src/js/ui/tour/tourGuide.js | 4 +-- .../editor-client/src/tours/welcome.js | 6 ++-- .../node_modules/@node-red/nodes/package.json | 4 +-- .../@node-red/registry/package.json | 4 +-- .../@node-red/runtime/package.json | 6 ++-- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++--- 11 files changed, 59 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fcc30bd..ba74e4d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +#### 3.0.0-beta.4: Beta Release + +Editor + + - 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 + + - 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.}} within a template node (#3690) @cow0w + #### 3.0.0-beta.3: Beta Release Editor diff --git a/package.json b/package.json index 715d09a5b..1f5759634 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -45,7 +45,7 @@ "express-session": "1.17.3", "form-data": "4.0.0", "fs-extra": "10.1.0", - "got": "11.8.3", + "got": "11.8.5", "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 874fc2cec..777e15cc3 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.3", - "@node-red/editor-client": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.4", + "@node-red/editor-client": "3.0.0-beta.4", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 2a32cbded..1041c9745 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index adf7f4212..913582c10 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -436,8 +436,8 @@ RED.tourGuide = (function() { function listTour() { return [ { - id: "2_3", - label: "3.0.0-beta.3", + id: "3_0", + label: "3.0.0-beta.4", path: "./tours/welcome.js" }, { diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 98d83c7e5..4f80f93ef 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,14 +1,14 @@ export default { - version: "3.0.0-beta.3", + version: "3.0.0-beta.4", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 3.0 Beta 3!", + "en-US": "Welcome to Node-RED 3.0 Beta 4!", "ja": "Node-RED 3.0 ベータ3へようこそ!" }, description: { - "en-US": "

        This is the final beta release of Node-RED 3.0.

        Let's take a moment to discover the new features in this release.

        ", + "en-US": "

        This is another final beta release of Node-RED 3.0.

        Let's take a moment to discover the new features in this release.

        ", "ja": "

        これはNode-RED 3.0の最後のベータリリースです。

        本リリースの新機能を見つけてみましょう。

        " } }, diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 5df2fd503..68c7099bf 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", @@ -28,7 +28,7 @@ "denque": "2.0.1", "form-data": "4.0.0", "fs-extra": "10.1.0", - "got": "11.8.3", + "got": "11.8.5", "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 92dbb0d95..f4808c8ad 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.4", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index dcf7bf47c..c6dc2130e 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0-beta.3", - "@node-red/util": "3.0.0-beta.3", + "@node-red/registry": "3.0.0-beta.4", + "@node-red/util": "3.0.0-beta.4", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index fec9690ba..6723b9b6c 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 36207c17e..3457e03af 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0-beta.3", - "@node-red/runtime": "3.0.0-beta.3", - "@node-red/util": "3.0.0-beta.3", - "@node-red/nodes": "3.0.0-beta.3", + "@node-red/editor-api": "3.0.0-beta.4", + "@node-red/runtime": "3.0.0-beta.4", + "@node-red/util": "3.0.0-beta.4", + "@node-red/nodes": "3.0.0-beta.4", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From 1d130a7857e77f92d7008b06dcf23acf022b4810 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 30 Jun 2022 10:14:51 +0100 Subject: [PATCH 142/195] Add missing entries from beta.4 changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba74e4d5f..fb99d8e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ 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 @@ -22,6 +23,7 @@ Editor 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 From 1b6cbe5c233cc8404c1f63fa2c3dc53df0cd4cb3 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 30 Jun 2022 15:17:49 +0100 Subject: [PATCH 143/195] Ensure importMap is not null when using import UI fixes #3722 --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index a898834b0..9da5aad05 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1664,6 +1664,7 @@ RED.nodes = (function() { function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} } options = Object.assign({}, defOpts, options) + options.importMap = options.importMap || {} const createNewIds = options.generateIds; const reimport = (!createNewIds && !!options.reimport) const createMissingWorkspace = options.addFlow; From d4fdf6be670bf9a0f4ec98b251aaf080cac02e51 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 1 Jul 2022 01:09:06 +0900 Subject: [PATCH 144/195] Add Japanese translations for v3.0-beta.4 --- .../editor-client/locales/en-US/editor.json | 4 ++++ .../editor-client/locales/ja/editor.json | 20 +++++++++++++++---- .../editor-client/src/js/ui/deploy.js | 4 ++-- .../editor-client/src/tours/welcome.js | 4 ++-- .../@node-red/runtime/locales/ja/runtime.json | 1 + 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 87725bd14..c8abada3e 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -300,6 +300,10 @@ "modifiedFlowsDesc": "Only deploys flows that contain changed nodes", "modifiedNodes": "Modified Nodes", "modifiedNodesDesc": "Only deploys nodes that have changed", + "startFlows": "Start", + "startFlowsDesc": "Start Flows", + "stopFlows": "Stop", + "stopFlowsDesc": "Stop Flows", "restartFlows": "Restart Flows", "restartFlowsDesc": "Restarts the current deployed flows", "successfulDeploy": "Successfully deployed", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 752bfc906..fb3458eed 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -169,6 +169,10 @@ } }, "notification": { + "state": { + "flowsStopped": "フローを停止しました", + "flowsStarted": "フローを開始しました" + }, "warning": "警告: __message__", "warnings": { "undeployedChanges": "ノードの変更をデプロイしていません", @@ -296,6 +300,10 @@ "modifiedFlowsDesc": "変更したノードを含むフローのみデプロイ", "modifiedNodes": "変更したノード", "modifiedNodesDesc": "変更したノードのみデプロイ", + "startFlows": "開始", + "startFlowsDesc": "フローを開始", + "stopFlows": "停止", + "stopFlowsDesc": "フローを停止", "restartFlows": "フローを再起動", "restartFlowsDesc": "デプロイされた現在のフローを再起動", "successfulDeploy": "デプロイが成功しました", @@ -685,7 +693,8 @@ "showHelp": "ヘルプを表示", "showInOutline": "アウトラインに表示", "showTopics": "トピックを表示", - "noHelp": "ヘルプのトピックが未選択" + "noHelp": "ヘルプのトピックが未選択", + "changeLog": "更新履歴" }, "config": { "name": "設定ノードを表示", @@ -845,7 +854,7 @@ "pushFailed": "リモートに新しいコミットがあるため、プッシュに失敗しました。プルしてマージしてから、再度プッシュしてください。", "push": "プッシュ", "pull": "プル", - "unablePull": "

        リモートの変更のプル失敗:ステージングされていないローカルの変更を上書きされてしまいます。

        変更をコミットしてから再度実行してください。

        ", + "unablePull": "

        リモートの変更のプル失敗:ステージングされていないローカルの変更が上書きされてしまいます。

        変更をコミットしてから再度実行してください。

        ", "showUnstagedChanges": "ステージングされていない変更を表示", "connectionFailed": "リモートリポジトリに接続できません: ", "pullUnrelatedHistory": "

        リモートに関連のないコミット履歴があります。

        本当に変更をプルしてローカルリポジトリに反映しますか?

        ", @@ -1159,7 +1168,8 @@ "takeATour": "ツアーを開始", "start": "開始", "next": "次へ", - "tours": "ツアー" + "welcomeTours": "ウェルカムツアー", + "tours": "ツアー" }, "diagnostics": { "title": "システム情報" @@ -1336,6 +1346,8 @@ "new-project": "新しいプロジェクト", "open-project": "プロジェクトを開く", "show-project-settings": "プロジェクト設定を表示", - "show-version-control-tab": "バージョンコントロールタブを表示" + "show-version-control-tab": "バージョンコントロールタブを表示", + "start-flows": "フローを開始", + "stop-flows": "フローを停止" } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index f0d4754f4..8a8df6837 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -70,8 +70,8 @@ RED.deploy = (function() { null ] if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { - mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) - mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) + mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:RED._("deploy.startFlows"),sublabel:RED._("deploy.startFlowsDesc"),onselect:"core:start-flows", visible:false}) + mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:RED._("deploy.stopFlows"),sublabel:RED._("deploy.stopFlowsDesc"),onselect:"core:stop-flows", visible:false}) } 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 }); diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 4f80f93ef..35a276606 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -5,11 +5,11 @@ export default { titleIcon: "fa fa-map-o", title: { "en-US": "Welcome to Node-RED 3.0 Beta 4!", - "ja": "Node-RED 3.0 ベータ3へようこそ!" + "ja": "Node-RED 3.0 ベータ4へようこそ!" }, description: { "en-US": "

        This is another final beta release of Node-RED 3.0.

        Let's take a moment to discover the new features in this release.

        ", - "ja": "

        これはNode-RED 3.0の最後のベータリリースです。

        本リリースの新機能を見つけてみましょう。

        " + "ja": "

        これはNode-RED 3.0のもう一つの最後のベータリリースです。

        本リリースの新機能を見つけてみましょう。

        " } }, { diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index df5f9fa08..bb5b0badc 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -122,6 +122,7 @@ "stopped-flows": "フローを停止しました", "stopped": "停止しました", "stopping-error": "ノードの停止に失敗しました: __message__", + "updated-flows": "フローを更新しました", "added-flow": "フローを追加します: __label__", "updated-flow": "フローを更新しました: __label__", "removed-flow": "フローを削除しました: __label__", From 6090a5b2740ab271a23d01568c304647de8bc7d3 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 21:51:51 +0200 Subject: [PATCH 145/195] Revert disable/enable logic proposed before --- .../editor-client/src/js/ui/editor.js | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 0ea00621e..fb4c200f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1033,8 +1033,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - if (editing_node.hasOwnProperty('outputs')) { editing_node.__outputs = editing_node.outputs; } @@ -1077,8 +1075,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1182,8 +1178,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayHeader = tray.find(".red-ui-tray-header"); var trayBody = tray.find('.red-ui-tray-body'); var trayFooter = tray.find(".red-ui-tray-footer"); @@ -1264,8 +1258,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - RED.workspaces.refresh(); activeEditPanes.forEach(function(pane) { @@ -1643,8 +1635,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
        ", { class: "red-ui-tray-footer-left" @@ -1676,8 +1666,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1767,8 +1755,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
        ", { class: "red-ui-tray-footer-left" @@ -1790,8 +1776,6 @@ RED.editor = (function() { }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1904,8 +1888,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayFooter = tray.find(".red-ui-tray-footer"); var trayBody = tray.find('.red-ui-tray-body'); trayBody.parent().css('overflow','hidden'); @@ -1933,8 +1915,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } From 3726d6fe3b81f8cc11d68833630968501b37d109 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 21:52:16 +0200 Subject: [PATCH 146/195] Finetune keyboard shortcut management --- .../editor-client/src/js/ui/keyboard.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index d9511269d..ae104637c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -265,13 +265,18 @@ RED.keyboard = (function() { if (partialState) { partialState = null; return resolveKeyEvent(evt); - } else if (Object.keys(handler).length > 0) { - partialState = handler; - evt.preventDefault(); - return null; - } else { - return null; } + if (Object.keys(handler).length > 0) { + // check if there's a potential combined handler initiated by this keyCode + for (h in handler) { + if (matchHandlerToEvent(evt,handler[h]) > -1) { + partialState = handler; + evt.preventDefault(); + break; + } + } + } + return null; } else { var depth = Infinity; var matchedHandler; From d8a781632c0dc9874b09efa672dfcccccc097bd6 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 21:52:52 +0200 Subject: [PATCH 147/195] Re-order shortcut keymap --- .../editor-client/src/js/keymap.json | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index 62a1f4414..1bedab6e8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -3,16 +3,12 @@ "alt-shift-p":"core:manage-palette", "ctrl-f": "core:search", "ctrl-shift-f": "core:list-flows", - "ctrl-+": "core:zoom-in", - "ctrl--": "core:zoom-out", - "ctrl-0": "core:zoom-reset", - "ctrl-enter": "core:confirm-edit-tray", - "ctrl-escape": "core:cancel-edit-tray", "ctrl-d": "core:deploy-flows", - "ctrl-g i": "core:show-info-tab", - "ctrl-g h": "core:show-help-tab", - "ctrl-g d": "core:show-debug-tab", "ctrl-g c": "core:show-config-tab", + "ctrl-g d": "core:show-debug-tab", + "ctrl-g h": "core:show-help-tab", + "ctrl-g i": "core:show-info-tab", + "ctrl-g v": "core:show-version-control-tab", "ctrl-g x": "core:show-context-tab", "ctrl-e": "core:show-export-dialog", "ctrl-i": "core:show-import-dialog", @@ -23,11 +19,8 @@ "ctrl-alt-r": "core:show-remote-diff", "ctrl-alt-n": "core:new-project", "ctrl-alt-o": "core:open-project", - "ctrl-g v": "core:show-version-control-tab", "ctrl-shift-l": "core:show-event-log", - "ctrl-shift-p":"core:show-action-list", - "alt-w": "core:hide-flow", - "alt-shift-w": "core:show-last-hidden-flow" + "ctrl-shift-p":"core:show-action-list" }, "red-ui-sidebar-node-config": { "backspace": "core:delete-config-selection", @@ -93,7 +86,16 @@ "alt-a v": "core:distribute-selection-vertically", "shift-f": "core:search-previous", "f": "core:search-next", - "alt-l l": "core:split-wire-with-link-nodes" + "alt-l l": "core:split-wire-with-link-nodes", + "alt-w": "core:hide-flow", + "alt-shift-w": "core:show-last-hidden-flow", + "ctrl-+": "core:zoom-in", + "ctrl--": "core:zoom-out", + "ctrl-0": "core:zoom-reset" + }, + "red-ui-editor-stack": { + "ctrl-enter": "core:confirm-edit-tray", + "ctrl-escape": "core:cancel-edit-tray" } } From 4d42f8ec589114d1ce3fd7881e3251faa3985245 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 22:08:58 +0200 Subject: [PATCH 148/195] Global variable definition in for loop: Fixed --- .../node_modules/@node-red/editor-client/src/js/ui/keyboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index ae104637c..40e11aa72 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -268,7 +268,7 @@ RED.keyboard = (function() { } if (Object.keys(handler).length > 0) { // check if there's a potential combined handler initiated by this keyCode - for (h in handler) { + for (let h in handler) { if (matchHandlerToEvent(evt,handler[h]) > -1) { partialState = handler; evt.preventDefault(); From 0a5d7c21003f325ff88e252b9e32b34090640659 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Jul 2022 10:31:50 +0100 Subject: [PATCH 149/195] Update add-junction menu to work in more cases --- .../editor-client/src/js/ui/contextMenu.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 0b388aff3..a8b787ba5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -43,7 +43,16 @@ RED.contextMenu = (function() { const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g + const offset = $("#red-ui-workspace-chart").offset() + let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() + let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop() + + if (RED.view.snapGrid) { + const gridSize = RED.view.gridSize() + addX = gridSize*Math.floor(addX/gridSize) + addY = gridSize*Math.floor(addY/gridSize) + } const menuItems = [ { onselect: 'core:show-action-list', onpostselect: function() {} }, @@ -54,17 +63,42 @@ RED.contextMenu = (function() { label: RED._("contextMenu.node"), onselect: function() { RED.view.showQuickAddDialog({ - position: [ options.x - offset.left, options.y - offset.top ], + position: [ addX, addY ], touchTrigger: true, splice: isSingleLink?selection.links[0]:undefined, // spliceMultiple: isMultipleLinks }) } }, - { + ( hasSelection || hasLinks ) ? { label: RED._("contextMenu.junction"), onselect: 'core:split-wires-with-junctions', - disabled: hasSelection || !hasLinks + disabled: !hasLinks + } : { + label: RED._("contextMenu.junction"), + onselect: function() { + const nn = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: addX, + y: addY, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + const historyEvent = { + dirty: RED.nodes.dirty(), + t:'add', + junctions:[nn] + } + RED.nodes.addJunction(nn); + RED.history.push(historyEvent); + RED.nodes.dirty(true); + RED.view.redraw(true) + } }, { label: RED._("contextMenu.linkNodes"), @@ -118,7 +152,6 @@ RED.contextMenu = (function() { } } - const offset = $("#red-ui-workspace-chart").offset() menu = RED.menu.init({ direction: 'right', onpreselect: function() { From 9ac83cf62e66db3cb4c66aae2370f62d4d0095f5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Jul 2022 17:34:16 +0100 Subject: [PATCH 150/195] Do not remove unknown credentials of Subflow Modules Fixes #3641 --- .../@node-red/runtime/lib/flows/Subflow.js | 1 - .../@node-red/runtime/lib/nodes/credentials.js | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index 2e2beed74..824b88a28 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -390,7 +390,6 @@ class Subflow extends Flow { } name = newName; } - var parent = this.parent; if (parent) { diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 0432e01fb..5a72ab7fc 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -373,11 +373,14 @@ var api = module.exports = { } } - for (cred in savedCredentials) { - if (savedCredentials.hasOwnProperty(cred)) { - if (!newCreds.hasOwnProperty(cred)) { - delete savedCredentials[cred]; - dirty = true; + if (/^subflow(:|$)/.test(nodeType)) { + for (cred in savedCredentials) { + if (savedCredentials.hasOwnProperty(cred)) { + if (!newCreds.hasOwnProperty(cred)) { + console.log(` + ${cred} deleting for some reason`) + delete savedCredentials[cred]; + dirty = true; + } } } } From 829ccc3466ba6da0a927fbd8f64ad593a3c49a29 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Jul 2022 17:48:14 +0100 Subject: [PATCH 151/195] Do not generate new node-ids when pasting a cut flow Fixes #3629 --- .../@node-red/editor-client/src/js/ui/view.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 02b8df5d8..d24445deb 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -95,6 +95,7 @@ RED.view = (function() { let flashingNodeId; var clipboard = ""; + let clipboardSource // Note: these are the permitted status colour aliases. The actual RGB values // are set in the CSS - flow.scss/colors.scss @@ -628,8 +629,8 @@ RED.view = (function() { }); RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); - RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); - RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});}); + RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();}); + RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});}); RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) @@ -2703,7 +2704,7 @@ RED.view = (function() { } } - function copySelection() { + function copySelection(isCut) { if (mouse_mode === RED.state.SELECTING_NODE) { return; } @@ -2767,6 +2768,7 @@ RED.view = (function() { } } clipboard = JSON.stringify(nns); + clipboardSource = isCut ? 'cut' : 'copy' RED.menu.setDisabled("menu-item-edit-paste", false); if (nodeCount > 0) { RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); @@ -4086,7 +4088,7 @@ RED.view = (function() { var mdn = mousedown_node; var options = []; options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); - options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}}); options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); From 0fdcbb46115d283289a8a6041c43ce211a887351 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Sat, 2 Jul 2022 09:52:14 +0200 Subject: [PATCH 152/195] Fix label overflow @ config-node palette --- .../@node-red/editor-client/src/sass/tab-config.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 3223420f9..d6bde7db5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -54,6 +54,9 @@ ul.red-ui-sidebar-node-config-list { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + &:not(:last-child) { + width: calc(100% - 38px); + } } .red-ui-palette-icon-container { font-size: 12px; From 02308f9e2fce251ab8a40adc8a68566cc035e349 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sun, 3 Jul 2022 20:39:54 +0900 Subject: [PATCH 153/195] prevennt node from moving out of workspace --- .../@node-red/editor-client/src/js/ui/view-tools.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index a781c44fc..44da78fec 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -105,6 +105,8 @@ RED.view.tools = (function() { $(document).one('keyup',endKeyboardMove); endMoveSet = true; } + var space_width = 5000; + var space_height = 5000; var minX = 0; var minY = 0; var node; @@ -120,6 +122,12 @@ RED.view.tools = (function() { node.n.dirty = true; node.n.x += dx; node.n.y += dy; + if ((node.n.x +node.n.w/2) >= space_width) { + node.n.x = space_width -node.n.w/2; + } + if ((node.n.y +node.n.h/2) >= space_height) { + node.n.y = space_height -node.n.h/2; + } node.n.dirty = true; if (node.n.type === "group") { RED.group.markDirty(node.n); @@ -130,6 +138,7 @@ RED.view.tools = (function() { minY = Math.min(node.n.y-node.n.h/2-5,minY); } } + console.log("; mS:", dx, dy, minX, minY); if (minX !== 0 || minY !== 0) { for (var n = 0; n Date: Sun, 3 Jul 2022 20:22:19 +0100 Subject: [PATCH 154/195] Fix defaulting to monaco if settings does not contain codeEditor --- .../node_modules/@node-red/editor-api/lib/editor/theme.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 5a8dacde4..88b3eeb62 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -101,7 +101,10 @@ module.exports = { } themeSettings = null; theme = settings.editorTheme || {}; - themeContext.asset.vendorMonaco = ((theme.codeEditor || {}).lib === "monaco") ? "vendor/monaco/monaco-bootstrap.js" : ""; + themeContext.asset.vendorMonaco = "vendor/monaco/monaco-bootstrap.js" + if (theme.codeEditor && theme.codeEditor.lib === 'ace') { + themeContext.asset.vendorMonaco = '' + } activeTheme = theme.theme; }, From ca20f41d0ef5cd14974837d8b7fff40a6daf66bb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 3 Jul 2022 20:37:55 +0100 Subject: [PATCH 155/195] Update theme tests to ensure monaco is loaded --- test/unit/@node-red/editor-api/lib/editor/theme_spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js index 900be126f..fd1b42d61 100644 --- a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js @@ -51,7 +51,7 @@ describe("api/editor/theme", function () { context.should.have.a.property("asset"); context.asset.should.have.a.property("red", "red/red.min.js"); context.asset.should.have.a.property("main", "red/main.min.js"); - context.asset.should.have.a.property("vendorMonaco", ""); + context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js"); should.not.exist(theme.settings()); }); @@ -69,16 +69,16 @@ describe("api/editor/theme", function () { } }); - it("Adds monaco bootstrap when enabled", async function () { + it("Does not add monaco bootstrap when ace selected", async function () { theme.init({ editorTheme: { codeEditor: { - lib: 'monaco' + lib: 'ace' } } }); var context = await theme.context(); - context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js"); + context.asset.should.have.a.property("vendorMonaco", ""); }); it("picks up custom theme", async function () { From 67f45532139a72e64f9ab8ba1b7a0eae8c776235 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 4 Jul 2022 10:45:20 +0900 Subject: [PATCH 156/195] fix handling of global debug message --- .../node_modules/@node-red/nodes/core/common/21-debug.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index f861f518b..a85a4892b 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -252,6 +252,9 @@ if (pathParts.length === 1) { // The source node is on a flow - so can use its id to find sourceNode = RED.nodes.node(o.id); + if (pathParts[0] === "global") { + pathParts = []; + } } else if (pathParts.length > 1) { // Highlight the subflow instance node. sourceNode = RED.nodes.node(pathParts[1]); From 277cc19ec372bd3e38bd4fa054ca6d8df9d23594 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 4 Jul 2022 11:23:47 +0900 Subject: [PATCH 157/195] call done after close handler --- .../node_modules/@node-red/nodes/core/network/22-websocket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js index 5a46f04ff..d2a862a46 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js @@ -215,6 +215,7 @@ module.exports = function(RED) { delete listenerNodes[node.fullPath]; node.server.close(); node._inputNodes = []; + done(); } else { node.closing = true; From d95314c754f2dc2d2436b9f416719dc369b200bc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 4 Jul 2022 20:21:25 +0100 Subject: [PATCH 158/195] Update packages/node_modules/@node-red/runtime/lib/nodes/credentials.js --- packages/node_modules/@node-red/runtime/lib/nodes/credentials.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 5a72ab7fc..305594c85 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -377,7 +377,6 @@ var api = module.exports = { for (cred in savedCredentials) { if (savedCredentials.hasOwnProperty(cred)) { if (!newCreds.hasOwnProperty(cred)) { - console.log(` + ${cred} deleting for some reason`) delete savedCredentials[cred]; dirty = true; } From 78ed53f4fba241217062fdca0c30862e0dc42ff4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 4 Jul 2022 20:46:21 +0100 Subject: [PATCH 159/195] Ensure a second paste of cut nodes is treated as a copy-paste --- .../@node-red/editor-client/src/js/ui/view.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index d24445deb..224f0a6f6 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -630,7 +630,9 @@ RED.view = (function() { RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();}); - RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});}); + RED.actions.add("core:paste-from-internal-clipboard",function(){ + importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'}); + }); RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) @@ -2150,6 +2152,9 @@ RED.view = (function() { } } if (mouse_mode == RED.state.IMPORT_DRAGGING) { + if (clipboardSource === 'cut') { + clipboardSource = 'copy' + } updateActiveNodes(); RED.nodes.dirty(true); } @@ -3479,6 +3484,9 @@ RED.view = (function() { updateSelection(); RED.nodes.dirty(true); redraw(); + if (clipboardSource === 'cut') { + clipboardSource = 'copy' + } resetMouseVars(); d3.event.stopPropagation(); return; From daa9cb65e5443517e18b364a36f59ce79c466d96 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Mon, 4 Jul 2022 17:17:40 -0400 Subject: [PATCH 160/195] Change disabled config node background color variable --- .../@node-red/editor-client/src/sass/tab-config.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index d6bde7db5..876a21662 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -101,7 +101,7 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { } .red-ui-palette-node-config-unused,.red-ui-palette-node-config-disabled { border-color: var(--red-ui-primary-border-color); - background: var(--red-ui-secondary-background-inactive); + background: var(--red-ui-node-config-background); border-style: dashed; color: var(--red-ui-tertiary-text-color); } From 7d0267c924e98f262a8a560b7122cc051d83129b Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Mon, 4 Jul 2022 18:38:45 -0400 Subject: [PATCH 161/195] Move colors left behind in #3692 to CSS variables --- .../@node-red/editor-client/src/sass/dropdownMenu.scss | 2 +- .../@node-red/editor-client/src/sass/projects.scss | 4 +++- .../node_modules/@node-red/editor-client/src/sass/search.scss | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 4323e9342..5cb5f725c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -220,7 +220,7 @@ margin-right: -15px; /* Caret Arrow */ border-color: transparent; - border-left-color: $menuCaret; + border-left-color: var(--red-ui-menuCaret); border-style: solid; border-width: 5px 0 5px 5px; content: " "; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index 0019ba516..ee43c7a87 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -711,7 +711,9 @@ transform: rotate(90deg); } } -.red-ui-projects-dialog-file-list-entry-file-type-git { color: $tertiary-text-color } +.red-ui-projects-dialog-file-list-entry-file-type-git { + color: var(--red-ui-tertiary-text-color); +} .red-ui-projects-dialog-remote-list { .red-ui-editableList-container { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index cf37fcb6e..f5502715b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -84,7 +84,7 @@ .red-ui-search-result-node-port { position: absolute; border-radius: 2px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); width: 6px; height: 7px; top:4px; From 6c1f63bfbbc56308546bd69a22f53bbeb61dcc73 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 5 Jul 2022 09:02:21 +0900 Subject: [PATCH 162/195] introduce RED.view.dimensions for workspace dimensions --- .../@node-red/editor-client/src/js/ui/view-tools.js | 6 +++--- .../@node-red/editor-client/src/js/ui/view.js | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 44da78fec..2bec5669c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -105,8 +105,9 @@ RED.view.tools = (function() { $(document).one('keyup',endKeyboardMove); endMoveSet = true; } - var space_width = 5000; - var space_height = 5000; + var dim = RED.view.dimensions(); + var space_width = dim.width; + var space_height = dim.height; var minX = 0; var minY = 0; var node; @@ -138,7 +139,6 @@ RED.view.tools = (function() { minY = Math.min(node.n.y-node.n.h/2-5,minY); } } - console.log("; mS:", dx, dy, minX, minY); if (minX !== 0 || minY !== 0) { for (var n = 0; n Date: Tue, 5 Jul 2022 07:33:31 -0400 Subject: [PATCH 163/195] Don't let themes change config node label colors --- .../node_modules/@node-red/editor-client/src/sass/colors.scss | 1 + .../@node-red/editor-client/src/sass/tab-config.scss | 4 ++-- .../@node-red/editor-client/src/sass/variables.scss | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index b311b06b5..ce71bcdba 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -217,6 +217,7 @@ $node-icon-border-color: #000; $node-icon-border-color-opacity: 0.1; $node-config-background: #f3f3f3; +$node-config-icon-container-disabled: #aaa; $node-link-port-background: #eee; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 876a21662..c1f151ca6 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -103,13 +103,13 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { border-color: var(--red-ui-primary-border-color); background: var(--red-ui-node-config-background); border-style: dashed; - color: var(--red-ui-tertiary-text-color); + color: var(--red-ui-node-config-icon-container-disabled); } .red-ui-palette-node-config-disabled { opacity: 0.6; font-style: italic; i { - color: var(--red-ui-secondary-text-color); + color: var(--red-ui-node-port-label-color); margin-right: 5px; } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss index abd31629c..50e1c9310 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss @@ -203,6 +203,7 @@ --red-ui-node-icon-border-color-opacity: #{$node-icon-border-color-opacity}; --red-ui-node-config-background: #{$node-config-background}; + --red-ui-node-config-icon-container-disabled: #{$node-config-icon-container-disabled}; --red-ui-node-link-port-background: #{$node-link-port-background}; From eaa85ae8d505d259d8b346ba0e9cd705c521ba34 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 12:54:22 +0100 Subject: [PATCH 164/195] Fix insert=>link nodes enable/disable state --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index a8b787ba5..44f11c87d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -103,7 +103,7 @@ RED.contextMenu = (function() { { label: RED._("contextMenu.linkNodes"), onselect: 'core:split-wire-with-link-nodes', - disabled: hasSelection || !hasLinks + disabled: !hasLinks } ] From 03f758720cbcacabb8aa13ee95d9b814b26e878c Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 13:05:18 +0100 Subject: [PATCH 165/195] Allow escape key to close context menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 44f11c87d..bebd178a4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -192,16 +192,12 @@ RED.contextMenu = (function() { disposeMenu() }); menu.show(); - - // menu.show({ - // target: $('#red-ui-main-container'), - // x: options.x, - // y: options.y - // }) - + // set focus to first item so that pressing escape key closes the menu + $("#red-ui-workspace-context-menu :first(ul) > a").trigger("focus") } - + // Allow escape key hook and other editor events to close context menu + RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() }) return { show: show, hide: disposeMenu From 21d3261eecea2889699ef08410f4b805796d2df4 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 13:05:57 +0100 Subject: [PATCH 166/195] Allow editor events to close context menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index bebd178a4..ac88ebe76 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -198,6 +198,11 @@ RED.contextMenu = (function() { } // Allow escape key hook and other editor events to close context menu RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() }) + RED.events.on("editor:open",function() { RED.contextMenu.hide() }); + RED.events.on("search:open",function() { RED.contextMenu.hide() }); + RED.events.on("type-search:open",function() { RED.contextMenu.hide() }); + RED.events.on("actionList:open",function() { RED.contextMenu.hide() }); + RED.events.on("view:selection-changed",function() { RED.contextMenu.hide() }); return { show: show, hide: disposeMenu From 7216c6d62a2f8420d1524772a9e5907398bebe6c Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 13:08:56 +0100 Subject: [PATCH 167/195] code linting and remove excessive new lines --- .../editor-client/src/js/ui/contextMenu.js | 81 +++++++------------ 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index ac88ebe76..063654d4a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -1,4 +1,4 @@ -RED.contextMenu = (function() { +RED.contextMenu = (function () { let menu; function createMenu() { @@ -15,10 +15,6 @@ RED.contextMenu = (function() { // ], // width: 200, // }) - - - - } function disposeMenu() { @@ -50,35 +46,35 @@ RED.contextMenu = (function() { if (RED.view.snapGrid) { const gridSize = RED.view.gridSize() - addX = gridSize*Math.floor(addX/gridSize) - addY = gridSize*Math.floor(addY/gridSize) + addX = gridSize * Math.floor(addX / gridSize) + addY = gridSize * Math.floor(addY / gridSize) } const menuItems = [ - { onselect: 'core:show-action-list', onpostselect: function() {} }, + { onselect: 'core:show-action-list', onpostselect: function () { } }, { label: RED._("contextMenu.insert"), options: [ { label: RED._("contextMenu.node"), - onselect: function() { + onselect: function () { RED.view.showQuickAddDialog({ - position: [ addX, addY ], + position: [addX, addY], touchTrigger: true, - splice: isSingleLink?selection.links[0]:undefined, + splice: isSingleLink ? selection.links[0] : undefined, // spliceMultiple: isMultipleLinks }) } }, - ( hasSelection || hasLinks ) ? { + (hasSelection || hasLinks) ? { label: RED._("contextMenu.junction"), onselect: 'core:split-wires-with-junctions', disabled: !hasLinks } : { label: RED._("contextMenu.junction"), - onselect: function() { + onselect: function () { const nn = { - _def: {defaults:{}}, + _def: { defaults: {} }, type: 'junction', z: RED.workspaces.active(), id: RED.nodes.id(), @@ -91,8 +87,8 @@ RED.contextMenu = (function() { } const historyEvent = { dirty: RED.nodes.dirty(), - t:'add', - junctions:[nn] + t: 'add', + junctions: [nn] } RED.nodes.addJunction(nn); RED.history.push(historyEvent); @@ -111,28 +107,13 @@ RED.contextMenu = (function() { } ] - // menuItems.push( - // { - // label: (isSingleLink || isMultipleLinks)?'Insert into wire...':'Add node...', - // onselect: function() { - // RED.view.showQuickAddDialog({ - // position: [ options.x - offset.left, options.y - offset.top ], - // touchTrigger: true, - // splice: isSingleLink?selection.links[0]:undefined, - // spliceMultiple: isMultipleLinks - // }) - // } - // }, - // ) - // if (hasLinks && !hasSelection) { - // menuItems.push({ onselect: 'core:split-wires-with-junctions', label: 'Insert junction'}) - // } + menuItems.push( null, { onselect: 'core:undo', disabled: RED.history.list().length === 0 }, { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, null, - { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection}, + { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection }, { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection }, { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() }, { onselect: 'core:delete-selection', disabled: !canDelete }, @@ -144,8 +125,8 @@ RED.contextMenu = (function() { menuItems.push( null, isGroup ? - { onselect: 'core:ungroup-selection', disabled: !isGroup } - : { onselect: 'core:group-selection', disabled: !hasSelection } + { onselect: 'core:ungroup-selection', disabled: !isGroup } + : { onselect: 'core:group-selection', disabled: !hasSelection } ) if (canRemoveFromGroup) { menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }) @@ -154,16 +135,16 @@ RED.contextMenu = (function() { } menu = RED.menu.init({ direction: 'right', - onpreselect: function() { + onpreselect: function () { disposeMenu() }, - onpostselect: function() { + onpostselect: function () { RED.view.focus() }, options: menuItems }); - menu.attr("id","red-ui-workspace-context-menu"); + menu.attr("id", "red-ui-workspace-context-menu"); menu.css({ position: "absolute" }) @@ -174,18 +155,18 @@ RED.contextMenu = (function() { var top = options.y var left = options.x - if (top+menu.height()-$(document).scrollTop() > $(window).height()) { - top -= (top+menu.height())-$(window).height() + 22; + if (top + menu.height() - $(document).scrollTop() > $(window).height()) { + top -= (top + menu.height()) - $(window).height() + 22; } - if (left+menu.width()-$(document).scrollLeft() > $(window).width()) { - left -= (left+menu.width())-$(window).width() + 18; + if (left + menu.width() - $(document).scrollLeft() > $(window).width()) { + left -= (left + menu.width()) - $(window).width() + 18; } menu.css({ - top: top+"px", - left: left+"px" + top: top + "px", + left: left + "px" }) $(".red-ui-menu.red-ui-menu-dropdown").hide(); - $(document).on("mousedown.red-ui-workspace-context-menu", function(evt) { + $(document).on("mousedown.red-ui-workspace-context-menu", function (evt) { if (menu && menu[0].contains(evt.target)) { return } @@ -198,11 +179,11 @@ RED.contextMenu = (function() { } // Allow escape key hook and other editor events to close context menu RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() }) - RED.events.on("editor:open",function() { RED.contextMenu.hide() }); - RED.events.on("search:open",function() { RED.contextMenu.hide() }); - RED.events.on("type-search:open",function() { RED.contextMenu.hide() }); - RED.events.on("actionList:open",function() { RED.contextMenu.hide() }); - RED.events.on("view:selection-changed",function() { RED.contextMenu.hide() }); + RED.events.on("editor:open", function () { RED.contextMenu.hide() }); + RED.events.on("search:open", function () { RED.contextMenu.hide() }); + RED.events.on("type-search:open", function () { RED.contextMenu.hide() }); + RED.events.on("actionList:open", function () { RED.contextMenu.hide() }); + RED.events.on("view:selection-changed", function () { RED.contextMenu.hide() }); return { show: show, hide: disposeMenu From 962672564eba014b299a15193fcfa244ec036008 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 15:17:58 +0100 Subject: [PATCH 168/195] prevent exception generating tooltip for deleted nodes --- .../@node-red/editor-client/src/js/ui/view.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 70583d741..5a9df8ed7 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3293,11 +3293,17 @@ RED.view = (function() { if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { portLabelHoverTimeout = setTimeout(function() { + const n = port && port.node() + const nId = n && n.__data__ && n.__data__.id + //check see if node has been deleted since timeout started + if(!n || !n.parentNode || !RED.nodes.node(n.__data__.id)) { + return; //node is gone! + } var tooltip = getPortLabel(d,portType,portIndex); if (!tooltip) { return; } - var pos = getElementPosition(port.node()); + var pos = getElementPosition(n); portLabelHoverTimeout = null; portLabelHover = showTooltip( (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)), @@ -3734,6 +3740,10 @@ RED.view = (function() { if (d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")) { var parentNode = this.parentNode; portLabelHoverTimeout = setTimeout(function() { + //check see if node has been deleted since timeout started + if(!parentNode || !parentNode.parentNode || !RED.nodes.node(parentNode.id)) { + return; //node is gone! + } var tooltip; if (d._def.label) { tooltip = d._def.label; From 257b1f89f30cb430479e10854979b43f2ed2ad90 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 7 Jul 2022 16:47:26 +0900 Subject: [PATCH 169/195] fix display direction of context sub-menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index a8b787ba5..44090aa1a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -152,8 +152,16 @@ RED.contextMenu = (function() { } } + + var direction = "right"; + var MENU_WIDTH = 600; // can not use menu width here + if ((options.x -$(document).scrollLeft()) > + ($(window).height() -MENU_WIDTH)) { + direction = "left"; + } + menu = RED.menu.init({ - direction: 'right', + direction: direction, onpreselect: function() { disposeMenu() }, From bd4a5ac8441c473f3b6da2b9c1c949157eca984f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 7 Jul 2022 19:39:28 +0900 Subject: [PATCH 170/195] fixed menu width and window width --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 44090aa1a..7d893225b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -154,9 +154,9 @@ RED.contextMenu = (function() { } var direction = "right"; - var MENU_WIDTH = 600; // can not use menu width here + var MENU_WIDTH = 500; // can not use menu width here if ((options.x -$(document).scrollLeft()) > - ($(window).height() -MENU_WIDTH)) { + ($(window).width() -MENU_WIDTH)) { direction = "left"; } From 18f9ab0cdae802f5c857b3a283a79d6db8108fa1 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 7 Jul 2022 20:23:27 +0900 Subject: [PATCH 171/195] make left sub-menu caret fit within context menu --- .../@node-red/editor-client/src/sass/dropdownMenu.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 5cb5f725c..84edd405e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -199,7 +199,7 @@ width: 0; height: 0; margin-top: 5px; - margin-left: -30px; + margin-left: -8px; /* Caret Arrow */ border-color: transparent; border-right-color: var(--red-ui-menuCaret); From 5ae566952be124329a194a805cd9a940ef7c1b9e Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 8 Jul 2022 13:10:21 +0900 Subject: [PATCH 172/195] do not show clear pinned if not available --- .../core/common/lib/debug/debug-utils.js | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js index 5782c6ffc..70dc33605 100644 --- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js +++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js @@ -459,30 +459,38 @@ RED.debug = (function() { function showMessageMenu(button,dbgMessage,sourceId) { activeMenuMessage = dbgMessage; if (!menuOptionMenu) { - menuOptionMenu = RED.menu.init({id:"red-ui-debug-msg-option-menu", - options: [ - {id:"red-ui-debug-msg-menu-item-collapse",label:RED._("node-red:debug.messageMenu.collapseAll"),onselect:function(){ - activeMenuMessage.collapse(); - }}, + var opts = [ + {id:"red-ui-debug-msg-menu-item-collapse",label:RED._("node-red:debug.messageMenu.collapseAll"),onselect:function(){ + activeMenuMessage.collapse(); + }}, + ]; + if (activeMenuMessage.clearPinned) { + opts.push( {id:"red-ui-debug-msg-menu-item-clear-pins",label:RED._("node-red:debug.messageMenu.clearPinned"),onselect:function(){ activeMenuMessage.clearPinned(); }}, - null, - {id:"red-ui-debug-msg-menu-item-filter", label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){ - var candidateNodes = RED.nodes.filterNodes({type:'debug'}); - candidateNodes.forEach(function(n) { - filteredNodes[n.id] = true; - }); - delete filteredNodes[sourceId]; - $("#red-ui-sidebar-debug-filterSelected").trigger("click"); - RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) - refreshMessageList(); - }}, - {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ - $("#red-ui-sidebar-debug-filterAll").trigger("click"); - refreshMessageList(); - }} - ] + ); + } + opts.push( + null, + {id:"red-ui-debug-msg-menu-item-filter", label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){ + var candidateNodes = RED.nodes.filterNodes({type:'debug'}); + candidateNodes.forEach(function(n) { + filteredNodes[n.id] = true; + }); + delete filteredNodes[sourceId]; + $("#red-ui-sidebar-debug-filterSelected").trigger("click"); + RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) + refreshMessageList(); + }}, + {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ + $("#red-ui-sidebar-debug-filterAll").trigger("click"); + refreshMessageList(); + }} + ); + + menuOptionMenu = RED.menu.init({id:"red-ui-debug-msg-option-menu", + options: opts }); menuOptionMenu.css({ position: "absolute" From 69beecf334aafe21f1f344c652d3699f7347a71b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 8 Jul 2022 13:06:05 +0100 Subject: [PATCH 173/195] Fix menu padding for pull-left submenus --- .../editor-client/src/sass/dropdownMenu.scss | 11 +++++++---- .../@node-red/editor-client/src/sass/header.scss | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 84edd405e..f6a2d6fde 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -63,11 +63,14 @@ padding: 4px 12px 4px 12px; } - &.red-ui-menu-dropdown-submenus > li > a, - &.red-ui-menu-dropdown-submenus > li > a:focus { + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-right > li > a, + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-right > li > a:focus { padding-right: 20px; } - + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-left > li > a, + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-left > li > a:focus { + padding-left: 20px; + } & > .active > a, @@ -199,7 +202,7 @@ width: 0; height: 0; margin-top: 5px; - margin-left: -8px; + margin-left: -15px; /* Caret Arrow */ border-color: transparent; border-right-color: var(--red-ui-menuCaret); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index e837f0805..723c1e9bd 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -191,7 +191,7 @@ margin-top: 0; li a { color: var(--red-ui-header-menu-color); - padding: 3px 10px 3px 40px; + padding: 3px 10px 3px 30px; img { max-width: 100%; margin-right: 10px; @@ -243,6 +243,7 @@ } .red-ui-menu-dropdown-submenu>a:before { border-right-color: var(--red-ui-headerMenuCaret); + margin-left: -25px !important; } /* Deploy menu customisations */ From 639030924fa969def5e210c8d78332ce5a6379a8 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 8 Jul 2022 14:09:54 +0100 Subject: [PATCH 174/195] include dirty state in history event --- .../@node-red/editor-client/src/js/ui/view-tools.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 2bec5669c..23316b1a1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1089,6 +1089,7 @@ RED.view.tools = (function() { linkGroups.sort(function(A,B) { return groupedLinks[B].length - groupedLinks[A].length }) + const wasDirty = RED.nodes.dirty() linkGroups.forEach(function(gid) { var links = groupedLinks[gid] var junction = { @@ -1179,6 +1180,7 @@ RED.view.tools = (function() { }) if (addedJunctions.length > 0) { RED.history.push({ + dirty: wasDirty, t: 'add', links: addedLinks, junctions: addedJunctions, From 7a048d5b32ff8dfbcd7a7f015b69dd276ed74c76 Mon Sep 17 00:00:00 2001 From: Franck Mourre Date: Mon, 11 Jul 2022 10:51:31 +0200 Subject: [PATCH 175/195] Fix change node, not handling from field properly when using context --- .../node_modules/@node-red/nodes/core/function/15-change.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.js b/packages/node_modules/@node-red/nodes/core/function/15-change.js index d177caec8..94f47fcbf 100644 --- a/packages/node_modules/@node-red/nodes/core/function/15-change.js +++ b/packages/node_modules/@node-red/nodes/core/function/15-change.js @@ -168,9 +168,9 @@ module.exports = function(RED) { return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done); } else if (rule.fromt === 'flow' || rule.fromt === 'global') { var contextKey = RED.util.parseContextStore(rule.from); - if (/\[msg\./.test(context.key)) { + if (/\[msg\./.test(contextKey.key)) { // The key has a nest msg. reference to evaluate first - context.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true); + contextKey.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true); } node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => { if (err) { From 55a94d659bccce27e90ffa0bf554493e1bdf1f94 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 10:27:30 +0100 Subject: [PATCH 176/195] fix const reassignment --- packages/node_modules/@node-red/nodes/core/common/60-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 08bad7a62..24a964807 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -29,7 +29,7 @@ module.exports = function(RED) { "use strict"; const crypto = require("crypto"); const targetCache = (function () { - const registry = { id: {}, name: {} }; + let registry = { id: {}, name: {} } function getIndex(/** @type {[LinkTarget]}*/ targets, id) { for (let index = 0; index < (targets || []).length; index++) { const element = targets[index]; From e4098d39912edd4f3272811c7b5bf1303b544387 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 10:28:17 +0100 Subject: [PATCH 177/195] fix undecalred variable access --- packages/node_modules/@node-red/nodes/core/common/60-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 24a964807..90d1d5741 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -115,7 +115,7 @@ module.exports = function(RED) { targs.splice(idx, 1); } if (targs.length === 0) { - delete registry.name[tn.name]; + delete registry.name[target.name] } delete registry.id[target.id]; }, From 5c69599e78f68b09a7376656ee96731a2ee3804b Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 10:28:31 +0100 Subject: [PATCH 178/195] Apply linting suggestions to the `registry` enclosure --- .../@node-red/nodes/core/common/60-link.js | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 90d1d5741..90b7966e0 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -30,22 +30,22 @@ module.exports = function(RED) { const crypto = require("crypto"); const targetCache = (function () { let registry = { id: {}, name: {} } - function getIndex(/** @type {[LinkTarget]}*/ targets, id) { + function getIndex (/** @type {[LinkTarget]} */ targets, id) { for (let index = 0; index < (targets || []).length; index++) { - const element = targets[index]; + const element = targets[index] if (element.id === id) { - return index; + return index } } - return -1; + return -1 } /** * Generate a target object from a node - * @param {LinkInNode} node + * @param {LinkInNode} node * @returns {LinkTarget} a link target object */ - function generateTarget(node) { - const isSubFlow = node._flow.TYPE === "subflow"; + function generateTarget (node) { + const isSubFlow = node._flow.TYPE === 'subflow' return { id: node.id, name: node.name || node.id, @@ -58,72 +58,72 @@ module.exports = function(RED) { /** * Get a list of targets registerd to this name * @param {string} name Name of the target - * @param {boolean} [excludeSubflows] set `true` to exclude + * @param {boolean} [excludeSubflows] set `true` to exclude * @returns {[LinkTarget]} Targets registerd to this name. */ - getTargets(name, excludeSubflows) { - const targets = registry.name[name] || []; + getTargets (name, excludeSubflows) { + const targets = registry.name[name] || [] if (excludeSubflows) { - return targets.filter(e => e.isSubFlow != true); + return targets.filter(e => e.isSubFlow !== true) } - return targets; + return targets }, /** * Get a single target by registered name. * To restrict to a single flow, include the `flowId` * If there is no targets OR more than one target, null is returned * @param {string} name Name of the node - * @param {string} [flowId] + * @param {string} [flowId] * @returns {LinkTarget} target */ - getTarget(name, flowId) { - /** @type {[LinkTarget]}*/ - let possibleTargets = this.getTargets(name); - /** @type {LinkTarget}*/ - let target; + getTarget (name, flowId) { + /** @type {[LinkTarget]} */ + let possibleTargets = this.getTargets(name) + /** @type {LinkTarget} */ + let target if (possibleTargets.length && flowId) { - possibleTargets = possibleTargets.filter(e => e.flowId == flowId); + possibleTargets = possibleTargets.filter(e => e.flowId === flowId) } if (possibleTargets.length === 1) { - target = possibleTargets[0]; + target = possibleTargets[0] } - return target; + return target }, /** * Get a target by node ID * @param {string} nodeId ID of the node * @returns {LinkTarget} target */ - getTargetById(nodeId) { - return registry.id[nodeId]; + getTargetById (nodeId) { + return registry.id[nodeId] }, - register(/** @type {LinkInNode} */ node) { - const target = generateTarget(node); - const tByName = this.getTarget(target.name, target.flowId); + register (/** @type {LinkInNode} */ node) { + const target = generateTarget(node) + const tByName = this.getTarget(target.name, target.flowId) if (!tByName || tByName.id !== target.id) { - registry.name[target.name] = registry.name[target.name] || []; + registry.name[target.name] = registry.name[target.name] || [] registry.name[target.name].push(target) } - registry.id[target.id] = target; - return target; + registry.id[target.id] = target + return target }, - remove(node) { - const target = generateTarget(node); - const targs = this.getTargets(target.name); - const idx = getIndex(targs, target.id); + remove (node) { + const target = generateTarget(node) + const targs = this.getTargets(target.name) + const idx = getIndex(targs, target.id) if (idx > -1) { - targs.splice(idx, 1); + targs.splice(idx, 1) } if (targs.length === 0) { delete registry.name[target.name] } - delete registry.id[target.id]; + delete registry.id[target.id] }, - clear() { - registry = { id: {}, name: {} }; + clear () { + registry = { id: {}, name: {} } } } - })(); + })() function LinkInNode(n) { RED.nodes.createNode(this,n); From 44216310ca5bc87e5670c9903bc64612d3de58f2 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 13:09:49 +0100 Subject: [PATCH 179/195] Ensure global/config and flow/config have info fixes #3750 --- .../@node-red/nodes/core/common/21-debug.html | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index a85a4892b..88d51b283 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -245,47 +245,37 @@ // complete parentage of the node that generated this message. // flow-id/subflow-A-instance/subflow-B-instance - // If it has one id, that is a top level flow + // If it has one id, that is a top level flow or config node/global // each subsequent id is the instance id of a subflow node // pathParts = o.path.split("/"); if (pathParts.length === 1) { - // The source node is on a flow - so can use its id to find + // The source node is on a flow or is a global/config - so can use its id to find sourceNode = RED.nodes.node(o.id); - if (pathParts[0] === "global") { - pathParts = []; - } } else if (pathParts.length > 1) { // Highlight the subflow instance node. sourceNode = RED.nodes.node(pathParts[1]); } + const getNodeLabel = (n) => n.name || (typeof n.label === "function" && n.label()) || (typeof n.label === "string" && n.label) || (n.type + ":" + n.id); pathHierarchy = pathParts.map((id,index) => { if (index === 0) { - return { - id: id, - label: RED.nodes.workspace(id).label - } + if (id === "global") { + return { id: sourceNode.id, label: getNodeLabel(sourceNode) } + } + return { id: id, label: RED.nodes.workspace(id).label } //flow id + name } else { - var instanceNode = RED.nodes.node(id) - return { - id: id, - label: (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name) - } + const instanceNode = RED.nodes.node(id) + const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name) + return { id: id, label: pathLabel } } }) - if (pathParts.length === 1) { - pathHierarchy.push({ - id: o.id, - label: sourceNode.name || sourceNode.type+":"+sourceNode.id - }) + if (pathParts.length === 1 && pathParts[0] !== "global") { + pathHierarchy.push({ id: o.id, label: getNodeLabel(sourceNode) }) } if (o._alias) { let aliasNode = RED.nodes.node(o._alias) if (aliasNode) { - pathHierarchy.push({ - id: o._alias, - label: aliasNode.name || aliasNode.type+":"+aliasNode.id - }) + pathHierarchy.push({ id: o._alias, label: getNodeLabel(aliasNode) }) } } } else { From c86e4f52a0e827b543b8056811025425bbd9289f Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 12 Jul 2022 00:10:51 +0900 Subject: [PATCH 180/195] Fix shade for node icon to be rounded rectangle --- .../node_modules/@node-red/editor-client/src/sass/palette.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 0d123918a..fdfe77f09 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -171,6 +171,7 @@ left:0; width: 30px; border-right: 1px solid var(--red-ui-node-icon-background-color); + border-radius: 4px 0px 0px 4px; background-color: var(--red-ui-node-icon-background-color); } .red-ui-palette-icon-container-right { @@ -178,6 +179,7 @@ right: 0; border-right: none; border-left: 1px solid var(--red-ui-node-icon-background-color); + border-radius: 0px 4px 4px 0px; } .red-ui-palette-icon { display: inline-block; From cee287da99f1533b0a88c0ed74f95de906e74e8c Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 12 Jul 2022 02:09:20 +0900 Subject: [PATCH 181/195] Focus editor for undo after some actions in menu --- .../node_modules/@node-red/editor-client/src/js/ui/group.js | 4 ++++ .../node_modules/@node-red/editor-client/src/js/ui/subflow.js | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index e11a55660..add6da6c9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -308,6 +308,7 @@ RED.group = (function() { RED.history.push(historyEvent); RED.view.select({nodes:[group]}); RED.nodes.dirty(true); + RED.view.focus(); } } } @@ -330,6 +331,7 @@ RED.group = (function() { RED.history.push(historyEvent); RED.view.select({nodes:newSelection}) RED.nodes.dirty(true); + RED.view.focus(); } } @@ -424,6 +426,7 @@ RED.group = (function() { }); RED.history.push(historyEvent); RED.nodes.dirty(true); + RED.view.focus(); } } @@ -451,6 +454,7 @@ RED.group = (function() { } } RED.view.select({nodes:selection.nodes}) + RED.view.focus(); } } function createGroup(nodes) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 3aeb7151f..d15e52283 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -877,6 +877,7 @@ RED.subflow = (function() { RED.nodes.dirty(true); RED.view.updateActive(); RED.view.select(null); + RED.view.focus(); } From 0a0a7ca39b233ad4d8f99a2b920dc728d3cfd086 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 11 Jul 2022 20:19:48 +0100 Subject: [PATCH 182/195] Fix storing subflow credential type when input has multiple types Fixes #3749 --- .../@node-red/editor-client/src/js/ui/subflow.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 3aeb7151f..10fece9a2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -931,6 +931,7 @@ RED.subflow = (function() { function buildEnvUIRow(row, tenv, ui, node) { + console.log(tenv, ui) ui.label = ui.label||{}; if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { ui.type = "cred"; @@ -991,6 +992,17 @@ RED.subflow = (function() { default: inputType }) input.typedInput('value',val.value) + if (inputType === 'cred') { + if (node.credentials) { + if (node.credentials[tenv.name]) { + input.typedInput('value', node.credentials[tenv.name]); + } else if (node.credentials['has_'+tenv.name]) { + input.typedInput('value', "__PWRD__") + } else { + input.typedInput('value', ""); + } + } + } } else { input.val(val.value) } From 6ff2232df3b0cfff1bc7e44cd97ac7f76054d055 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 09:27:45 +0100 Subject: [PATCH 183/195] Ensure node icon shade has properly rounded corners --- .../@node-red/editor-client/src/js/ui/view.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 5a9df8ed7..8c7ea22a3 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4580,12 +4580,10 @@ RED.view = (function() { icon_groupEl.setAttribute("y",0); icon_groupEl.style["pointer-events"] = "none"; node[0][0].__iconGroup__ = icon_groupEl; - var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","rect"); + var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","path"); icon_shade.setAttribute("x",0); icon_shade.setAttribute("y",0); icon_shade.setAttribute("class","red-ui-flow-node-icon-shade") - icon_shade.setAttribute("width",30); - icon_shade.setAttribute("height",Math.min(50,d.h-4)); icon_groupEl.appendChild(icon_shade); node[0][0].__iconShade__ = icon_shade; @@ -4878,9 +4876,20 @@ RED.view = (function() { } icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); - this.__iconShade__.setAttribute("height", d.h ); + + + const iconShadeHeight = d.h + const iconShadeWidth = 30 + this.__iconShade__.setAttribute("d", hideLabel ? + `M5 0 h${iconShadeWidth-10} a 5 5 0 0 1 5 5 v${iconShadeHeight-10} a 5 5 0 0 1 -5 5 h-${iconShadeWidth-10} a 5 5 0 0 1 -5 -5 v-${iconShadeHeight-10} a 5 5 0 0 1 5 -5` : ( + "right" === d._def.align ? + `M 0 0 h${iconShadeWidth-5} a 5 5 0 0 1 5 5 v${iconShadeHeight-10} a 5 5 0 0 1 -5 5 h-${iconShadeWidth-5} v-${iconShadeHeight}` : + `M5 0 h${iconShadeWidth-5} v${iconShadeHeight} h-${iconShadeWidth-5} a 5 5 0 0 1 -5 -5 v-${iconShadeHeight-10} a 5 5 0 0 1 5 -5` + ) + ) + this.__iconShadeBorder__.style.display = hideLabel?'none':'' this.__iconShadeBorder__.setAttribute("d", - "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0.5 : 29.5) + " "+(d.selected?1:0.5)+" l 0 " + (d.h - (d.selected?2:1)) ); faIcon.attr("y",(d.h+13)/2); } From 4c784af55db40455e77274c24eadfd58b78fffba Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:12:00 +0100 Subject: [PATCH 184/195] Update dependencies --- package.json | 20 +++++++++---------- .../@node-red/editor-api/package.json | 2 +- .../@node-red/registry/package.json | 2 +- .../node_modules/@node-red/util/package.json | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 1f5759634..e585567ed 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", - "i18next": "21.8.10", + "i18next": "21.8.13", "iconv-lite": "0.6.3", "is-utf8": "0.2.1", "js-yaml": "4.1.0", @@ -59,7 +59,7 @@ "media-typer": "1.1.0", "memorystore": "1.6.7", "mime": "3.0.0", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34", "mqtt": "4.3.7", "multer": "1.4.5-lts.1", @@ -69,14 +69,14 @@ "nopt": "5.0.0", "oauth2orize": "1.11.1", "on-headers": "1.0.2", - "passport": "0.5.2", + "passport": "0.6.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "raw-body": "2.5.1", "semver": "7.3.7", "tar": "6.1.11", "tough-cookie": "4.0.0", - "uglify-js": "3.16.0", + "uglify-js": "3.16.2", "uuid": "8.3.2", "ws": "7.5.6", "xml2js": "0.4.23" @@ -85,7 +85,7 @@ "bcrypt": "5.0.1" }, "devDependencies": { - "dompurify": "2.3.8", + "dompurify": "2.3.9", "grunt": "1.5.3", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.4.3", @@ -95,7 +95,7 @@ "grunt-contrib-concat": "2.1.0", "grunt-contrib-copy": "1.0.0", "grunt-contrib-jshint": "3.2.0", - "grunt-contrib-uglify": "5.2.1", + "grunt-contrib-uglify": "5.2.2", "grunt-contrib-watch": "1.1.0", "grunt-jsdoc": "2.4.1", "grunt-jsdoc-to-markdown": "6.0.0", @@ -108,17 +108,17 @@ "i18next-http-backend": "1.4.1", "jquery-i18next": "1.2.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "4.0.17", + "marked": "4.0.18", "minami": "1.2.3", "mocha": "9.2.2", "node-red-node-test-helper": "^0.3.0", - "nodemon": "2.0.16", + "nodemon": "2.0.19", "proxy": "^1.0.2", - "sass": "1.52.3", + "sass": "1.53.0", "should": "13.2.3", "sinon": "11.1.2", "stoppable": "^1.1.0", - "supertest": "6.2.3" + "supertest": "6.2.4" }, "engines": { "node": ">=14" diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 777e15cc3..9056b0a1d 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -31,7 +31,7 @@ "oauth2orize": "1.11.1", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", - "passport": "0.5.2", + "passport": "0.6.0", "ws": "7.5.6" }, "optionalDependencies": { diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index f4808c8ad..2aa0504e1 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -21,6 +21,6 @@ "fs-extra": "10.1.0", "semver": "7.3.7", "tar": "6.1.11", - "uglify-js": "3.16.0" + "uglify-js": "3.16.2" } } diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 6723b9b6c..e4fdb92c0 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -16,11 +16,11 @@ ], "dependencies": { "fs-extra": "10.1.0", - "i18next": "21.8.10", + "i18next": "21.8.13", "json-stringify-safe": "5.0.1", "jsonata": "1.8.6", "lodash.clonedeep": "^4.5.0", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34" } } From 613d34e6e62dfcb1fa9977c324e2dd67697c2cb1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:12:31 +0100 Subject: [PATCH 185/195] Update tourGuide for 3.0 --- .../editor-client/src/js/ui/tour/tourGuide.js | 6 +++--- .../@node-red/editor-client/src/tours/welcome.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 913582c10..406715651 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -437,17 +437,17 @@ RED.tourGuide = (function() { return [ { id: "3_0", - label: "3.0.0-beta.4", + label: "3.0", path: "./tours/welcome.js" }, { id: "2_2", - label: "2.2.0", + label: "2.2", path: "./tours/2.2/welcome.js" }, { id: "2_1", - label: "2.1.0", + label: "2.1", path: "./tours/2.1/welcome.js" } ]; diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 35a276606..7d095ba8c 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,15 +1,15 @@ export default { - version: "3.0.0-beta.4", + version: "3.0.0", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 3.0 Beta 4!", - "ja": "Node-RED 3.0 ベータ4へようこそ!" + "en-US": "Welcome to Node-RED 3.0!", + "ja": "Node-RED 3.0へようこそ!" }, description: { - "en-US": "

        This is another final beta release of Node-RED 3.0.

        Let's take a moment to discover the new features in this release.

        ", - "ja": "

        これはNode-RED 3.0のもう一つの最後のベータリリースです。

        本リリースの新機能を見つけてみましょう。

        " + "en-US": "

        Let's take a moment to discover the new features in this release.

        ", + "ja": "

        本リリースの新機能を見つけてみましょう。

        " } }, { From ee378ea0c4b0b809f0376601420a872c52477cc3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:18:49 +0100 Subject: [PATCH 186/195] Update version to 3.0 --- package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../node_modules/@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 4 ++-- packages/node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index e585567ed..448501d98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.4", + "version": "3.0.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 9056b0a1d..fd2f7d469 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.4", - "@node-red/editor-client": "3.0.0-beta.4", + "@node-red/util": "3.0.0", + "@node-red/editor-client": "3.0.0", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 1041c9745..a72fedc96 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 68c7099bf..ff0161f86 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 2aa0504e1..f9ab44be8 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.4", + "@node-red/util": "3.0.0", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index c6dc2130e..f33cb5175 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0-beta.4", - "@node-red/util": "3.0.0-beta.4", + "@node-red/registry": "3.0.0", + "@node-red/util": "3.0.0", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index e4fdb92c0..e1b9f7aa2 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 3457e03af..dfd98f6fb 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.4", + "version": "3.0.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0-beta.4", - "@node-red/runtime": "3.0.0-beta.4", - "@node-red/util": "3.0.0-beta.4", - "@node-red/nodes": "3.0.0-beta.4", + "@node-red/editor-api": "3.0.0", + "@node-red/runtime": "3.0.0", + "@node-red/util": "3.0.0", + "@node-red/nodes": "3.0.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From f93fe684c0faf23daeddad4887895beac8011c50 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:26:16 +0100 Subject: [PATCH 187/195] Update changelog --- CHANGELOG.md | 558 ++++----------------------------------------------- 1 file changed, 36 insertions(+), 522 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb99d8e0e..37fba1de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +#### 3.0.0: Milestone Release + +Editor + + - 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 + +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 @@ -185,528 +221,6 @@ Nodes - Watch: Update Watch node to use node-watch module (#3559 #3569) @knolleary - WebSocket: call done after ws disconnects (#3531) @Steve-Mcl - -#### 2.2.2: Maintenance Release - -Nodes - - - Fix "close timed out" error when performing full deploy or modifying broker node. (#3451) @Steve-Mcl - - -#### 2.2.1: Maintenance Release - -Editor - - - Handle mixed-cased filter terms in keyboard shortcut dialog (#3444) @knolleary - - Prevent duplicate links being added between nodes (#3442) @knolleary - - Fix to hide tooltip after removing subflow tab (#3391) @HiroyasuNishiyama - - Fix node validation to be applied to config node (#3397) @HiroyasuNishiyama - - Fix: Dont add wires to undo buffer twice (#3437) @Steve-Mcl - -Runtime - - - Improve module location parsing (of stack info) when adding hook (#3447) @Steve-Mcl - - Fix substitution of NR_NODE_PATH (#3445) @HiroyasuNishiyama - - Remove console.log when ignoring disabled module (#3439) @knolleary - - Improve "Unexpected Node Error" logging (#3446) @Steve-Mcl - -Nodes - - - Debug: Fix no-prototype-builtins bug in debug node and utils (#3394) @Alkarex - - Delay: Fix Japanese message of delay node (#3434) - - Allow nbRateUnits to be undefined when validating (#3443) @knolleary - - Coding help for recently added node-red Predefined Environment Variables (#3440) @Steve-Mcl - - -#### 2.2.0: Milestone Release - -Editor - - - Add editorTheme.tours property to default settings file (#3375) @knolleary - - Remember Zoom level and Sidebar tab selection between sessions (#3361) @knolleary - - Fix timing issue when merging background changes fixes #3364 (#3373) @Steve-Mcl - - Use a nodes palette label in help tree (#3372) @Steve-Mcl - - Subflow: Add labels to OUTPUT nodes (#3352) @ralphwetzel - - Fix vertical align subflow port (#3370) @knolleary - - Make actions list i18n ready and Japanese translation (#3359) @HiroyasuNishiyama - - Update tour for 2.2.0 (#3378) @knolleary - - Include paletteLabel when building search index (#3380) @Steve-Mcl - - Fix opening/closing subflow template not to make subflow changed (#3382) @HiroyasuNishiyama - - Add Japanese translations for v2.2.0 (#3353, #3381) @kazuhitoyokoi - -Runtime - - - Add support for accessing node id & name as environment variable (#3356) @HiroyasuNishiyama - - Clear context contents when switching projects (#3243) @knolleary - -Nodes - - - MQTT: reject invalid topics (#3374) @Steve-Mcl - - Function: Expose node.path property (#3371) @knolleary - - Function: Update `node` declarations in func.d.ts (#3377) @Steve-Mcl - -#### 2.2.0-beta.1: Beta Release - -Editor - - - Add search history to main search box (#3262) @knolleary - - Check availability of type of config node on deploy (#3304) @k-toumura - - Add wire-slice mode to delete wires with Ctrl-RHClick-Drag (#3340) @knolleary - - Wiring keyboard shortcuts (#3288) @knolleary - - Snap nodes on grid using either edge as reference (#3289) @knolleary - - Detach node action (#3338) @knolleary - - Highlight links when selecting nodes (#3323) @knolleary - - Allow multiple links to be selected by ctrl-click (#3294) @knolleary - -Nodes - - - JSON: Let JSON node attempt to parse buffer if it contains a valid string (#3296) @dceejay - - Remove use of verbose flag in core nodes - and use node.debug level instead (#3300) @dceejay - - TCP: Add TLS option to tcp client nodes (#3307) @dceejay - - WebSocket: Implemented support for Websocket Subprotocols in WS Client Node. (#3333) @tobiasoort - -#### 2.1.6: Maintenance Release - -Editor - - - Revert copy-text change and apply alternative fix (#3363) @knolleary - - Update marked to latest (#3362) @knolleary - - fix to make start of property error tooltip messages aligned (#3358) @HiroyasuNishiyama - -Nodes - - - Inject: fix JSON propety validation of inject node (#3349) @HiroyasuNishiyama - - Delay: fix unit value validation of delay node (#3351) @HiroyasuNishiyama - -#### 2.1.5: Maintenance Release - -Runtime - - - Handle reporting error location when stack is truncated (#3346) @knolleary - - Initialize passport when only adminAuth.tokens is set (#3343) @knolleary - - Add log logging (#3342) @knolleary - -Editor - - - Fix copy buttons on the debug window (another method) (#3331) @kazuhitoyokoi - - Add Japanese translations for hidden flow (#3302) @kazuhitoyokoi - - Improve jsonata legacy mode detection regex (#3345) @knolleary - - Fix generating flow name with incrementing number (#3347) @knolleary - - resume focus after import/export dialog close (#3337) @HiroyasuNishiyama - - Fix findPreviousVisibleTab action (#3321) @knolleary - - Fix storing hidden tab state when not hidden via action (#3312) @knolleary - - Avoid adding empty env properties to tabs/groups (#3311) @knolleary - - Fix hide icon in tour guide (#3301) @kazuhitoyokoi - -Nodes - - - File: Update file node examples according to node name change (#3335) @HiroyasuNishiyama - - Filter (RBE): Fix for filter node narrrowbandEq mode start condition failure (#3339) @dceejay - - Function: Prevent function scrollbar from obscuring expand button (#3348) @knolleary - - Function: load extralibs when expanding monaco. fixes #3319 (#3334) @Steve-Mcl - - Function: Update Function to use correct api to access env vars (#3310) @knolleary - - HTTP Request: Fix basic auth with empty username or password (#3325) @hardillb - - Inject: Fix incorrect clearing of blank payload property in Inject node (#3322) @knolleary - - Link Call: add link call example (#3336) @HiroyasuNishiyama - - WebSocket: Only setup ws client heartbeat once it is connected (#3344) @knolleary - - Update Japanese translations in node help (#3332) @kazuhitoyokoi - -#### 2.1.4: Maintenance Release - -Runtime - - - fix env var access using $parent for groups (#3278) @HiroyasuNishiyama - - Add proper error handling for 404 errors when serving debug files (#3277) @knolleary - - Add Japanese translations for Node-RED v2.1.0-beta.1 (#3179) @kazuhitoyokoi - - Include full user object on login audit events (#3269) @knolleary - - Remove styling from de locale files (#3237) @knolleary - -Editor - - - Change tab hide button icon to an eye and add search option (#3282) @knolleary - - Fix i18n handling of namespaces with spaces in (#3281) @knolleary - - Trigger change event when autoComplete fills in input (#3280) @knolleary - - Apply CN i18n fix (#3279) @knolleary - - fix select menu label of config node to use paletteLabel (#3273) @HiroyasuNishiyama - - fix removed tab not to cause node conflict (#3275) @HiroyasuNishiyama - - Group diff fix (#3239) @knolleary - - Only toggle disabled workspace flag if on activeWorkspace (#3252) @knolleary - - Do not show status for disabled nodes (#3253) @knolleary - - Set dimension value for tour guide (#3265) @kazuhitoyokoi - - Avoid redundant initialisation of TypedInput type (#3263) @knolleary - - Don't let themes change flow port label color (#3270) @bonanitech - - Fix treeList gutter calculation to handle floating gutters (#3238) @knolleary - -Nodes - -- Debug: Handle RegExp types in Debug sidebar (#3251) @knolleary -- Delay: fix 2nd output when in rate limit per topic modes (#3261) @dceejay -- Link: fix to show link target when selected (#3267) @HiroyasuNishiyama -- Inject: Do not modify inject node props in oneditprepare (#3242) @knolleary -- HTTP Request: HTTP Basic Auth should always add : between username and password even if empty (#3236) @hardillb - -#### 2.1.3: Maintenance Release - -Runtime - - - Update gen-publish script to update 'next' tag for main releases - - Add environment variable to enable/disable tours (#3221) @hardillb - - Fix loading non-default language files leaving runtime in wrong locale (#3225) @knolleary - -Editor - - - Refresh editor settings whenever a node is added or enabled (#3227) @knolleary - - Revert spinner css change that made it shrink in some cases (#3229) @knolleary - - Fix import notification message when importing config nodes (#3224) @knolleary - - Handle changing types of TypedInput repeatedly (#3223) @knolleary - - -#### 2.1.2: Maintenance Release - - -Runtime - - - node-red-pi: Remove bash dependency (#3216) @a16bitsysop - -Editor - - - Improved regex for markdown renderer (#3213) @GerwinvBeek - - Fix TypedInput initialisation (#3220) @knolleary - -Nodes - - - MQTT: fix datatype in node config not used. fixes #3215 (#3219) @Steve-Mcl - -#### 2.1.1: Maintenance Release - -Editor - - - Ensure tourGuide popover doesn't fall offscreen (#3212) @knolleary - - Fix issue with old inject nodes that migrated topic to 'string' type (#3210) @knolleary - - Add cache-busting query params to index.mst (#3211) @knolleary - - Fix TypedInput validation of type without options (#3207) @knolleary - -#### 2.1.0: Milestone Release - -Editor - - - Position popover properly on a scrolled page - - Fixes from 2.1.0-beta.2 (#3202) @knolleary - -Nodes - -- Link Out: Fix saving link out node links (#3201) @knolleary - - Switch: Refix #3170 - copy switch rule type when adding new rule - - TCP Request: Add string option to TCP request node output (#3204) @dceejay - -#### 2.1.0-beta.2: Beta Release - -Editor - - - Fix switching projects (#3199) @knolleary - - Use locale setting when installing/enabling node (#3198) @knolleary - - Do not show projects-wecome dialog until welcome tour completes (#3197) @knolleary - - Fix converting selection to subflow (#3196) @knolleary - - Avoid conflicts with native browser cmd-ctrl type shortcuts (#3195) @knolleary - - Ensure message tools stay attached to top-level entry in Debug/Context (#3186) @knolleary - - Ensure tab state updates properly when toggling enable state (#3175) @knolleary - - Improve handling of long labels in TreeList (#3176) @knolleary - - Shift-click tab scroll arrows to jump to start/end (#3177) @knolleary - -Runtime - - - Update package dependencies - - Update to latest node-red-admin - -Nodes - - - Dynamic MQTT connections (#3189) - - Link: Filter out Link Out Return nodes in Link In edit dialog Fixes #3187 - - Link: Fix link call label (#3200) @knolleary - - Debug: Redesign debug filter options and make them persistant (#3183) @knolleary - - Inject: Widen Inject interval box for >1 digit (#3184) @knolleary - - Switch: Fix rule focus when switch 'otherwise' rule is used (#3185) @knolleary - -#### 2.1.0-beta.1: Beta Release - -Editor - - - Add Tour Guide component (#3136) @knolleary - - Allow tabs to be hidden (#3120) @knolleary - - Add align actions to editor (#3110) @knolleary - - Add support of environment variable for tab & group (#3112) @HiroyasuNishiyama - - Add autoComplete widget and add to TypedInput for msg. props (#3171) @knolleary - - Render node documentation to node-red style guide when written in markdown. (#3169) @Steve-Mcl - - Allow colouring of tab icon svg (#3140) @harmonic7 - - Restore tab selection after merging conflicts (#3151) @GerwinvBeek - - Fix serving of theme files on Windows (#3154) @knolleary - - Ensure config-node select inherits width properly from input (#3155) @knolleary - - Do better remembering TypedInput values whilst switching types (#3159) @knolleary - - Update monaco to 0.28.1 (#3153) @knolleary - - Improve themeing of tourGuide (#3161) @knolleary - - Allow a node to specify a filter for the config nodes it can pick from (#3160) @knolleary - - Allow RED.notify.update to modify any notification setting (#3163) @knolleary - - Fix typo in ko editor.json Fixes #3119 - - Improve RED.actions api to ensure actions cannot be overridden - - Ensure treeList row has suitable min-height when no content Fixes #3109 - - Refactor edit dialogs to use separate edit panes - - Ensure type select button is not focussable when TypedInput only has one type - - Place close tab link in front of fade - -Runtime - - - Improve error reporting with oauth login strategies (#3148) @knolleary - - Add allowUpdate feature to externalModules.palette (#3143) @knolleary - - Improve node install error reporting (#3158) @knolleary - - Improve unit test coverage (#3168) @knolleary - - Allow coreNodesDir to be set to false (#3149) @hardillb - - Update package dependencies - - uncaughtException debug improvements (#3146) @renatojuniorrs - -Nodes - - - Change: Add option to deep-clone properties in Change node (#3156) @knolleary - - Delay: Add push to front of rate limit queue. (#3069) @dceejay - - File: Add paletteLabel to file nodes to make read/write more obvious (#3157) @knolleary - - HTTP Request: Extend HTTP request node to log detailed timing information (#3116) @k-toumura - - HTTP Response: Fix sizing of HTTP Response header fields (#3164) @knolleary - - Join: Support for msg.restartTimeout (#3121) @magma1447 - - Link Call: Add Link Call node (#3152) @knolleary - - Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary - - Delay node: add option to send intermediate messages on separate output (#3166) @knolleary - - Typo in http request set method translation (#3173) @mailsvb - -#### 2.0.6: Maintenance Release - -Editor - - - Fix typo in ko editor.json Fixes #3119 - - Change fade color when hovering an inactive tab (#3106) @bonanitech - - Ensure treeList row has suitable min-height when no content Fixes #3109 - -Runtime - - - Update tar to latest (#3128) @aksswami - - Give passport verify callback the same arity as the original callback (#3117) @dschmidt - - Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers - -#### 2.0.5: Maintenance Release - -Editor - - - Remove default ctrl-enter keybinding from monaco editor Fixes #3093 - -Runtime - - - Update tar dependency - - Add support for maintenance streams in generate-publish-script - - -Nodes - - - Fix regression in Join node when manual joining array with msg.parts present Fixes #3096 - -#### 2.0.4: Maintenance Release - -Editor - - - Fix tab fade CSS for when using themes (#3085) @bonanitech - - Handle just-copied-but-not-deployed node with credentials in editor Fixes #3090 - -Nodes - - - Filter: Fix RBE node handling of default topi property Fixes #3087 - - HTTP Request: Handle partially encoded url query strings in request node - - HTTP Request: Fix support for supplied CA certs (#3089) @hardillb - - HTTP Request: Ensure TLS Cert is used (#3092) @hardillb - - Inject: Fix inject now button unable to send empty props - - Inject: Inject now button success notification should use label with updated props - -#### 2.0.3: Maintenance Release - -Nodes - - - HTML: Fix HTML parsing when body is included in the select tag Fixes #3079 - - HTTP Request: Preserve case of user-provided http headers in request node Fixes #3081 - - HTTP Request: Set decompress to false for HTTP Request to keep 1.x compatibility Fixes #3083 - - HTTP Request: Add unit tests for HTTP Request encodeURI and error response - - HTTP Request: Do not throw HTTP errors in request node Fixes #3082 - - HTTP Request: Ensure uri is properly encoded before passing to got module Fixes #3080 - -#### 2.0.2: Maintenance Release - -Runtime - - - Use file:// url with dynamic import - - Detect if agent-base has patched https.request and undo it Fixes #3072 - -Editor - - - Fix tab fade css because Safari Fixes #3073 - - Fix error closing library dialog with monaco - - Handle other error types in Manage Palette view - - -Nodes - - - HTTP Request node - ignore invalid cookies rather than fail request Fixes #3075 - - Fix msg.reset handling in Delay node Fixes #3074 - -#### 2.0.1: Maintenance Release - -Nodes - - - Function: Ensure default module export is exposed in Function node - -#### 2.0.0: Milestone Release - -**Migration from 1.x** - - - Node-RED now requires Node.js 12.x or later. - - - The following nodes have had significant dependency updates. Unless stated, - they should be fully backward compatible. - - - RBE: Relabelled as 'filter' to make it more discoverable and made part of - the core palette, rather than as a separate module. - - Tail: This node has been removed from the default palette. You can reinstall it - from node-red-node-tail - - HTTP Request: Reimplemented with a different underlying module. We have - tried to maintain 100% functional compatibility, but it is possible - some edge cases remain. - - JSON: The schema validation option no longer supports JSON-Schema draft-04 - - HTML: Its underlying module has had a major version update. Should be fully - backward compatible. - - - `functionExternalModules` is now enabled by default for new installs. - If you have an existing settings file that contains this setting, you will - need to set it to `true` yourself. - - The external modules will now get installed in your Node-RED user directory, - (`~/.node-red`) rather than in a subdirectory. This means all dependencies will - be listed in your top-level `package.json`. If you have existing external modules, - they will get reinstalled to the new location when you first run Node-RED 2.0. - - -Runtime - - - Fix missing dependencies (#3052, #2057) @kazuhitoyokoi - - Ensure node.types is defined if node html file missing - - Fix reporting of type_already_registered error - - Move install location of external modules (#3064) @knolleary - -Editor - - - Update translations (#3063) @kazuhitoyokoi - - Add a slight fade to tab labels that overflow - - Show config node details when selected in outliner - - Fix layout of info outliner for subflow entries - -Nodes - - - Delay: let `msg.flush` specify how many messages to flush from node (#3059) @dceejay - - Function: external modules is now enabled by default (#3065) @knolleary - - Function: external modules now supports both ES6 and CJS modules (#3065) @knolleary - - WebSocket: add option for client node to send automatic pings (#3056) @knolleary - - -##### 2.0.0-beta.2: Beta Release - -Runtime - - - Add `node-red admin init` (via `node-red-admin@2.1.0`) - - Move to GH Actions rather than Travis for build (#3042) @knolleary - -Editor - - - Include hasUser=false config nodes when exporting whole flow (#3048) - - Emit nodes:change for any updated config node when node deleted/added - - Fix padding of compact notification Closes #3045 - - Ensure any html in changelog is escaped before displaying - - Add support for Map/Set property types on Debug (#3040) @knolleary - - Add 'theme' to default settings file - - Add RED.view.annotations api (#3032) @knolleary - - Update monaco editor to V0.25.2 (#3031) @Steve-Mcl - - Lower tray zIndex when overlay tray being opened Fixes #3019 - - Reduce z-Index of Function expand buttons to prevent overlap Part of #3019 - - Ensure RED.clipboard.import displays the right library Fixes #3021 - - Batch messages sent over comms to prevent flooding (#3025) @knolleary - - Allow RED.popover.panel to specify a closeButton to ignore click events on - - Use browser default language for initial page load - - Add css var for node font color - - Fix label padding of toggleButton - - Give sidebar open tab a bit more room for its label - - Various Monaco updates (#3015) @Steve-Mcl - - Log readOnly on startup (#3024) @sammachin - - Translation updates (#3020 #3022) @HiroyasuNishiyama @kazuhitoyokoi - -Nodes - - - HTTP Request: Fix proxy handling (#3044) @hardillb - - HTTP Request: Handle basic auth with @ in username (#3017) @hardillb - - Add Japanese translation for file-in node (#3037 #3039) @kazuhitoyokoi - - File In: Add option for file-in node to include all properties (default off) (#3035) @dceejay - - Exec: add windowsHide option to hide windows under Windows (#3026) @natcl - - Support loading external module sub path Fixes #3023 - -##### 2.0.0-beta.1: Beta Release - - - -Runtime - - - [MAJOR] Set minimum node version to 12. - - [MAJOR] Fix flowfile name to flows.json in settings (#2951) @dceejay - - [MAJOR] Update to latest i18n in editor and runtime (#2940) @knolleary - - [MAJOR] Deprecate usage of httpRoot (#2953) @knolleary - - Add pre/postInstall hooks to npm install handling (#2936) @knolleary - - Add engine-strict flag to npm install args (#2965) @nileio - - Restructure default settings.js to be more organised (#3012) @knolleary - - Ensure httpServerOptions gets applied to ALL the express apps - - Allow RED.settings.set to replace string property with object property - - Update debug tests to handle compact comms format - - Updates to encode/decode message when passed over debug comms link - - Remove all input event listeners on a node once it is closed - - Move hooks to util package - - Rework hooks structure to be a linkedlist - - Update dependencies (#2922) @knolleary - -Editor - - - [MAJOR] Change node id generation to give fixed length values without '.' (#2987) @knolleary - - [MAJOR] Add Monaco code editor (#2971) @Steve-Mcl - - Update to latest Monaco (#3007) @Steve-Mcl - - Update Node-RED Function typings in Monaco (#3008) @Steve-Mcl - - Add css named variables for certain key colours (#2994) @knolleary - - Improve contrast of export dialog JSON font color - - Switch editableList buttons from to