mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #2148 from node-red/new-export-dialog
Updated Library UX
This commit is contained in:
commit
369f8b3fe0
@ -93,9 +93,8 @@ module.exports = {
|
|||||||
// Library
|
// Library
|
||||||
var library = require("./library");
|
var library = require("./library");
|
||||||
library.init(runtimeAPI);
|
library.init(runtimeAPI);
|
||||||
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
|
editorApp.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
|
||||||
editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
|
editorApp.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
|
||||||
editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
|
|
||||||
|
|
||||||
|
|
||||||
// Credentials
|
// Credentials
|
||||||
|
@ -25,23 +25,12 @@ module.exports = {
|
|||||||
init: function(_runtimeAPI) {
|
init: function(_runtimeAPI) {
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
},
|
},
|
||||||
|
|
||||||
getAll: function(req,res) {
|
|
||||||
var opts = {
|
|
||||||
user: req.user,
|
|
||||||
type: 'flows'
|
|
||||||
}
|
|
||||||
runtimeAPI.library.getEntries(opts).then(function(result) {
|
|
||||||
res.json(result);
|
|
||||||
}).catch(function(err) {
|
|
||||||
apiUtils.rejectHandler(req,res,err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getEntry: function(req,res) {
|
getEntry: function(req,res) {
|
||||||
var opts = {
|
var opts = {
|
||||||
user: req.user,
|
user: req.user,
|
||||||
type: req.params[0],
|
library: req.params[0],
|
||||||
path: req.params[1]||""
|
type: req.params[1],
|
||||||
|
path: req.params[2]||""
|
||||||
}
|
}
|
||||||
runtimeAPI.library.getEntry(opts).then(function(result) {
|
runtimeAPI.library.getEntry(opts).then(function(result) {
|
||||||
if (typeof result === "string") {
|
if (typeof result === "string") {
|
||||||
@ -62,8 +51,9 @@ module.exports = {
|
|||||||
saveEntry: function(req,res) {
|
saveEntry: function(req,res) {
|
||||||
var opts = {
|
var opts = {
|
||||||
user: req.user,
|
user: req.user,
|
||||||
type: req.params[0],
|
library: req.params[0],
|
||||||
path: req.params[1]||""
|
type: req.params[1],
|
||||||
|
path: req.params[2]||""
|
||||||
}
|
}
|
||||||
// TODO: horrible inconsistencies between flows and all other types
|
// TODO: horrible inconsistencies between flows and all other types
|
||||||
if (opts.type === "flows") {
|
if (opts.type === "flows") {
|
||||||
|
@ -55,9 +55,6 @@
|
|||||||
"export" : "Exportieren",
|
"export" : "Exportieren",
|
||||||
"search" : "Flows durchsuchen",
|
"search" : "Flows durchsuchen",
|
||||||
"searchInput" : "durchsuchen Sie Ihre Flows",
|
"searchInput" : "durchsuchen Sie Ihre Flows",
|
||||||
"clipboard" : "Zwischenablage",
|
|
||||||
"library" : "Bibliothek",
|
|
||||||
"examples" : "Beispiele",
|
|
||||||
"subflows" : "Subflow",
|
"subflows" : "Subflow",
|
||||||
"createSubflow" : "Subflow erstellen",
|
"createSubflow" : "Subflow erstellen",
|
||||||
"selectionToSubflow" : "Auswahl für Subflow",
|
"selectionToSubflow" : "Auswahl für Subflow",
|
||||||
@ -136,8 +133,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard" : {
|
"clipboard" : {
|
||||||
|
"clipboard" : "Zwischenablage",
|
||||||
"nodes" : "Knoten",
|
"nodes" : "Knoten",
|
||||||
"selectNodes" : "Wählen Sie den Text oben aus, und kopieren Sie die Datei in die Zwischenablage.",
|
|
||||||
"pasteNodes" : "Knoten hier einfügen",
|
"pasteNodes" : "Knoten hier einfügen",
|
||||||
"importNodes" : "Knoten importieren",
|
"importNodes" : "Knoten importieren",
|
||||||
"exportNodes" : "Knoten in Zwischenablage exportieren",
|
"exportNodes" : "Knoten in Zwischenablage exportieren",
|
||||||
@ -297,22 +294,19 @@
|
|||||||
"managePalette" : "Palette verwalten"
|
"managePalette" : "Palette verwalten"
|
||||||
},
|
},
|
||||||
"library" : {
|
"library" : {
|
||||||
|
"library" : "Bibliothek",
|
||||||
"openLibrary" : "Bibliothek öffnen ...",
|
"openLibrary" : "Bibliothek öffnen ...",
|
||||||
"saveToLibrary" : "In Bibliothek speichern ...",
|
"saveToLibrary" : "In Bibliothek speichern ...",
|
||||||
"typeLibrary" : "__type__, Bibliothek",
|
"typeLibrary" : "__type__, Bibliothek",
|
||||||
"unnamedType" : "Unbenannt __type__",
|
"unnamedType" : "Unbenannt __type__",
|
||||||
"exportToLibrary" : "Knoten in Bibliothek exportieren",
|
|
||||||
"dialogSaveOverwrite" : "Ein __libraryType__ mit dem Namen __libraryName__ ist bereits vorhanden. Überschreiben?",
|
"dialogSaveOverwrite" : "Ein __libraryType__ mit dem Namen __libraryName__ ist bereits vorhanden. Überschreiben?",
|
||||||
"invalidFilename" : "Ungültiger Dateiname",
|
"invalidFilename" : "Ungültiger Dateiname",
|
||||||
"savedNodes" : "Gespeicherte Knoten",
|
"savedNodes" : "Gespeicherte Knoten",
|
||||||
"savedType" : "Gespeichert __type__",
|
"savedType" : "Gespeichert __type__",
|
||||||
"saveFailed" : "Speichern fehlgeschlagen: __message__",
|
"saveFailed" : "Speichern fehlgeschlagen: __message__",
|
||||||
"filename" : "Name der Datei",
|
"types": {
|
||||||
"folder" : "Ordner",
|
"examples" : "Beispiele"
|
||||||
"filenamePlaceholder" : "Datei",
|
}
|
||||||
"fullFilenamePlaceholder" : "a/b/Datei",
|
|
||||||
"folderPlaceholder" : "a/b",
|
|
||||||
"breadcrumb" : "Bibliothek"
|
|
||||||
},
|
},
|
||||||
"palette" : {
|
"palette" : {
|
||||||
"noInfo" : "Keine Informationen verfügbar",
|
"noInfo" : "Keine Informationen verfügbar",
|
||||||
|
@ -61,9 +61,6 @@
|
|||||||
"export": "Export",
|
"export": "Export",
|
||||||
"search": "Search flows",
|
"search": "Search flows",
|
||||||
"searchInput": "search your flows",
|
"searchInput": "search your flows",
|
||||||
"clipboard": "Clipboard",
|
|
||||||
"library": "Library",
|
|
||||||
"examples": "Examples",
|
|
||||||
"subflows": "Subflows",
|
"subflows": "Subflows",
|
||||||
"createSubflow": "Create Subflow",
|
"createSubflow": "Create Subflow",
|
||||||
"selectionToSubflow": "Selection to Subflow",
|
"selectionToSubflow": "Selection to Subflow",
|
||||||
@ -156,6 +153,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
|
"clipboard": "Clipboard",
|
||||||
"nodes": "Nodes",
|
"nodes": "Nodes",
|
||||||
"node": "__count__ node",
|
"node": "__count__ node",
|
||||||
"node_plural": "__count__ nodes",
|
"node_plural": "__count__ nodes",
|
||||||
@ -165,7 +163,6 @@
|
|||||||
"flow_plural": "__count__ flows",
|
"flow_plural": "__count__ flows",
|
||||||
"subflow": "__count__ subflow",
|
"subflow": "__count__ subflow",
|
||||||
"subflow_plural": "__count__ subflows",
|
"subflow_plural": "__count__ subflows",
|
||||||
"selectNodes": "Select the text above and copy to the clipboard.",
|
|
||||||
"pasteNodes": "Paste flow json or",
|
"pasteNodes": "Paste flow json or",
|
||||||
"selectFile": "select a file to import",
|
"selectFile": "select a file to import",
|
||||||
"importNodes": "Import nodes",
|
"importNodes": "Import nodes",
|
||||||
@ -184,7 +181,11 @@
|
|||||||
"all":"all flows",
|
"all":"all flows",
|
||||||
"compact":"compact",
|
"compact":"compact",
|
||||||
"formatted":"formatted",
|
"formatted":"formatted",
|
||||||
"copy": "Export to clipboard"
|
"copy": "Copy to clipboard",
|
||||||
|
"export": "Export to library",
|
||||||
|
"exportAs": "Export as",
|
||||||
|
"overwrite": "Replace",
|
||||||
|
"exists": "<p><b>\"__file__\"</b> already exists.</p><p>Do you want to replace it?</p>"
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"import": "Import to",
|
"import": "Import to",
|
||||||
@ -351,22 +352,21 @@
|
|||||||
"managePalette": "Manage palette"
|
"managePalette": "Manage palette"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
|
"library": "Library",
|
||||||
"openLibrary": "Open Library...",
|
"openLibrary": "Open Library...",
|
||||||
"saveToLibrary": "Save to Library...",
|
"saveToLibrary": "Save to Library...",
|
||||||
"typeLibrary": "__type__ library",
|
"typeLibrary": "__type__ library",
|
||||||
"unnamedType": "Unnamed __type__",
|
"unnamedType": "Unnamed __type__",
|
||||||
"exportToLibrary": "Export nodes to library",
|
"exportedToLibrary": "Nodes exported to library",
|
||||||
"dialogSaveOverwrite": "A __libraryType__ called __libraryName__ already exists. Overwrite?",
|
"dialogSaveOverwrite": "A __libraryType__ called __libraryName__ already exists. Overwrite?",
|
||||||
"invalidFilename": "Invalid filename",
|
"invalidFilename": "Invalid filename",
|
||||||
"savedNodes": "Saved nodes",
|
"savedNodes": "Saved nodes",
|
||||||
"savedType": "Saved __type__",
|
"savedType": "Saved __type__",
|
||||||
"saveFailed": "Save failed: __message__",
|
"saveFailed": "Save failed: __message__",
|
||||||
"filename": "Filename",
|
"types": {
|
||||||
"folder": "Folder",
|
"local": "Local",
|
||||||
"filenamePlaceholder": "file",
|
"examples": "Examples"
|
||||||
"fullFilenamePlaceholder": "a/b/file",
|
}
|
||||||
"folderPlaceholder": "a/b",
|
|
||||||
"breadcrumb": "Library"
|
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "no information available",
|
"noInfo": "no information available",
|
||||||
|
@ -59,9 +59,6 @@
|
|||||||
"export": "書き出し",
|
"export": "書き出し",
|
||||||
"search": "ノードを検索",
|
"search": "ノードを検索",
|
||||||
"searchInput": "ノードを検索",
|
"searchInput": "ノードを検索",
|
||||||
"clipboard": "クリップボード",
|
|
||||||
"library": "ライブラリ",
|
|
||||||
"examples": "サンプル",
|
|
||||||
"subflows": "サブフロー",
|
"subflows": "サブフロー",
|
||||||
"createSubflow": "サブフローを作成",
|
"createSubflow": "サブフローを作成",
|
||||||
"selectionToSubflow": "選択部分をサブフロー化",
|
"selectionToSubflow": "選択部分をサブフロー化",
|
||||||
@ -154,6 +151,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
|
"clipboard": "クリップボード",
|
||||||
"nodes": "ノード",
|
"nodes": "ノード",
|
||||||
"node": "__count__ 個のノード",
|
"node": "__count__ 個のノード",
|
||||||
"node_plural": "__count__ 個のノード",
|
"node_plural": "__count__ 個のノード",
|
||||||
@ -163,7 +161,6 @@
|
|||||||
"flow_plural": "__count__ 個のフロー",
|
"flow_plural": "__count__ 個のフロー",
|
||||||
"subflow": "__count__ 個のサブフロー",
|
"subflow": "__count__ 個のサブフロー",
|
||||||
"subflow_plural": "__count__ 個のサブフロー",
|
"subflow_plural": "__count__ 個のサブフロー",
|
||||||
"selectNodes": "上のテキストを選択し、クリップボードへコピーしてください",
|
|
||||||
"pasteNodes": "JSON形式のフローデータを貼り付けてください",
|
"pasteNodes": "JSON形式のフローデータを貼り付けてください",
|
||||||
"selectFile": "読み込むファイルを選択してください",
|
"selectFile": "読み込むファイルを選択してください",
|
||||||
"importNodes": "フローをクリップボートから読み込み",
|
"importNodes": "フローをクリップボートから読み込み",
|
||||||
@ -349,22 +346,19 @@
|
|||||||
"managePalette": "パレットの管理"
|
"managePalette": "パレットの管理"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
|
"library": "ライブラリ",
|
||||||
"openLibrary": "ライブラリを開く",
|
"openLibrary": "ライブラリを開く",
|
||||||
"saveToLibrary": "ライブラリへ保存",
|
"saveToLibrary": "ライブラリへ保存",
|
||||||
"typeLibrary": "__type__ ライブラリ",
|
"typeLibrary": "__type__ ライブラリ",
|
||||||
"unnamedType": "名前なし __type__",
|
"unnamedType": "名前なし __type__",
|
||||||
"exportToLibrary": "ライブラリへフローを書き出す",
|
|
||||||
"dialogSaveOverwrite": "__libraryName__ という __libraryType__ は既に存在しています 上書きしますか?",
|
"dialogSaveOverwrite": "__libraryName__ という __libraryType__ は既に存在しています 上書きしますか?",
|
||||||
"invalidFilename": "不正なファイル名",
|
"invalidFilename": "不正なファイル名",
|
||||||
"savedNodes": "フローを保存しました",
|
"savedNodes": "フローを保存しました",
|
||||||
"savedType": "__type__ を保存しました",
|
"savedType": "__type__ を保存しました",
|
||||||
"saveFailed": "保存に失敗しました: __message__",
|
"saveFailed": "保存に失敗しました: __message__",
|
||||||
"filename": "ファイル名",
|
"types": {
|
||||||
"folder": "フォルダ",
|
"examples": "サンプル"
|
||||||
"filenamePlaceholder": "ファイル",
|
}
|
||||||
"fullFilenamePlaceholder": "a/b/file",
|
|
||||||
"folderPlaceholder": "a/b",
|
|
||||||
"breadcrumb": "ライブラリ"
|
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "情報がありません",
|
"noInfo": "情報がありません",
|
||||||
|
@ -58,9 +58,6 @@
|
|||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"search": "플로우 겅색",
|
"search": "플로우 겅색",
|
||||||
"searchInput": "플로우 검색",
|
"searchInput": "플로우 검색",
|
||||||
"clipboard": "클립보드",
|
|
||||||
"library": "라이브러리",
|
|
||||||
"examples": "예시",
|
|
||||||
"subflows": "보조 플로우",
|
"subflows": "보조 플로우",
|
||||||
"createSubflow": "보조 플로우 생성",
|
"createSubflow": "보조 플로우 생성",
|
||||||
"selectionToSubflow": "보조 플로우 선택",
|
"selectionToSubflow": "보조 플로우 선택",
|
||||||
@ -148,6 +145,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
|
"clipboard": "클립보드",
|
||||||
"nodes": "노드",
|
"nodes": "노드",
|
||||||
"node": "__count__ 개의 노드",
|
"node": "__count__ 개의 노드",
|
||||||
"node_plural": "__count__ 개의 노드",
|
"node_plural": "__count__ 개의 노드",
|
||||||
@ -157,7 +155,6 @@
|
|||||||
"flow_plural": "__count__ 개의 플로우",
|
"flow_plural": "__count__ 개의 플로우",
|
||||||
"subflow": "__count__ 개의 서브 플로우",
|
"subflow": "__count__ 개의 서브 플로우",
|
||||||
"subflow_plural": "__count__ 개의 서브 플로우",
|
"subflow_plural": "__count__ 개의 서브 플로우",
|
||||||
"selectNodes": "텍스트를 선택하고 클립보드에 복사하세요",
|
|
||||||
"pasteNodes": "여기에 노드를 붙여넣기 하세요",
|
"pasteNodes": "여기에 노드를 붙여넣기 하세요",
|
||||||
"selectFile": "불러올 파일을 선택하세요",
|
"selectFile": "불러올 파일을 선택하세요",
|
||||||
"importNodes": "노드 불러오기",
|
"importNodes": "노드 불러오기",
|
||||||
@ -338,22 +335,19 @@
|
|||||||
"managePalette": "팔렛트 관리"
|
"managePalette": "팔렛트 관리"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
|
"library": "라이브러리",
|
||||||
"openLibrary": "라이브러리 열기...",
|
"openLibrary": "라이브러리 열기...",
|
||||||
"saveToLibrary": "라이브러리로 저장...",
|
"saveToLibrary": "라이브러리로 저장...",
|
||||||
"typeLibrary": "__type__ 라이브러리",
|
"typeLibrary": "__type__ 라이브러리",
|
||||||
"unnamedType": "이름없는 __type__",
|
"unnamedType": "이름없는 __type__",
|
||||||
"exportToLibrary": "라이브러리로 노드 내보내기",
|
|
||||||
"dialogSaveOverwrite": "__libraryType__이 __libraryName__으로 이미 등록되어있습니다. 덮어쓸까요?",
|
"dialogSaveOverwrite": "__libraryType__이 __libraryName__으로 이미 등록되어있습니다. 덮어쓸까요?",
|
||||||
"invalidFilename": "파일명이 올바르지 않습니다",
|
"invalidFilename": "파일명이 올바르지 않습니다",
|
||||||
"savedNodes": "저장된 노드",
|
"savedNodes": "저장된 노드",
|
||||||
"savedType": "저장된 __type__",
|
"savedType": "저장된 __type__",
|
||||||
"saveFailed": "저장 실패 : __message__",
|
"saveFailed": "저장 실패 : __message__",
|
||||||
"filename": "파일명",
|
"types": {
|
||||||
"folder": "폴더명",
|
"examples": "예시"
|
||||||
"filenamePlaceholder": "파일",
|
}
|
||||||
"fullFilenamePlaceholder": "a/b/file",
|
|
||||||
"folderPlaceholder": "a/b",
|
|
||||||
"breadcrumb": "라이브러리"
|
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "정보 없음",
|
"noInfo": "정보 없음",
|
||||||
|
@ -50,9 +50,6 @@
|
|||||||
"export": "导出",
|
"export": "导出",
|
||||||
"search": "查找流程",
|
"search": "查找流程",
|
||||||
"searchInput": "查找流程",
|
"searchInput": "查找流程",
|
||||||
"clipboard": "剪贴板",
|
|
||||||
"library": "库",
|
|
||||||
"examples": "例子",
|
|
||||||
"subflows": "子流程",
|
"subflows": "子流程",
|
||||||
"createSubflow": "新建子流程",
|
"createSubflow": "新建子流程",
|
||||||
"selectionToSubflow": "将选择部分更改为子流程",
|
"selectionToSubflow": "将选择部分更改为子流程",
|
||||||
@ -100,8 +97,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
|
"clipboard": "剪贴板",
|
||||||
"nodes": "节点",
|
"nodes": "节点",
|
||||||
"selectNodes": "选择上面的文本并复制到剪贴板",
|
|
||||||
"pasteNodes": "在这里粘贴节点",
|
"pasteNodes": "在这里粘贴节点",
|
||||||
"importNodes": "导入节点",
|
"importNodes": "导入节点",
|
||||||
"exportNodes": "导出节点至剪贴板",
|
"exportNodes": "导出节点至剪贴板",
|
||||||
@ -237,6 +234,7 @@
|
|||||||
"managePalette": "管理面板"
|
"managePalette": "管理面板"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
|
"library": "库",
|
||||||
"openLibrary": "打开库...",
|
"openLibrary": "打开库...",
|
||||||
"saveToLibrary": "保存到库...",
|
"saveToLibrary": "保存到库...",
|
||||||
"typeLibrary": "__type__类型库",
|
"typeLibrary": "__type__类型库",
|
||||||
@ -247,12 +245,9 @@
|
|||||||
"savedNodes": "保存的节点",
|
"savedNodes": "保存的节点",
|
||||||
"savedType": "已保存__type__",
|
"savedType": "已保存__type__",
|
||||||
"saveFailed": "保存失败: __message__",
|
"saveFailed": "保存失败: __message__",
|
||||||
"filename": "文件名",
|
"types": {
|
||||||
"folder": "文件夹",
|
"examples": "例子"
|
||||||
"filenamePlaceholder": "文件",
|
}
|
||||||
"fullFilenamePlaceholder": "a/b/文件",
|
|
||||||
"folderPlaceholder": "a/b",
|
|
||||||
"breadcrumb": "库"
|
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "无可用信息",
|
"noInfo": "无可用信息",
|
||||||
|
@ -462,14 +462,8 @@ var RED = (function() {
|
|||||||
null
|
null
|
||||||
]});
|
]});
|
||||||
menuOptions.push(null);
|
menuOptions.push(null);
|
||||||
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),onselect:"core:show-import-dialog"});
|
||||||
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"},
|
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),onselect:"core:show-export-dialog"});
|
||||||
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
|
|
||||||
]});
|
|
||||||
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
|
|
||||||
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
|
|
||||||
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
|
|
||||||
]});
|
|
||||||
menuOptions.push(null);
|
menuOptions.push(null);
|
||||||
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"});
|
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"});
|
||||||
menuOptions.push(null);
|
menuOptions.push(null);
|
||||||
|
@ -24,6 +24,8 @@ RED.clipboard = (function() {
|
|||||||
var disabled = false;
|
var disabled = false;
|
||||||
var popover;
|
var popover;
|
||||||
var currentPopoverError;
|
var currentPopoverError;
|
||||||
|
var activeTab;
|
||||||
|
var libraryBrowser;
|
||||||
|
|
||||||
function setupDialogs() {
|
function setupDialogs() {
|
||||||
dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
|
dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
|
||||||
@ -31,7 +33,7 @@ RED.clipboard = (function() {
|
|||||||
.dialog({
|
.dialog({
|
||||||
modal: true,
|
modal: true,
|
||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
width: 500,
|
width: 700,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
@ -41,14 +43,6 @@ RED.clipboard = (function() {
|
|||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "clipboard-dialog-close",
|
|
||||||
class: "primary",
|
|
||||||
text: RED._("common.label.close"),
|
|
||||||
click: function() {
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "clipboard-dialog-download",
|
id: "clipboard-dialog-download",
|
||||||
class: "primary",
|
class: "primary",
|
||||||
@ -65,15 +59,73 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "clipboard-dialog-copy",
|
id: "clipboard-dialog-export",
|
||||||
class: "primary",
|
class: "primary",
|
||||||
text: RED._("clipboard.export.copy"),
|
text: RED._("clipboard.export.copy"),
|
||||||
click: function() {
|
click: function() {
|
||||||
$("#clipboard-export").select();
|
if (activeTab === "clipboard-dialog-export-tab-clipboard") {
|
||||||
document.execCommand("copy");
|
$("#clipboard-export").select();
|
||||||
document.getSelection().removeAllRanges();
|
document.execCommand("copy");
|
||||||
RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
|
document.getSelection().removeAllRanges();
|
||||||
$( this ).dialog( "close" );
|
RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
} else {
|
||||||
|
var flowToExport = $("#clipboard-export").val();
|
||||||
|
var selectedPath = libraryBrowser.getSelected();
|
||||||
|
if (!selectedPath.children) {
|
||||||
|
selectedPath = selectedPath.parent;
|
||||||
|
}
|
||||||
|
var filename = $("#clipboard-dialog-tab-library-name").val().trim();
|
||||||
|
var saveFlow = function() {
|
||||||
|
$.ajax({
|
||||||
|
url:'library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path + filename,
|
||||||
|
type: "POST",
|
||||||
|
data: flowToExport,
|
||||||
|
contentType: "application/json; charset=utf-8"
|
||||||
|
}).done(function() {
|
||||||
|
$(dialog).dialog( "close" );
|
||||||
|
RED.notify(RED._("library.exportedToLibrary"),"success");
|
||||||
|
}).fail(function(xhr,textStatus,err) {
|
||||||
|
if (xhr.status === 401) {
|
||||||
|
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||||
|
} else {
|
||||||
|
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (selectedPath.children) {
|
||||||
|
var exists = false;
|
||||||
|
selectedPath.children.forEach(function(f) {
|
||||||
|
if (f.label === filename) {
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (exists) {
|
||||||
|
dialog.dialog("close");
|
||||||
|
var notification = RED.notify(RED._("clipboard.export.exists",{file:RED.utils.sanitize(filename)}),{
|
||||||
|
type: "warning",
|
||||||
|
fixed: true,
|
||||||
|
buttons: [{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
notification.hideNotification()
|
||||||
|
dialog.dialog( "open" );
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
text: RED._("clipboard.export.overwrite"),
|
||||||
|
click: function() {
|
||||||
|
notification.hideNotification()
|
||||||
|
saveFlow();
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
saveFlow();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveFlow();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -81,7 +133,17 @@ RED.clipboard = (function() {
|
|||||||
class: "primary",
|
class: "primary",
|
||||||
text: RED._("common.label.import"),
|
text: RED._("common.label.import"),
|
||||||
click: function() {
|
click: function() {
|
||||||
RED.view.importNodes($("#clipboard-import").val(),$("#import-tab > a.selected").attr('id') === 'import-tab-new');
|
var addNewFlow = ($("#import-tab > a.selected").attr('id') === 'import-tab-new');
|
||||||
|
if (activeTab === "clipboard-dialog-import-tab-clipboard") {
|
||||||
|
RED.view.importNodes($("#clipboard-import").val(),addNewFlow);
|
||||||
|
} else {
|
||||||
|
var selectedPath = libraryBrowser.getSelected();
|
||||||
|
if (selectedPath.path) {
|
||||||
|
$.get('library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path, function(data) {
|
||||||
|
RED.view.importNodes(data,addNewFlow);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,146 +163,257 @@ RED.clipboard = (function() {
|
|||||||
|
|
||||||
exportNodesDialog =
|
exportNodesDialog =
|
||||||
'<div class="form-row">'+
|
'<div class="form-row">'+
|
||||||
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.export.copy"></label>'+
|
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
|
||||||
'<span id="export-range-group" class="button-group">'+
|
'<span id="export-range-group" class="button-group">'+
|
||||||
'<a id="export-range-selected" class="editor-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
'<a id="export-range-selected" class="editor-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
||||||
'<a id="export-range-flow" class="editor-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
'<a id="export-range-flow" class="editor-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
||||||
'<a id="export-range-full" class="editor-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
'<a id="export-range-full" class="editor-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row">'+
|
'<div style="height: 400px; position:relative; border:1px solid #999;">'+
|
||||||
'<textarea readonly style="resize: none; width: 100%; border-radius: 4px;font-family: monospace; font-size: 12px; background:#f3f3f3; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
|
'<div style="position: absolute; top:0;left:0;bottom:0;width:120px;background: #f3f3f3;">'+
|
||||||
|
'<ul id="clipboard-dialog-export-tabs"></ul>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div id="clipboard-dialog-export-tabs-content" class="clipboard-dialog-tabs-content">'+
|
||||||
|
'<div id="clipboard-dialog-export-tab-clipboard" class="clipboard-dialog-tab-clipboard">'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<textarea readonly id="clipboard-export"></textarea>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row" style="text-align: right;">'+
|
||||||
|
'<span id="export-format-group" class="button-group">'+
|
||||||
|
'<a id="export-format-mini" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
||||||
|
'<a id="export-format-full" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div id="clipboard-dialog-export-tab-library" class="clipboard-dialog-tab-library">'+
|
||||||
|
'<div id="clipboard-dialog-export-tab-library-browser"></div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<label data-i18n="clipboard.export.exportAs"></label><input id="clipboard-dialog-tab-library-name" type="text">'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
importNodesDialog =
|
||||||
|
'<div style="height: 400px; position:relative; border:1px solid #999; margin-bottom: 12px">'+
|
||||||
|
'<div style="position: absolute; top:0;left:0;bottom:0;width:120px;background: #f3f3f3;">'+
|
||||||
|
'<ul id="clipboard-dialog-import-tabs"></ul>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div id="clipboard-dialog-import-tabs-content" class="clipboard-dialog-tabs-content">'+
|
||||||
|
'<div id="clipboard-dialog-import-tab-clipboard" class="clipboard-dialog-tab-clipboard">'+
|
||||||
|
'<div class="form-row"><span data-i18n="clipboard.pasteNodes"></span>'+
|
||||||
|
' <a class="editor-button" id="import-file-upload-btn"><i class="fa fa-upload"></i> <span data-i18n="clipboard.selectFile"></span></a>'+
|
||||||
|
'<input type="file" id="import-file-upload" accept=".json" style="display:none">'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<textarea id="clipboard-import"></textarea>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div id="clipboard-dialog-import-tab-library" class="clipboard-dialog-tab-library">'+
|
||||||
|
'<div id="clipboard-dialog-import-tab-library-browser"></div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row" style="text-align: right;">'+
|
'<div class="form-row">'+
|
||||||
'<span id="export-format-group" class="button-group">'+
|
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+
|
||||||
'<a id="export-format-mini" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
'<span id="import-tab" class="button-group">'+
|
||||||
'<a id="export-format-full" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
'<a id="import-tab-current" class="editor-button toggle selected" href="#" data-i18n="clipboard.export.current"></a>'+
|
||||||
|
'<a id="import-tab-new" class="editor-button toggle" href="#" data-i18n="clipboard.import.newFlow"></a>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
importNodesDialog =
|
|
||||||
'<div class="form-row"><span data-i18n="clipboard.pasteNodes"></span>'+
|
|
||||||
' <a class="editor-button" id="import-file-upload-btn"><i class="fa fa-upload"></i> <span data-i18n="clipboard.selectFile"></span></a>'+
|
|
||||||
'<input type="file" id="import-file-upload" accept=".json" style="display:none">'+
|
|
||||||
'</div>'+
|
|
||||||
'<div class="form-row">'+
|
|
||||||
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5"></textarea>'+
|
|
||||||
'</div>'+
|
|
||||||
'<div class="form-row">'+
|
|
||||||
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+
|
|
||||||
'<span id="import-tab" class="button-group">'+
|
|
||||||
'<a id="import-tab-current" class="editor-button toggle selected" href="#" data-i18n="clipboard.export.current"></a>'+
|
|
||||||
'<a id="import-tab-new" class="editor-button toggle" href="#" data-i18n="clipboard.import.newFlow"></a>'+
|
|
||||||
'</span>'+
|
|
||||||
'</div>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var validateImportTimeout;
|
var validateExportFilenameTimeout
|
||||||
|
function validateExportFilename() {
|
||||||
function validateImport() {
|
if (validateExportFilenameTimeout) {
|
||||||
if (validateImportTimeout) {
|
clearTimeout(validateExportFilenameTimeout);
|
||||||
clearTimeout(validateImportTimeout);
|
|
||||||
}
|
}
|
||||||
validateImportTimeout = setTimeout(function() {
|
validateExportFilenameTimeout = setTimeout(function() {
|
||||||
var importInput = $("#clipboard-import");
|
var filenameInput = $("#clipboard-dialog-tab-library-name");
|
||||||
var v = importInput.val().trim();
|
var filename = filenameInput.val().trim();
|
||||||
if (v === "") {
|
var valid = filename.length > 0 && !/[\/\\]/.test(filename);
|
||||||
popover.close(true);
|
if (valid) {
|
||||||
currentPopoverError = null;
|
filenameInput.removeClass("input-error");
|
||||||
importInput.removeClass("input-error");
|
$("#clipboard-dialog-export").button("enable");
|
||||||
$("#clipboard-dialog-ok").button("disable");
|
} else {
|
||||||
return;
|
filenameInput.addClass("input-error");
|
||||||
}
|
$("#clipboard-dialog-export").button("disable");
|
||||||
try {
|
|
||||||
if (!/^\[[\s\S]*\]$/m.test(v)) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.notArray"));
|
|
||||||
}
|
|
||||||
var res = JSON.parse(v);
|
|
||||||
for (var i=0;i<res.length;i++) {
|
|
||||||
if (typeof res[i] !== "object") {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('id')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('type')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentPopoverError = null;
|
|
||||||
popover.close(true);
|
|
||||||
importInput.removeClass("input-error");
|
|
||||||
importInput.val(v);
|
|
||||||
$("#clipboard-dialog-ok").button("enable");
|
|
||||||
} catch(err) {
|
|
||||||
if (v !== "") {
|
|
||||||
importInput.addClass("input-error");
|
|
||||||
var errString = err.toString();
|
|
||||||
if (errString !== currentPopoverError) {
|
|
||||||
// Display the error as-is.
|
|
||||||
// Error messages are only in English. Each browser has its
|
|
||||||
// own set of messages with very little consistency.
|
|
||||||
// To provide translated messages this code will either need to:
|
|
||||||
// - reduce everything down to 'unexpected token at position x'
|
|
||||||
// which is the least useful, but most consistent message
|
|
||||||
// - use a custom/library parser that gives consistent messages
|
|
||||||
// which can be translated.
|
|
||||||
var message = $('<div class="clipboard-import-error"></div>').text(errString);
|
|
||||||
var errorPos;
|
|
||||||
// Chrome error messages
|
|
||||||
var m = /at position (\d+)/i.exec(errString);
|
|
||||||
if (m) {
|
|
||||||
errorPos = parseInt(m[1]);
|
|
||||||
} else {
|
|
||||||
// Firefox error messages
|
|
||||||
m = /at line (\d+) column (\d+)/i.exec(errString);
|
|
||||||
if (m) {
|
|
||||||
var line = parseInt(m[1])-1;
|
|
||||||
var col = parseInt(m[2])-1;
|
|
||||||
var lines = v.split("\n");
|
|
||||||
errorPos = 0;
|
|
||||||
for (var i=0;i<line;i++) {
|
|
||||||
errorPos += lines[i].length+1;
|
|
||||||
}
|
|
||||||
errorPos += col;
|
|
||||||
} else {
|
|
||||||
// Safari doesn't provide any position information
|
|
||||||
// IE: tbd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorPos !== undefined) {
|
|
||||||
v = v.replace(/\n/g,"↵");
|
|
||||||
var index = parseInt(m[1]);
|
|
||||||
var parseError = $('<div>').appendTo(message);
|
|
||||||
var code = $('<pre>').appendTo(parseError);
|
|
||||||
$('<span>').text(v.substring(errorPos-12,errorPos)).appendTo(code)
|
|
||||||
$('<span class="error">').text(v.charAt(errorPos)).appendTo(code);
|
|
||||||
$('<span>').text(v.substring(errorPos+1,errorPos+12)).appendTo(code);
|
|
||||||
}
|
|
||||||
popover.close(true).setContent(message).open();
|
|
||||||
currentPopoverError = errString;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentPopoverError = null;
|
|
||||||
}
|
|
||||||
$("#clipboard-dialog-ok").button("disable");
|
|
||||||
}
|
}
|
||||||
},100);
|
},100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function importNodes() {
|
var validateImportTimeout;
|
||||||
|
function validateImport() {
|
||||||
|
if (activeTab === "clipboard-dialog-import-tab-clipboard") {
|
||||||
|
if (validateImportTimeout) {
|
||||||
|
clearTimeout(validateImportTimeout);
|
||||||
|
}
|
||||||
|
validateImportTimeout = setTimeout(function() {
|
||||||
|
var importInput = $("#clipboard-import");
|
||||||
|
var v = importInput.val().trim();
|
||||||
|
if (v === "") {
|
||||||
|
popover.close(true);
|
||||||
|
currentPopoverError = null;
|
||||||
|
importInput.removeClass("input-error");
|
||||||
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!/^\[[\s\S]*\]$/m.test(v)) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.notArray"));
|
||||||
|
}
|
||||||
|
var res = JSON.parse(v);
|
||||||
|
for (var i=0;i<res.length;i++) {
|
||||||
|
if (typeof res[i] !== "object") {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
||||||
|
}
|
||||||
|
if (!res[i].hasOwnProperty('id')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
||||||
|
}
|
||||||
|
if (!res[i].hasOwnProperty('type')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentPopoverError = null;
|
||||||
|
popover.close(true);
|
||||||
|
importInput.removeClass("input-error");
|
||||||
|
importInput.val(v);
|
||||||
|
$("#clipboard-dialog-ok").button("enable");
|
||||||
|
} catch(err) {
|
||||||
|
if (v !== "") {
|
||||||
|
importInput.addClass("input-error");
|
||||||
|
var errString = err.toString();
|
||||||
|
if (errString !== currentPopoverError) {
|
||||||
|
// Display the error as-is.
|
||||||
|
// Error messages are only in English. Each browser has its
|
||||||
|
// own set of messages with very little consistency.
|
||||||
|
// To provide translated messages this code will either need to:
|
||||||
|
// - reduce everything down to 'unexpected token at position x'
|
||||||
|
// which is the least useful, but most consistent message
|
||||||
|
// - use a custom/library parser that gives consistent messages
|
||||||
|
// which can be translated.
|
||||||
|
var message = $('<div class="clipboard-import-error"></div>').text(errString);
|
||||||
|
var errorPos;
|
||||||
|
// Chrome error messages
|
||||||
|
var m = /at position (\d+)/i.exec(errString);
|
||||||
|
if (m) {
|
||||||
|
errorPos = parseInt(m[1]);
|
||||||
|
} else {
|
||||||
|
// Firefox error messages
|
||||||
|
m = /at line (\d+) column (\d+)/i.exec(errString);
|
||||||
|
if (m) {
|
||||||
|
var line = parseInt(m[1])-1;
|
||||||
|
var col = parseInt(m[2])-1;
|
||||||
|
var lines = v.split("\n");
|
||||||
|
errorPos = 0;
|
||||||
|
for (var i=0;i<line;i++) {
|
||||||
|
errorPos += lines[i].length+1;
|
||||||
|
}
|
||||||
|
errorPos += col;
|
||||||
|
} else {
|
||||||
|
// Safari doesn't provide any position information
|
||||||
|
// IE: tbd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorPos !== undefined) {
|
||||||
|
v = v.replace(/\n/g,"↵");
|
||||||
|
var index = parseInt(m[1]);
|
||||||
|
var parseError = $('<div>').appendTo(message);
|
||||||
|
var code = $('<pre>').appendTo(parseError);
|
||||||
|
$('<span>').text(v.substring(errorPos-12,errorPos)).appendTo(code)
|
||||||
|
$('<span class="error">').text(v.charAt(errorPos)).appendTo(code);
|
||||||
|
$('<span>').text(v.substring(errorPos+1,errorPos+12)).appendTo(code);
|
||||||
|
}
|
||||||
|
popover.close(true).setContent(message).open();
|
||||||
|
currentPopoverError = errString;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentPopoverError = null;
|
||||||
|
}
|
||||||
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
|
}
|
||||||
|
},100);
|
||||||
|
} else {
|
||||||
|
var file = libraryBrowser.getSelected();
|
||||||
|
if (file && file.label && !file.children) {
|
||||||
|
$("#clipboard-dialog-ok").button("enable");
|
||||||
|
} else {
|
||||||
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importNodes(mode) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mode = mode || "clipboard";
|
||||||
|
|
||||||
dialogContainer.empty();
|
dialogContainer.empty();
|
||||||
dialogContainer.append($(importNodesDialog));
|
dialogContainer.append($(importNodesDialog));
|
||||||
|
|
||||||
|
var tabs = RED.tabs.create({
|
||||||
|
id: "clipboard-dialog-import-tabs",
|
||||||
|
vertical: true,
|
||||||
|
onchange: function(tab) {
|
||||||
|
$("#clipboard-dialog-import-tabs-content").children().hide();
|
||||||
|
$("#" + tab.id).show();
|
||||||
|
activeTab = tab.id;
|
||||||
|
if (popover) {
|
||||||
|
popover.close(true);
|
||||||
|
currentPopoverError = null;
|
||||||
|
}
|
||||||
|
if (tab.id === "clipboard-dialog-import-tab-clipboard") {
|
||||||
|
$("#clipboard-import").focus();
|
||||||
|
} else {
|
||||||
|
libraryBrowser.focus();
|
||||||
|
}
|
||||||
|
validateImport();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "clipboard-dialog-import-tab-clipboard",
|
||||||
|
label: RED._("clipboard.clipboard")
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "clipboard-dialog-import-tab-library",
|
||||||
|
label: RED._("library.library")
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs.activateTab("clipboard-dialog-import-tab-"+mode);
|
||||||
|
if (mode === 'clipboard') {
|
||||||
|
setTimeout(function() {
|
||||||
|
$("#clipboard-import").focus();
|
||||||
|
},100)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$("#clipboard-dialog-tab-library-name").keyup(validateExportFilename);
|
||||||
|
$("#clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
|
||||||
|
$("#clipboard-dialog-export").button("enable");
|
||||||
|
|
||||||
|
libraryBrowser = RED.library.createBrowser({
|
||||||
|
container: $("#clipboard-dialog-import-tab-library-browser"),
|
||||||
|
onselect: function(file) {
|
||||||
|
if (file && file.label && !file.children) {
|
||||||
|
$("#clipboard-dialog-ok").button("enable");
|
||||||
|
} else {
|
||||||
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
loadFlowLibrary(libraryBrowser,true);
|
||||||
|
|
||||||
dialogContainer.i18n();
|
dialogContainer.i18n();
|
||||||
|
|
||||||
$("#clipboard-dialog-ok").show();
|
$("#clipboard-dialog-ok").show();
|
||||||
$("#clipboard-dialog-cancel").show();
|
$("#clipboard-dialog-cancel").show();
|
||||||
$("#clipboard-dialog-close").hide();
|
$("#clipboard-dialog-export").hide();
|
||||||
$("#clipboard-dialog-copy").hide();
|
|
||||||
$("#clipboard-dialog-download").hide();
|
$("#clipboard-dialog-download").hide();
|
||||||
$("#clipboard-dialog-ok").button("disable");
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
$("#clipboard-import").keyup(validateImport);
|
$("#clipboard-import").keyup(validateImport);
|
||||||
@ -277,13 +450,62 @@ RED.clipboard = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportNodes() {
|
function exportNodes(mode) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mode = mode || "clipboard";
|
||||||
|
|
||||||
dialogContainer.empty();
|
dialogContainer.empty();
|
||||||
dialogContainer.append($(exportNodesDialog));
|
dialogContainer.append($(exportNodesDialog));
|
||||||
|
|
||||||
|
var tabs = RED.tabs.create({
|
||||||
|
id: "clipboard-dialog-export-tabs",
|
||||||
|
vertical: true,
|
||||||
|
onchange: function(tab) {
|
||||||
|
$("#clipboard-dialog-export-tabs-content").children().hide();
|
||||||
|
$("#" + tab.id).show();
|
||||||
|
activeTab = tab.id;
|
||||||
|
if (tab.id === "clipboard-dialog-export-tab-clipboard") {
|
||||||
|
$("#clipboard-dialog-export").button("option","label", RED._("clipboard.export.copy"))
|
||||||
|
$("#clipboard-dialog-download").show();
|
||||||
|
} else {
|
||||||
|
$("#clipboard-dialog-export").button("option","label", RED._("clipboard.export.export"))
|
||||||
|
$("#clipboard-dialog-download").hide();
|
||||||
|
libraryBrowser.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "clipboard-dialog-export-tab-clipboard",
|
||||||
|
label: RED._("clipboard.clipboard")
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "clipboard-dialog-export-tab-library",
|
||||||
|
label: RED._("library.library")
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs.activateTab("clipboard-dialog-export-tab-"+mode);
|
||||||
|
|
||||||
|
$("#clipboard-dialog-tab-library-name").keyup(validateExportFilename);
|
||||||
|
$("#clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
|
||||||
|
$("#clipboard-dialog-export").button("enable");
|
||||||
|
|
||||||
|
libraryBrowser = RED.library.createBrowser({
|
||||||
|
container: $("#clipboard-dialog-export-tab-library-browser"),
|
||||||
|
folderTools: true,
|
||||||
|
onselect: function(file) {
|
||||||
|
if (file && file.label && !file.children) {
|
||||||
|
$("#clipboard-dialog-tab-library-name").val(file.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
loadFlowLibrary(libraryBrowser,false);
|
||||||
|
|
||||||
|
$("#clipboard-dialog-tab-library-name").val("flows.json").select();
|
||||||
|
|
||||||
dialogContainer.i18n();
|
dialogContainer.i18n();
|
||||||
var format = RED.settings.flowFilePretty ? "export-format-full" : "export-format-mini";
|
var format = RED.settings.flowFilePretty ? "export-format-full" : "export-format-mini";
|
||||||
|
|
||||||
@ -307,6 +529,8 @@ RED.clipboard = (function() {
|
|||||||
flow = JSON.stringify(nodes);
|
flow = JSON.stringify(nodes);
|
||||||
}
|
}
|
||||||
$("#clipboard-export").val(flow);
|
$("#clipboard-export").val(flow);
|
||||||
|
setTimeout(function() { $("#clipboard-export").scrollTop(0); },50);
|
||||||
|
|
||||||
$("#clipboard-export").focus();
|
$("#clipboard-export").focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -314,7 +538,6 @@ RED.clipboard = (function() {
|
|||||||
$("#export-range-group > a").click(function(evt) {
|
$("#export-range-group > a").click(function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
|
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
|
||||||
$("#clipboard-export").focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$(this).parent().children().removeClass('selected');
|
$(this).parent().children().removeClass('selected');
|
||||||
@ -357,13 +580,13 @@ RED.clipboard = (function() {
|
|||||||
$("#export-copy").addClass('disabled');
|
$("#export-copy").addClass('disabled');
|
||||||
}
|
}
|
||||||
$("#clipboard-export").val(flow);
|
$("#clipboard-export").val(flow);
|
||||||
|
setTimeout(function() { $("#clipboard-export").scrollTop(0); },50);
|
||||||
$("#clipboard-export").focus();
|
$("#clipboard-export").focus();
|
||||||
})
|
})
|
||||||
|
|
||||||
$("#clipboard-dialog-ok").hide();
|
$("#clipboard-dialog-ok").hide();
|
||||||
$("#clipboard-dialog-cancel").hide();
|
$("#clipboard-dialog-cancel").hide();
|
||||||
$("#clipboard-dialog-copy").hide();
|
$("#clipboard-dialog-export").hide();
|
||||||
$("#clipboard-dialog-close").hide();
|
|
||||||
var selection = RED.workspaces.selection();
|
var selection = RED.workspaces.selection();
|
||||||
if (selection.length > 0) {
|
if (selection.length > 0) {
|
||||||
$("#export-range-selected").click();
|
$("#export-range-selected").click();
|
||||||
@ -381,29 +604,49 @@ RED.clipboard = (function() {
|
|||||||
} else {
|
} else {
|
||||||
$("#export-format-mini").click();
|
$("#export-format-mini").click();
|
||||||
}
|
}
|
||||||
$("#clipboard-export")
|
|
||||||
.focus(function() {
|
|
||||||
var textarea = $(this);
|
|
||||||
textarea.select();
|
|
||||||
textarea.mouseup(function() {
|
|
||||||
textarea.unbind("mouseup");
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
|
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
|
||||||
|
|
||||||
$("#clipboard-export").focus();
|
$("#clipboard-export").focus();
|
||||||
if (!document.queryCommandSupported("copy")) {
|
$("#clipboard-dialog-cancel").show();
|
||||||
$("#clipboard-dialog-cancel").hide();
|
$("#clipboard-dialog-export").show();
|
||||||
$("#clipboard-dialog-close").show();
|
|
||||||
} else {
|
|
||||||
$("#clipboard-dialog-cancel").show();
|
|
||||||
$("#clipboard-dialog-copy").show();
|
|
||||||
}
|
|
||||||
$("#clipboard-dialog-download").show();
|
$("#clipboard-dialog-download").show();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadFlowLibrary(browser,includeExamples) {
|
||||||
|
var listing = [];
|
||||||
|
if (includeExamples) {
|
||||||
|
listing.push({
|
||||||
|
library: "_examples_",
|
||||||
|
type: "flows",
|
||||||
|
icon: 'fa fa-hdd-o',
|
||||||
|
label: RED._("library.types.examples"),
|
||||||
|
path: "",
|
||||||
|
children: function(item,done) {
|
||||||
|
RED.library.loadLibraryFolder("_examples_","flows","",function(children) {
|
||||||
|
item.children = children;
|
||||||
|
done(children);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
listing.push({
|
||||||
|
library: "local",
|
||||||
|
type: "flows",
|
||||||
|
icon: 'fa fa-hdd-o',
|
||||||
|
label: RED._("library.types.local"),
|
||||||
|
path: "",
|
||||||
|
expanded: true,
|
||||||
|
children: function(item,done) {
|
||||||
|
RED.library.loadLibraryFolder("local","flows","",function(children) {
|
||||||
|
item.children = children;
|
||||||
|
done(children);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
browser.data(listing);
|
||||||
|
}
|
||||||
|
|
||||||
function hideDropTarget() {
|
function hideDropTarget() {
|
||||||
$("#dropTarget").hide();
|
$("#dropTarget").hide();
|
||||||
RED.keyboard.remove("escape");
|
RED.keyboard.remove("escape");
|
||||||
@ -460,6 +703,7 @@ RED.clipboard = (function() {
|
|||||||
RED.actions.add("core:show-export-dialog",exportNodes);
|
RED.actions.add("core:show-export-dialog",exportNodes);
|
||||||
RED.actions.add("core:show-import-dialog",importNodes);
|
RED.actions.add("core:show-import-dialog",importNodes);
|
||||||
|
|
||||||
|
RED.actions.add("core:library-export",function() { exportNodes('library') });
|
||||||
|
|
||||||
RED.events.on("editor:open",function() { disabled = true; });
|
RED.events.on("editor:open",function() { disabled = true; });
|
||||||
RED.events.on("editor:close",function() { disabled = false; });
|
RED.events.on("editor:close",function() { disabled = false; });
|
||||||
@ -468,7 +712,6 @@ RED.clipboard = (function() {
|
|||||||
RED.events.on("type-search:open",function() { disabled = true; });
|
RED.events.on("type-search:open",function() { disabled = true; });
|
||||||
RED.events.on("type-search:close",function() { disabled = false; });
|
RED.events.on("type-search:close",function() { disabled = false; });
|
||||||
|
|
||||||
|
|
||||||
$('#chart').on("dragenter",function(event) {
|
$('#chart').on("dragenter",function(event) {
|
||||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
|
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
|
||||||
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
|
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
@ -263,6 +263,5 @@ RED.menu = (function() {
|
|||||||
addItem: addItem,
|
addItem: addItem,
|
||||||
removeItem: removeItem,
|
removeItem: removeItem,
|
||||||
setAction: setAction
|
setAction: setAction
|
||||||
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
* label: 'Local', // label for the item
|
* label: 'Local', // label for the item
|
||||||
* icon: 'fa fa-rocket', // (optional) icon for the item
|
* icon: 'fa fa-rocket', // (optional) icon for the item
|
||||||
* selected: true/false, // (optional) if present, display checkbox accordingly
|
* selected: true/false, // (optional) if present, display checkbox accordingly
|
||||||
* children: [] | function(done) // (optional) an array of child items, or a function
|
* children: [] | function(item,done) // (optional) an array of child items, or a function
|
||||||
* // that will call the `done` callback with an array
|
* // that will call the `done` callback with an array
|
||||||
* // of child items
|
* // of child items
|
||||||
* }
|
* }
|
||||||
@ -51,8 +51,55 @@
|
|||||||
var that = this;
|
var that = this;
|
||||||
|
|
||||||
this.element.addClass('red-ui-treeList');
|
this.element.addClass('red-ui-treeList');
|
||||||
|
this.element.attr("tabIndex",0);
|
||||||
var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
|
var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
|
||||||
|
this.element.on('keydown', function(evt) {
|
||||||
|
var selected = that._topList.find(".selected").parent().data('data');
|
||||||
|
if (!selected && (evt.keyCode === 40 || evt.keyCode === 38)) {
|
||||||
|
that.select(that._data[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var target;
|
||||||
|
switch(evt.keyCode) {
|
||||||
|
case 37: // LEFT
|
||||||
|
if (selected.children&& selected.treeList.container.hasClass("expanded")) {
|
||||||
|
selected.treeList.collapse()
|
||||||
|
} else if (selected.parent) {
|
||||||
|
target = selected.parent;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 38: // UP
|
||||||
|
target = that._getPreviousSibling(selected);
|
||||||
|
if (target) {
|
||||||
|
target = that._getLastDescendant(target);
|
||||||
|
}
|
||||||
|
if (!target && selected.parent) {
|
||||||
|
target = selected.parent;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 39: // RIGHT
|
||||||
|
if (selected.children) {
|
||||||
|
if (!selected.treeList.container.hasClass("expanded")) {
|
||||||
|
selected.treeList.expand()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 40: //DOWN
|
||||||
|
if (selected.children && Array.isArray(selected.children) && selected.children.length > 0 && selected.treeList.container.hasClass("expanded")) {
|
||||||
|
target = selected.children[0];
|
||||||
|
} else {
|
||||||
|
target = that._getNextSibling(selected);
|
||||||
|
while (!target && selected.parent) {
|
||||||
|
selected = selected.parent;
|
||||||
|
target = that._getNextSibling(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (target) {
|
||||||
|
that.select(target);
|
||||||
|
}
|
||||||
|
});
|
||||||
this._data = [];
|
this._data = [];
|
||||||
|
|
||||||
this._topList = $('<ol>').css({
|
this._topList = $('<ol>').css({
|
||||||
@ -66,34 +113,83 @@
|
|||||||
scrollOnAdd: false,
|
scrollOnAdd: false,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
addItem: function(container,i,item) {
|
addItem: function(container,i,item) {
|
||||||
that._addSubtree(container,item,0);
|
that._addSubtree(that._topList,container,item,0);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
if (this.options.data) {
|
if (this.options.data) {
|
||||||
this.data(this.options.data);
|
this.data(this.options.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_addChildren: function(container,children,depth) {
|
_getLastDescendant: function(item) {
|
||||||
|
// Gets the last visible descendant of the item
|
||||||
|
if (!item.children || !item.treeList.container.hasClass("expanded") || item.children.length === 0) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return this._getLastDescendant(item.children[item.children.length-1]);
|
||||||
|
},
|
||||||
|
_getPreviousSibling: function(item) {
|
||||||
|
var candidates;
|
||||||
|
if (!item.parent) {
|
||||||
|
candidates = this._data;
|
||||||
|
} else {
|
||||||
|
candidates = item.parent.children;
|
||||||
|
}
|
||||||
|
var index = candidates.indexOf(item);
|
||||||
|
if (index === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return candidates[index-1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_getNextSibling: function(item) {
|
||||||
|
var candidates;
|
||||||
|
if (!item.parent) {
|
||||||
|
candidates = this._data;
|
||||||
|
} else {
|
||||||
|
candidates = item.parent.children;
|
||||||
|
}
|
||||||
|
var index = candidates.indexOf(item);
|
||||||
|
if (index === candidates.length - 1) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return candidates[index+1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_addChildren: function(container,parent,children,depth) {
|
||||||
var that = this;
|
var that = this;
|
||||||
var subtree = $('<ol>').appendTo(container).editableList({
|
var subtree = $('<ol>').appendTo(container).editableList({
|
||||||
addButton: false,
|
addButton: false,
|
||||||
scrollOnAdd: false,
|
scrollOnAdd: false,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
addItem: function(container,i,item) {
|
addItem: function(container,i,item) {
|
||||||
that._addSubtree(container,item,depth+1);
|
that._addSubtree(subtree,container,item,depth+1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (var i=0;i<children.length;i++) {
|
for (var i=0;i<children.length;i++) {
|
||||||
|
children[i].parent = parent;
|
||||||
subtree.editableList('addItem',children[i])
|
subtree.editableList('addItem',children[i])
|
||||||
}
|
}
|
||||||
|
return subtree;
|
||||||
},
|
},
|
||||||
_addSubtree: function(container, item, depth) {
|
_addSubtree: function(parentList, container, item, depth) {
|
||||||
var that = this;
|
var that = this;
|
||||||
var labelNodeType = "<label>";
|
item.treeList = {};
|
||||||
if (item.children && item.hasOwnProperty('selected')) {
|
item.treeList.container = container;
|
||||||
labelNodeType = "<div>";
|
|
||||||
|
item.treeList.parentList = parentList;
|
||||||
|
item.treeList.remove = function() {
|
||||||
|
parentList.editableList('removeItem',item);
|
||||||
|
if (item.parent) {
|
||||||
|
var index = item.parent.children.indexOf(item);
|
||||||
|
item.parent.children.splice(index,1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var label = $(labelNodeType,{tabindex:"0",class:"red-ui-treeList-label"}).appendTo(container);
|
var labelNodeType = "<div>";
|
||||||
|
// if (item.children && item.hasOwnProperty('selected')) {
|
||||||
|
// labelNodeType = "<div>";
|
||||||
|
// }
|
||||||
|
var label = $(labelNodeType,{class:"red-ui-treeList-label"}).appendTo(container);
|
||||||
|
item.treeList.label = label;
|
||||||
if (item.class) {
|
if (item.class) {
|
||||||
label.addClass(item.class);
|
label.addClass(item.class);
|
||||||
}
|
}
|
||||||
@ -102,30 +198,78 @@
|
|||||||
})
|
})
|
||||||
label.on('mouseover',function(e) { that._trigger('itemmouseover',e,item); })
|
label.on('mouseover',function(e) { that._trigger('itemmouseover',e,item); })
|
||||||
label.on('mouseout',function(e) { that._trigger('itemmouseout',e,item); })
|
label.on('mouseout',function(e) { that._trigger('itemmouseout',e,item); })
|
||||||
|
label.on('mouseenter',function(e) { that._trigger('itemmouseenter',e,item); })
|
||||||
|
label.on('mouseleave',function(e) { that._trigger('itemmouseleave',e,item); })
|
||||||
|
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
$('<span class="red-ui-treeList-icon"><i class="fa fa-angle-right" /></span>').appendTo(label);
|
item.treeList.addChild = function(newItem,select) {
|
||||||
// $('<span class="red-ui-treeList-icon"><i class="fa fa-folder-o" /></span>').appendTo(label);
|
item.treeList.childList.editableList('addItem',newItem)
|
||||||
label.click(function(e) {
|
newItem.parent = item;
|
||||||
|
item.children.push(newItem);
|
||||||
|
if (select) {
|
||||||
|
setTimeout(function() {
|
||||||
|
that.select(newItem)
|
||||||
|
},100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.treeList.expand = function(done) {
|
||||||
|
if (container.hasClass("expanded")) {
|
||||||
|
done && done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!container.hasClass("built") && typeof item.children === 'function') {
|
if (!container.hasClass("built") && typeof item.children === 'function') {
|
||||||
container.addClass('built');
|
container.addClass('built');
|
||||||
var childrenAdded = false;
|
var childrenAdded = false;
|
||||||
var spinner;
|
var spinner;
|
||||||
item.children(function(children) {
|
var startTime = 0;
|
||||||
|
item.children(item,function(children) {
|
||||||
childrenAdded = true;
|
childrenAdded = true;
|
||||||
that._addChildren(container,children,depth);
|
item.treeList.childList = that._addChildren(container,item,children,depth).hide();
|
||||||
if (spinner) {
|
var delta = Date.now() - startTime;
|
||||||
spinner.remove();
|
if (delta < 400) {
|
||||||
|
setTimeout(function() {
|
||||||
|
item.treeList.childList.slideDown('fast');
|
||||||
|
if (spinner) {
|
||||||
|
spinner.remove();
|
||||||
|
}
|
||||||
|
},400-delta);
|
||||||
|
} else {
|
||||||
|
item.treeList.childList.slideDown('fast');
|
||||||
|
if (spinner) {
|
||||||
|
spinner.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
done && done();
|
||||||
|
that._trigger("childrenloaded",null,item)
|
||||||
});
|
});
|
||||||
if (!childrenAdded) {
|
if (!childrenAdded) {
|
||||||
|
startTime = Date.now();
|
||||||
spinner = $('<div class="red-ui-treeList-spinner">').css({
|
spinner = $('<div class="red-ui-treeList-spinner">').css({
|
||||||
"background-position": (35+depth*15)+'px 50%'
|
"background-position": (35+depth*15)+'px 50%'
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
item.treeList.childList.slideDown('fast');
|
||||||
|
done && done();
|
||||||
|
}
|
||||||
|
container.addClass("expanded");
|
||||||
|
}
|
||||||
|
item.treeList.collapse = function() {
|
||||||
|
item.treeList.childList.slideUp('fast');
|
||||||
|
container.removeClass("expanded");
|
||||||
|
}
|
||||||
|
|
||||||
|
$('<span class="red-ui-treeList-icon"><i class="fa fa-angle-right" /></span>').appendTo(label);
|
||||||
|
// $('<span class="red-ui-treeList-icon"><i class="fa fa-folder-o" /></span>').appendTo(label);
|
||||||
|
label.click(function(e) {
|
||||||
|
if (container.hasClass("expanded")) {
|
||||||
|
if (item.hasOwnProperty('selected') || label.hasClass("selected")) {
|
||||||
|
item.treeList.collapse();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.treeList.expand();
|
||||||
}
|
}
|
||||||
container.toggleClass("expanded");
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
$('<span class="red-ui-treeList-icon"></span>').appendTo(label);
|
$('<span class="red-ui-treeList-icon"></span>').appendTo(label);
|
||||||
@ -140,21 +284,27 @@
|
|||||||
item.selected = this.checked;
|
item.selected = this.checked;
|
||||||
that._trigger("select",e,item);
|
that._trigger("select",e,item);
|
||||||
})
|
})
|
||||||
} else if (!item.children) {
|
} else {
|
||||||
label.click(function(e) {
|
label.click(function(e) {
|
||||||
|
that._topList.find(".selected").removeClass("selected");
|
||||||
|
label.addClass("selected");
|
||||||
that._trigger("select",e,item)
|
that._trigger("select",e,item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (item.icon) {
|
if (item.icon) {
|
||||||
$('<span class="red-ui-treeList-icon"><i class="'+item.icon+'" /></span>').appendTo(label);
|
$('<span class="red-ui-treeList-icon"><i class="'+item.icon+'" /></span>').appendTo(label);
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-treeList-label-text"></span>').text(item.label).appendTo(label);
|
if (item.label) {
|
||||||
|
$('<span class="red-ui-treeList-label-text"></span>').text(item.label).appendTo(label);
|
||||||
|
} else if (item.element) {
|
||||||
|
$(item.element).appendTo(label);
|
||||||
|
}
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
if (Array.isArray(item.children)) {
|
if (Array.isArray(item.children)) {
|
||||||
that._addChildren(container,item.children,depth);
|
item.treeList.childList = that._addChildren(container,item,item.children,depth).hide();
|
||||||
}
|
}
|
||||||
if (item.expanded) {
|
if (item.expanded) {
|
||||||
label.click();
|
item.treeList.expand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -168,6 +318,8 @@
|
|||||||
for (var i=0; i<items.length;i++) {
|
for (var i=0; i<items.length;i++) {
|
||||||
this._topList.editableList('addItem',items[i]);
|
this._topList.editableList('addItem',items[i]);
|
||||||
}
|
}
|
||||||
|
this._trigger("select")
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return this._data;
|
return this._data;
|
||||||
}
|
}
|
||||||
@ -178,6 +330,16 @@
|
|||||||
this._topList.editableList('show',this._data[i]);
|
this._topList.editableList('show',this._data[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
select: function(item) {
|
||||||
|
this._topList.find(".selected").removeClass("selected");
|
||||||
|
item.treeList.label.addClass("selected");
|
||||||
|
this._trigger("select",null,item)
|
||||||
|
|
||||||
|
},
|
||||||
|
selected: function() {
|
||||||
|
var s = this._topList.find(".selected").parent().data('data');
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,84 +15,176 @@
|
|||||||
**/
|
**/
|
||||||
RED.library = (function() {
|
RED.library = (function() {
|
||||||
|
|
||||||
var exportToLibraryDialog;
|
var loadLibraryBrowser;
|
||||||
var elementPrefix = "node-input-";
|
var saveLibraryBrowser;
|
||||||
|
var libraryEditor;
|
||||||
|
var activeLibrary;
|
||||||
|
|
||||||
|
var _libraryLookup = '<div id="node-dialog-library-load" class="hide">'+
|
||||||
|
'<form class="form-horizontal">'+
|
||||||
|
'<div style="height: 400px; position:relative; ">'+
|
||||||
|
'<div id="node-dialog-library-load-panes">'+
|
||||||
|
'<div class="red-ui-panel" id="node-dialog-library-load-browser"></div>'+
|
||||||
|
'<div class="red-ui-panel">'+
|
||||||
|
'<div id="node-dialog-library-load-preview">'+
|
||||||
|
'<div class="red-ui-panel" id="node-dialog-library-load-preview-text"></div>'+
|
||||||
|
'<div class="red-ui-panel" id="node-dialog-library-load-preview-details">'+
|
||||||
|
'<table id="node-dialog-library-load-preview-details-table" class="node-info"></table>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</form>'+
|
||||||
|
'</div>'
|
||||||
|
|
||||||
|
|
||||||
var _librarySaveConfirm = '<div id="node-dialog-library-save-confirm" class="hide"><form class="form-horizontal"><div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content"></div></form></div>';
|
var _librarySave = '<div id="node-dialog-library-save" class="hide">'+
|
||||||
var _librarySave = '<div id="node-dialog-library-save" class="hide"><form class="form-horizontal"><div class="form-row"><label for="node-dialog-library-save-folder" data-i18n="[append]library.folder"><i class="fa fa-folder-open"></i> </label><input type="text" id="node-dialog-library-save-folder" data-i18n="[placeholder]library.folderPlaceholder"></div><div class="form-row"><label for="node-dialog-library-save-filename" data-i18n="[append]library.filename"><i class="fa fa-file"></i> </label><input type="text" id="node-dialog-library-save-filename" data-i18n="[placeholder]library.filenamePlaceholder"></div></form></div>';
|
'<form class="form-horizontal">'+
|
||||||
var _libraryLookup = '<div id="node-dialog-library-lookup" class="hide"><form class="form-horizontal"><div class="form-row"><ul id="node-dialog-library-breadcrumbs" class="breadcrumb"><li class="active"><a href="#" data-i18n="[append]library.breadcrumb"></a></li></ul></div><div class="form-row"><div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;"><div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div></div><div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;"><div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div></div></div></form></div>';
|
'<div style="height: 400px; position:relative; ">'+
|
||||||
|
'<div id="node-dialog-library-save-browser"></div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<label data-i18n="clipboard.export.exportAs"></label><input id="node-dialog-library-save-filename" type="text">'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
|
'</form>'+
|
||||||
|
'</div>'
|
||||||
|
|
||||||
|
function saveToLibrary() {
|
||||||
|
var elementPrefix = activeLibrary.elementPrefix || "node-input-";
|
||||||
|
var name = $("#"+elementPrefix+"name").val().trim();
|
||||||
|
if (name === "") {
|
||||||
|
name = RED._("library.unnamedType",{type:activeLibrary.type});
|
||||||
|
}
|
||||||
|
var filename = $("#node-dialog-library-save-filename").val().trim()
|
||||||
|
var selectedPath = saveLibraryBrowser.getSelected();
|
||||||
|
if (!selectedPath.children) {
|
||||||
|
selectedPath = selectedPath.parent;
|
||||||
|
}
|
||||||
|
|
||||||
function loadFlowLibrary() {
|
var queryArgs = [];
|
||||||
$.getJSON("library/flows",function(data) {
|
var data = {};
|
||||||
//console.log(data);
|
for (var i=0; i<activeLibrary.fields.length; i++) {
|
||||||
|
var field = activeLibrary.fields[i];
|
||||||
var buildMenu = function(data,root) {
|
if (field == "name") {
|
||||||
var i;
|
data.name = name;
|
||||||
var li;
|
} else {
|
||||||
var a;
|
data[field] = $("#"+elementPrefix+field).val();
|
||||||
var ul = document.createElement("ul");
|
|
||||||
if (root === "") {
|
|
||||||
ul.id = "menu-item-import-library-submenu";
|
|
||||||
}
|
|
||||||
ul.className = "dropdown-menu";
|
|
||||||
if (data.d) {
|
|
||||||
for (i in data.d) {
|
|
||||||
if (data.d.hasOwnProperty(i)) {
|
|
||||||
li = document.createElement("li");
|
|
||||||
li.className = "dropdown-submenu pull-left";
|
|
||||||
a = document.createElement("a");
|
|
||||||
a.href="#";
|
|
||||||
var label = i.replace(/^@.*\//,"").replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/g," ").replace(/_/g," ");
|
|
||||||
a.innerText = label;
|
|
||||||
li.appendChild(a);
|
|
||||||
li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
|
|
||||||
ul.appendChild(li);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.f) {
|
|
||||||
for (i in data.f) {
|
|
||||||
if (data.f.hasOwnProperty(i)) {
|
|
||||||
li = document.createElement("li");
|
|
||||||
a = document.createElement("a");
|
|
||||||
a.href="#";
|
|
||||||
a.innerText = data.f[i];
|
|
||||||
a.flowName = root+(root!==""?"/":"")+data.f[i];
|
|
||||||
a.onclick = function() {
|
|
||||||
$.get('library/flows/'+this.flowName, function(data) {
|
|
||||||
RED.view.importNodes(data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
li.appendChild(a);
|
|
||||||
ul.appendChild(li);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ul;
|
|
||||||
};
|
|
||||||
var examples;
|
|
||||||
if (data.d && data.d._examples_) {
|
|
||||||
examples = data.d._examples_;
|
|
||||||
delete data.d._examples_;
|
|
||||||
}
|
}
|
||||||
var menu = buildMenu(data,"");
|
}
|
||||||
$("#menu-item-import-examples").remove();
|
data.text = activeLibrary.editor.getValue();
|
||||||
if (examples) {
|
var saveFlow = function() {
|
||||||
RED.menu.addItem("menu-item-import",{id:"menu-item-import-examples",label:RED._("menu.label.examples"),options:[]})
|
$.ajax({
|
||||||
$("#menu-item-import-examples-submenu").replaceWith(buildMenu(examples,"_examples_"));
|
url:"library/"+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path + filename,
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
contentType: "application/json; charset=utf-8"
|
||||||
|
}).done(function(data,textStatus,xhr) {
|
||||||
|
RED.notify(RED._("library.savedType", {type:activeLibrary.type}),"success");
|
||||||
|
}).fail(function(xhr,textStatus,err) {
|
||||||
|
if (xhr.status === 401) {
|
||||||
|
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||||
|
} else {
|
||||||
|
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (selectedPath.children) {
|
||||||
|
var exists = false;
|
||||||
|
selectedPath.children.forEach(function(f) {
|
||||||
|
if (f.label === filename) {
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (exists) {
|
||||||
|
$( "#node-dialog-library-save" ).dialog("close");
|
||||||
|
var notification = RED.notify(RED._("clipboard.export.exists",{file:RED.utils.sanitize(filename)}),{
|
||||||
|
type: "warning",
|
||||||
|
fixed: true,
|
||||||
|
buttons: [{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
notification.hideNotification()
|
||||||
|
$( "#node-dialog-library-save" ).dialog( "open" );
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
text: RED._("clipboard.export.overwrite"),
|
||||||
|
click: function() {
|
||||||
|
notification.hideNotification()
|
||||||
|
saveFlow();
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
saveFlow();
|
||||||
}
|
}
|
||||||
//TODO: need an api in RED.menu for this
|
} else {
|
||||||
$("#menu-item-import-library-submenu").replaceWith(menu);
|
saveFlow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLibraryFolder(library,type,root,done) {
|
||||||
|
$.getJSON("library/"+library+"/"+type+"/"+root,function(data) {
|
||||||
|
var items = data.map(function(d) {
|
||||||
|
if (typeof d === "string") {
|
||||||
|
return {
|
||||||
|
library: library,
|
||||||
|
type: type,
|
||||||
|
icon: 'fa fa-folder',
|
||||||
|
label: d,
|
||||||
|
path: root+d+"/",
|
||||||
|
children: function(item,done) {
|
||||||
|
loadLibraryFolder(library,type,root+d+"/", function(children) {
|
||||||
|
item.children = children; // TODO: should this be done by treeList for us
|
||||||
|
done(children);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
library: library,
|
||||||
|
type: type,
|
||||||
|
icon: 'fa fa-file-o',
|
||||||
|
label: d.fn,
|
||||||
|
path: root+d.fn,
|
||||||
|
props: d
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
items.sort(function(A,B){
|
||||||
|
if (A.children && !B.children) {
|
||||||
|
return -1;
|
||||||
|
} else if (!A.children && B.children) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return A.label.localeCompare(B.label);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done(items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var validateExportFilenameTimeout;
|
||||||
|
function validateExportFilename(filenameInput) {
|
||||||
|
if (validateExportFilenameTimeout) {
|
||||||
|
clearTimeout(validateExportFilenameTimeout);
|
||||||
|
}
|
||||||
|
validateExportFilenameTimeout = setTimeout(function() {
|
||||||
|
var filename = filenameInput.val().trim();
|
||||||
|
var valid = filename.length > 0 && !/[\/\\]/.test(filename);
|
||||||
|
if (valid) {
|
||||||
|
filenameInput.removeClass("input-error");
|
||||||
|
$("#node-dialog-library-save-button").button("enable");
|
||||||
|
} else {
|
||||||
|
filenameInput.addClass("input-error");
|
||||||
|
$("#node-dialog-library-save-button").button("disable");
|
||||||
|
}
|
||||||
|
},100);
|
||||||
|
}
|
||||||
|
|
||||||
function createUI(options) {
|
function createUI(options) {
|
||||||
var libraryData = {};
|
var libraryData = {};
|
||||||
var selectedLibraryItem = null;
|
var elementPrefix = options.elementPrefix || "node-input-";
|
||||||
var libraryEditor = null;
|
|
||||||
elementPrefix = options.elementPrefix || "node-input-";
|
|
||||||
|
|
||||||
// Orion editor has set/getText
|
// Orion editor has set/getText
|
||||||
// ACE editor has set/getValue
|
// ACE editor has set/getValue
|
||||||
@ -107,64 +199,7 @@ RED.library = (function() {
|
|||||||
options.editor.getValue = options.editor.getText;
|
options.editor.getValue = options.editor.getText;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildFileListItem(item) {
|
// Add the library button to the name <input> in the edit dialog
|
||||||
var li = document.createElement("li");
|
|
||||||
li.onmouseover = function(e) { $(this).addClass("list-hover"); };
|
|
||||||
li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
|
|
||||||
return li;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFileList(root,data) {
|
|
||||||
var ul = document.createElement("ul");
|
|
||||||
var li;
|
|
||||||
for (var i=0; i<data.length; i++) {
|
|
||||||
var v = data[i];
|
|
||||||
if (typeof v === "string") {
|
|
||||||
// directory
|
|
||||||
li = buildFileListItem(v);
|
|
||||||
li.onclick = (function () {
|
|
||||||
var dirName = v;
|
|
||||||
return function(e) {
|
|
||||||
var bcli = $('<li class="active"><span class="divider">/</span> </li>');
|
|
||||||
$('<a href="#"></a>').text(dirName).appendTo(bcli).click(function(e) {
|
|
||||||
$(this).parent().nextAll().remove();
|
|
||||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
|
||||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
|
||||||
});
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
var bc = $("#node-dialog-library-breadcrumbs");
|
|
||||||
$(".active",bc).removeClass("active");
|
|
||||||
bc.append(bcli);
|
|
||||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
|
||||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
$('<i class="fa fa-folder"></i>').appendTo(li);
|
|
||||||
$('<span>').text(" "+v).appendTo(li);
|
|
||||||
ul.appendChild(li);
|
|
||||||
} else {
|
|
||||||
// file
|
|
||||||
li = buildFileListItem(v);
|
|
||||||
li.innerText = v.name;
|
|
||||||
li.onclick = (function() {
|
|
||||||
var item = v;
|
|
||||||
return function(e) {
|
|
||||||
$(".list-selected",ul).removeClass("list-selected");
|
|
||||||
$(this).addClass("list-selected");
|
|
||||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
|
||||||
selectedLibraryItem = item;
|
|
||||||
libraryEditor.setValue(data,-1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
ul.appendChild(li);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ul;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#'+elementPrefix+"name").css("width","calc(100% - 52px)").after(
|
$('#'+elementPrefix+"name").css("width","calc(100% - 52px)").after(
|
||||||
'<div class="btn-group" style="margin-left:5px;">'+
|
'<div class="btn-group" style="margin-left:5px;">'+
|
||||||
'<a id="node-input-'+options.type+'-lookup" class="editor-button" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></a>'+
|
'<a id="node-input-'+options.type+'-lookup" class="editor-button" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></a>'+
|
||||||
@ -175,331 +210,346 @@ RED.library = (function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$('#node-input-'+options.type+'-menu-open-library').click(function(e) {
|
$('#node-input-'+options.type+'-menu-open-library').click(function(e) {
|
||||||
$("#node-select-library").children().remove();
|
activeLibrary = options;
|
||||||
var bc = $("#node-dialog-library-breadcrumbs");
|
loadLibraryFolder("local",options.url, "", function(items) {
|
||||||
bc.children().first().nextAll().remove();
|
var listing = [{
|
||||||
libraryEditor.setValue('',-1);
|
library: "local",
|
||||||
|
type: options.url,
|
||||||
$.getJSON("library/"+options.url,function(data) {
|
icon: 'fa fa-hdd-o',
|
||||||
$("#node-select-library").append(buildFileList("/",data));
|
label: RED._("library.types.local"),
|
||||||
$("#node-dialog-library-breadcrumbs a").click(function(e) {
|
path: "",
|
||||||
$(this).parent().nextAll().remove();
|
expanded: true,
|
||||||
$("#node-select-library").children().first().replaceWith(buildFileList("/",data));
|
writable: false,
|
||||||
e.stopPropagation();
|
children: [{
|
||||||
});
|
icon: 'fa fa-cube',
|
||||||
$( "#node-dialog-library-lookup" ).dialog( "open" );
|
label: options.type,
|
||||||
|
path: options.type+"/",
|
||||||
|
expanded: true,
|
||||||
|
children: items
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
loadLibraryBrowser.data(listing);
|
||||||
});
|
});
|
||||||
|
libraryEditor = ace.edit('node-dialog-library-load-preview-text',{
|
||||||
|
useWorker: false
|
||||||
|
});
|
||||||
|
libraryEditor.setTheme("ace/theme/tomorrow");
|
||||||
|
if (options.mode) {
|
||||||
|
libraryEditor.getSession().setMode(options.mode);
|
||||||
|
}
|
||||||
|
libraryEditor.setOptions({
|
||||||
|
readOnly: true,
|
||||||
|
highlightActiveLine: false,
|
||||||
|
highlightGutterLine: false
|
||||||
|
});
|
||||||
|
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
|
||||||
|
libraryEditor.$blockScrolling = Infinity;
|
||||||
|
|
||||||
|
$( "#node-dialog-library-load" ).dialog("option","title",RED._("library.typeLibrary", {type:options.type})).dialog( "open" );
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#node-input-'+options.type+'-menu-save-library').click(function(e) {
|
$('#node-input-'+options.type+'-menu-save-library').click(function(e) {
|
||||||
|
activeLibrary = options;
|
||||||
//var found = false;
|
//var found = false;
|
||||||
var name = $("#"+elementPrefix+"name").val().replace(/(^\s*)|(\s*$)/g,"");
|
var name = $("#"+elementPrefix+"name").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||||
|
|
||||||
//var buildPathList = function(data,root) {
|
|
||||||
// var paths = [];
|
|
||||||
// if (data.d) {
|
|
||||||
// for (var i in data.d) {
|
|
||||||
// var dn = root+(root==""?"":"/")+i;
|
|
||||||
// var d = {
|
|
||||||
// label:dn,
|
|
||||||
// files:[]
|
|
||||||
// };
|
|
||||||
// for (var f in data.d[i].f) {
|
|
||||||
// d.files.push(data.d[i].f[f].fn.split("/").slice(-1)[0]);
|
|
||||||
// }
|
|
||||||
// paths.push(d);
|
|
||||||
// paths = paths.concat(buildPathList(data.d[i],root+(root==""?"":"/")+i));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return paths;
|
|
||||||
//};
|
|
||||||
$("#node-dialog-library-save-folder").attr("value","");
|
|
||||||
|
|
||||||
var filename = name.replace(/[^\w-]/g,"-");
|
var filename = name.replace(/[^\w-]/g,"-");
|
||||||
if (filename === "") {
|
if (filename === "") {
|
||||||
filename = "unnamed-"+options.type;
|
filename = "unnamed-"+options.type;
|
||||||
}
|
}
|
||||||
$("#node-dialog-library-save-filename").attr("value",filename+".js");
|
$("#node-dialog-library-save-filename").attr("value",filename+".js");
|
||||||
|
|
||||||
//var paths = buildPathList(libraryData,"");
|
loadLibraryFolder("local",options.url, "", function(items) {
|
||||||
//$("#node-dialog-library-save-folder").autocomplete({
|
var listing = [{
|
||||||
// minLength: 0,
|
icon: 'fa fa-archive',
|
||||||
// source: paths,
|
label: RED._("library.types.local"),
|
||||||
// select: function( event, ui ) {
|
path: "",
|
||||||
// $("#node-dialog-library-save-filename").autocomplete({
|
expanded: true,
|
||||||
// minLength: 0,
|
writable: false,
|
||||||
// source: ui.item.files
|
children: [{
|
||||||
// });
|
icon: 'fa fa-cube',
|
||||||
// }
|
label: options.type,
|
||||||
//});
|
path: options.type+"/",
|
||||||
|
expanded: true,
|
||||||
|
children: items
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
saveLibraryBrowser.data(listing);
|
||||||
|
});
|
||||||
|
|
||||||
$( "#node-dialog-library-save" ).dialog( "open" );
|
$( "#node-dialog-library-save" ).dialog( "open" );
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
libraryEditor = ace.edit('node-select-library-text');
|
|
||||||
libraryEditor.setTheme("ace/theme/tomorrow");
|
|
||||||
if (options.mode) {
|
|
||||||
libraryEditor.getSession().setMode(options.mode);
|
|
||||||
}
|
|
||||||
libraryEditor.setOptions({
|
|
||||||
readOnly: true,
|
|
||||||
highlightActiveLine: false,
|
|
||||||
highlightGutterLine: false
|
|
||||||
});
|
|
||||||
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
|
|
||||||
libraryEditor.$blockScrolling = Infinity;
|
|
||||||
|
|
||||||
$( "#node-dialog-library-lookup" ).dialog({
|
|
||||||
title: RED._("library.typeLibrary", {type:options.type}),
|
|
||||||
modal: true,
|
|
||||||
autoOpen: false,
|
|
||||||
width: 800,
|
|
||||||
height: 450,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: RED._("common.label.cancel"),
|
|
||||||
click: function() {
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: RED._("common.label.load"),
|
|
||||||
class: "primary",
|
|
||||||
click: function() {
|
|
||||||
if (selectedLibraryItem) {
|
|
||||||
for (var i=0; i<options.fields.length; i++) {
|
|
||||||
var field = options.fields[i];
|
|
||||||
$("#"+elementPrefix+field).val(selectedLibraryItem[field]);
|
|
||||||
}
|
|
||||||
options.editor.setValue(libraryEditor.getValue(),-1);
|
|
||||||
}
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
open: function(e) {
|
|
||||||
var form = $("form",this);
|
|
||||||
form.height(form.parent().height()-30);
|
|
||||||
$("#node-select-library-text").height("100%");
|
|
||||||
$(".form-row:last-child",form).children().height(form.height()-60);
|
|
||||||
},
|
|
||||||
resize: function(e) {
|
|
||||||
var form = $("form",this);
|
|
||||||
form.height(form.parent().height()-30);
|
|
||||||
$(".form-row:last-child",form).children().height(form.height()-60);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function saveToLibrary(overwrite) {
|
|
||||||
var name = $("#"+elementPrefix+"name").val().replace(/(^\s*)|(\s*$)/g,"");
|
|
||||||
if (name === "") {
|
|
||||||
name = RED._("library.unnamedType",{type:options.type});
|
|
||||||
}
|
|
||||||
var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,"");
|
|
||||||
var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
|
|
||||||
if (filename === "" || !/.+\.js$/.test(filename)) {
|
|
||||||
RED.notify(RED._("library.invalidFilename"),"warning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fullpath = pathname+(pathname===""?"":"/")+filename;
|
|
||||||
if (!overwrite) {
|
|
||||||
//var pathnameParts = pathname.split("/");
|
|
||||||
//var exists = false;
|
|
||||||
//var ds = libraryData;
|
|
||||||
//for (var pnp in pathnameParts) {
|
|
||||||
// if (ds.d && pathnameParts[pnp] in ds.d) {
|
|
||||||
// ds = ds.d[pathnameParts[pnp]];
|
|
||||||
// } else {
|
|
||||||
// ds = null;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//if (ds && ds.f) {
|
|
||||||
// for (var f in ds.f) {
|
|
||||||
// if (ds.f[f].fn == fullpath) {
|
|
||||||
// exists = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//if (exists) {
|
|
||||||
// $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
|
|
||||||
// $("#node-dialog-library-save-confirm").dialog( "open" );
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
var queryArgs = [];
|
|
||||||
var data = {};
|
|
||||||
for (var i=0; i<options.fields.length; i++) {
|
|
||||||
var field = options.fields[i];
|
|
||||||
if (field == "name") {
|
|
||||||
data.name = name;
|
|
||||||
} else {
|
|
||||||
data[field] = $("#"+elementPrefix+field).val();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.text = options.editor.getValue();
|
|
||||||
$.ajax({
|
|
||||||
url:"library/"+options.url+'/'+fullpath,
|
|
||||||
type: "POST",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
contentType: "application/json; charset=utf-8"
|
|
||||||
}).done(function(data,textStatus,xhr) {
|
|
||||||
RED.notify(RED._("library.savedType", {type:options.type}),"success");
|
|
||||||
}).fail(function(xhr,textStatus,err) {
|
|
||||||
if (xhr.status === 401) {
|
|
||||||
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
|
|
||||||
} else {
|
|
||||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$( "#node-dialog-library-save-confirm" ).dialog({
|
|
||||||
title: RED._("library.saveToLibrary"),
|
|
||||||
modal: true,
|
|
||||||
autoOpen: false,
|
|
||||||
width: 530,
|
|
||||||
height: 230,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: RED._("common.label.cancel"),
|
|
||||||
click: function() {
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: RED._("common.label.save"),
|
|
||||||
class: "primary",
|
|
||||||
click: function() {
|
|
||||||
saveToLibrary(true);
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
$( "#node-dialog-library-save" ).dialog({
|
|
||||||
title: RED._("library.saveToLibrary"),
|
|
||||||
modal: true,
|
|
||||||
autoOpen: false,
|
|
||||||
width: 530,
|
|
||||||
height: 230,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: RED._("common.label.cancel"),
|
|
||||||
click: function() {
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: RED._("common.label.save"),
|
|
||||||
class: "primary",
|
|
||||||
click: function() {
|
|
||||||
saveToLibrary(false);
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportFlow() {
|
function exportFlow() {
|
||||||
//TODO: don't rely on the main dialog
|
console.warn("Deprecated call to RED.library.export");
|
||||||
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
|
}
|
||||||
$("#node-input-library-filename").attr('nodes',JSON.stringify(nns));
|
|
||||||
exportToLibraryDialog.dialog( "open" );
|
var menuOptionMenu;
|
||||||
|
function createBrowser(options) {
|
||||||
|
var panes = $('<div class="red-ui-library-browser"></div>').appendTo(options.container);
|
||||||
|
var dirList = $("<div>").css({width: "100%", height: "100%"}).appendTo(panes)
|
||||||
|
.treeList({}).on('treelistselect', function(event, item) {
|
||||||
|
if (options.onselect) {
|
||||||
|
options.onselect(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var itemTools = $("<div>").css({position: "absolute",bottom:"6px",right:"8px"});
|
||||||
|
var menuButton = $('<button class="editor-button editor-button-small" type="button"><i class="fa fa-ellipsis-h"></i></button>')
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
var elementPos = menuButton.offset();
|
||||||
|
|
||||||
|
var menuOptionMenu = RED.menu.init({id:"red-ui-library-browser-menu",
|
||||||
|
options: [
|
||||||
|
{id:"red-ui-library-browser-menu-addFolder",label:"New folder", onselect: function() {
|
||||||
|
var defaultFolderName = "new-folder";
|
||||||
|
var defaultFolderNameMatches = {};
|
||||||
|
|
||||||
|
var selected = dirList.treeList('selected');
|
||||||
|
if (!selected.children) {
|
||||||
|
selected = selected.parent;
|
||||||
|
}
|
||||||
|
var complete = function() {
|
||||||
|
selected.children.forEach(function(c) {
|
||||||
|
if (/^new-folder/.test(c.label)) {
|
||||||
|
defaultFolderNameMatches[c.label] = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var folderIndex = 2;
|
||||||
|
while(defaultFolderNameMatches[defaultFolderName]) {
|
||||||
|
defaultFolderName = "new-folder-"+(folderIndex++)
|
||||||
|
}
|
||||||
|
|
||||||
|
selected.treeList.expand();
|
||||||
|
var input = $('<input type="text" class="red-ui-treeList-input">').val(defaultFolderName);
|
||||||
|
var newItem = {
|
||||||
|
icon: "fa fa-folder-o",
|
||||||
|
children:[],
|
||||||
|
path: selected.path,
|
||||||
|
element: input
|
||||||
|
}
|
||||||
|
var confirmAdd = function() {
|
||||||
|
var val = input.val().trim();
|
||||||
|
if (val === "") {
|
||||||
|
cancelAdd();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
for (var i=0;i<selected.children.length;i++) {
|
||||||
|
if (selected.children[i].label === val) {
|
||||||
|
cancelAdd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newItem.treeList.remove();
|
||||||
|
var finalItem = {
|
||||||
|
library: selected.library,
|
||||||
|
type: selected.type,
|
||||||
|
icon: "fa fa-folder",
|
||||||
|
children:[],
|
||||||
|
label: val,
|
||||||
|
path: newItem.path+val+"/"
|
||||||
|
}
|
||||||
|
selected.treeList.addChild(finalItem,true);
|
||||||
|
}
|
||||||
|
var cancelAdd = function() {
|
||||||
|
newItem.treeList.remove();
|
||||||
|
}
|
||||||
|
input.on('keydown', function(evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
if (evt.keyCode === 13) {
|
||||||
|
confirmAdd();
|
||||||
|
} else if (evt.keyCode === 27) {
|
||||||
|
cancelAdd();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
input.blur(function() {
|
||||||
|
confirmAdd();
|
||||||
|
})
|
||||||
|
selected.treeList.addChild(newItem);
|
||||||
|
setTimeout(function() {
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
},400);
|
||||||
|
}
|
||||||
|
selected.treeList.expand(complete);
|
||||||
|
|
||||||
|
} },
|
||||||
|
// null,
|
||||||
|
// {id:"red-ui-library-browser-menu-rename",label:"Rename", onselect: function() {} },
|
||||||
|
// {id:"red-ui-library-browser-menu-delete",label:"Delete", onselect: function() {} }
|
||||||
|
]
|
||||||
|
}).on('mouseleave', function(){ $(this).remove(); dirList.focus() })
|
||||||
|
.on('mouseup', function() { var self = $(this);self.hide(); dirList.focus(); setTimeout(function() { self.remove() },100)})
|
||||||
|
.appendTo("body");
|
||||||
|
menuOptionMenu.css({
|
||||||
|
position: "absolute",
|
||||||
|
top: elementPos.top+"px",
|
||||||
|
left: (elementPos.left - menuOptionMenu.width() + 20)+"px"
|
||||||
|
}).show();
|
||||||
|
|
||||||
|
}).appendTo(itemTools);
|
||||||
|
if (options.folderTools) {
|
||||||
|
dirList.on('treelistselect', function(event, item) {
|
||||||
|
if (item.writable !== false && item.treeList) {
|
||||||
|
itemTools.appendTo(item.treeList.label);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSelected: function() {
|
||||||
|
return dirList.treeList('selected');
|
||||||
|
},
|
||||||
|
focus: function() {
|
||||||
|
dirList.focus();
|
||||||
|
},
|
||||||
|
data: function(content) {
|
||||||
|
dirList.treeList('data',content);
|
||||||
|
// setTimeout(function() {
|
||||||
|
// dirList.treeList('select',content[0]);
|
||||||
|
// },100);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: function() {
|
init: function() {
|
||||||
|
|
||||||
|
|
||||||
$(_librarySave).appendTo(document.body);
|
$(_librarySave).appendTo(document.body);
|
||||||
$(_librarySaveConfirm).appendTo(document.body);
|
|
||||||
$(_libraryLookup).appendTo(document.body);
|
$(_libraryLookup).appendTo(document.body);
|
||||||
|
|
||||||
RED.actions.add("core:library-export",exportFlow);
|
$( "#node-dialog-library-save" ).dialog({
|
||||||
|
title: RED._("library.saveToLibrary"),
|
||||||
RED.events.on("view:selection-changed",function(selection) {
|
modal: true,
|
||||||
if (!selection.nodes) {
|
autoOpen: false,
|
||||||
RED.menu.setDisabled("menu-item-export-library",true);
|
width: 800,
|
||||||
} else {
|
resizable: false,
|
||||||
RED.menu.setDisabled("menu-item-export-library",false);
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "node-dialog-library-save-button",
|
||||||
|
text: RED._("common.label.save"),
|
||||||
|
class: "primary",
|
||||||
|
click: function() {
|
||||||
|
saveToLibrary(false);
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
open: function(e) {
|
||||||
|
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
|
saveLibraryBrowser = RED.library.createBrowser({
|
||||||
loadFlowLibrary();
|
container: $("#node-dialog-library-save-browser"),
|
||||||
}
|
addFolderButton: true,
|
||||||
|
onselect: function(item) {
|
||||||
exportToLibraryDialog = $('<div id="library-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
if (item.label) {
|
||||||
.appendTo("body")
|
if (!item.children) {
|
||||||
.dialog({
|
$("#node-dialog-library-save-filename").val(item.label);
|
||||||
modal: true,
|
item = item.parent;
|
||||||
autoOpen: false,
|
}
|
||||||
width: 500,
|
if (item.writable === false) {
|
||||||
resizable: false,
|
$("#node-dialog-library-save-button").button("disable");
|
||||||
title: RED._("library.exportToLibrary"),
|
} else {
|
||||||
buttons: [
|
$("#node-dialog-library-save-button").button("enable");
|
||||||
{
|
|
||||||
id: "library-dialog-cancel",
|
|
||||||
text: RED._("common.label.cancel"),
|
|
||||||
click: function() {
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "library-dialog-ok",
|
|
||||||
class: "primary",
|
|
||||||
text: RED._("common.label.export"),
|
|
||||||
click: function() {
|
|
||||||
//TODO: move this to RED.library
|
|
||||||
var flowName = $("#node-input-library-filename").val();
|
|
||||||
flowName = flowName.trim();
|
|
||||||
if(flowName === "" || flowName.endsWith("/")) {
|
|
||||||
RED.notify(RED._("library.invalidFilename"),"warning");
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
url:'library/flows/'+flowName,
|
|
||||||
type: "POST",
|
|
||||||
data: $("#node-input-library-filename").attr('nodes'),
|
|
||||||
contentType: "application/json; charset=utf-8"
|
|
||||||
}).done(function() {
|
|
||||||
RED.library.loadFlowLibrary();
|
|
||||||
RED.notify(RED._("library.savedNodes"),"success");
|
|
||||||
}).fail(function(xhr,textStatus,err) {
|
|
||||||
if (xhr.status === 401) {
|
|
||||||
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
|
|
||||||
} else {
|
|
||||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
open: function(e) {
|
|
||||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
|
||||||
},
|
|
||||||
close: function(e) {
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
exportToLibraryDialog.children(".dialog-form").append($(
|
});
|
||||||
'<div class="form-row">'+
|
$("#node-dialog-library-save-filename").keyup(function() { validateExportFilename($(this))});
|
||||||
'<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
|
$("#node-dialog-library-save-filename").on('paste',function() { var input = $(this); setTimeout(function() { validateExportFilename(input)},10)});
|
||||||
'<input type="text" id="node-input-library-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">'+
|
|
||||||
'<input type="text" style="display: none;" />'+ // Second hidden input to prevent submit on Enter
|
$( "#node-dialog-library-load" ).dialog({
|
||||||
'</div>'
|
modal: true,
|
||||||
));
|
autoOpen: false,
|
||||||
|
width: 800,
|
||||||
|
resizable: false,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: RED._("common.label.load"),
|
||||||
|
class: "primary",
|
||||||
|
click: function() {
|
||||||
|
if (selectedLibraryItem) {
|
||||||
|
var elementPrefix = activeLibrary.elementPrefix || "node-input-";
|
||||||
|
for (var i=0; i<activeLibrary.fields.length; i++) {
|
||||||
|
var field = activeLibrary.fields[i];
|
||||||
|
$("#"+elementPrefix+field).val(selectedLibraryItem[field]);
|
||||||
|
}
|
||||||
|
activeLibrary.editor.setValue(libraryEditor.getValue(),-1);
|
||||||
|
}
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
open: function(e) {
|
||||||
|
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||||
|
},
|
||||||
|
close: function(e) {
|
||||||
|
if (libraryEditor) {
|
||||||
|
libraryEditor.destroy();
|
||||||
|
libraryEditor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadLibraryBrowser = RED.library.createBrowser({
|
||||||
|
container: $("#node-dialog-library-load-browser"),
|
||||||
|
onselect: function(file) {
|
||||||
|
var table = $("#node-dialog-library-load-preview-details-table").empty();
|
||||||
|
selectedLibraryItem = file.props;
|
||||||
|
if (file && file.label && !file.children) {
|
||||||
|
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
||||||
|
//TODO: nls + sanitize
|
||||||
|
var propRow = $('<tr class="node-info-node-row"><td>Type</td><td></td></tr>').appendTo(table);
|
||||||
|
$(propRow.children()[1]).text(activeLibrary.type);
|
||||||
|
if (file.props.hasOwnProperty('name')) {
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td>Name</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
||||||
|
$(propRow.children()[1]).text(file.props.name);
|
||||||
|
}
|
||||||
|
for (var p in file.props) {
|
||||||
|
if (file.props.hasOwnProperty(p) && p !== 'name' && p !== 'fn') {
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td></td><td></td></tr>').appendTo(table);
|
||||||
|
$(propRow.children()[0]).text(p);
|
||||||
|
RED.utils.createObjectElement(file.props[p]).appendTo(propRow.children()[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
libraryEditor.setValue(data,-1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
libraryEditor.setValue("",-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RED.panels.create({
|
||||||
|
container:$("#node-dialog-library-load-panes"),
|
||||||
|
dir: "horizontal"
|
||||||
|
});
|
||||||
|
RED.panels.create({
|
||||||
|
container:$("#node-dialog-library-load-preview"),
|
||||||
|
dir: "vertical"
|
||||||
|
});
|
||||||
},
|
},
|
||||||
create: createUI,
|
create: createUI,
|
||||||
loadFlowLibrary: loadFlowLibrary,
|
createBrowser:createBrowser,
|
||||||
|
export: exportFlow,
|
||||||
export: exportFlow
|
loadLibraryFolder: loadLibraryFolder
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -22,6 +22,10 @@ $form-input-focus-color: rgba(85,150,230,0.8);
|
|||||||
$form-input-border-color: #ccc;
|
$form-input-border-color: #ccc;
|
||||||
$form-input-border-selected-color: #aaa;
|
$form-input-border-selected-color: #aaa;
|
||||||
|
|
||||||
|
$list-item-color: #666;
|
||||||
|
$list-item-background-hover: #f3f3f3;
|
||||||
|
$list-item-background-active: #efefef;
|
||||||
|
$list-item-background-selected: #eee;
|
||||||
|
|
||||||
$node-selected-color: #ff7f0e;
|
$node-selected-color: #ff7f0e;
|
||||||
$port-selected-color: #ff7f0e;
|
$port-selected-color: #ff7f0e;
|
||||||
|
@ -48,3 +48,117 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.clipboard-dialog-tab-clipboard {
|
||||||
|
padding: 10px;
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
height: 300px;
|
||||||
|
line-height: 1.3em;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: #F3E7E7;
|
||||||
|
color: #533;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboard-dialog-tabs-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 120px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: white;
|
||||||
|
&>div {
|
||||||
|
height: calc(100% - 20px)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboard-dialog-tab-library {
|
||||||
|
.form-row {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard-dialog {
|
||||||
|
form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.form-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#clipboard-dialog-tab-library-name {
|
||||||
|
width: calc(100% - 120px);
|
||||||
|
}
|
||||||
|
#clipboard-dialog-export-tab-library-browser {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid $primary-border-color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#clipboard-dialog-import-tab-library {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#clipboard-dialog-import-tab-library-browser {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.red-ui-library-browser {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
.red-ui-treeList-container {
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
li {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-editableList-border {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-ui-treeList-label input.red-ui-treeList-input {
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#node-dialog-library-save-browser {
|
||||||
|
height: calc(100% - 60px);
|
||||||
|
border: 1px solid $primary-border-color;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
#node-dialog-library-load-browser {
|
||||||
|
// border: 1px solid $primary-border-color;
|
||||||
|
}
|
||||||
|
#node-dialog-library-load-panes {
|
||||||
|
border: 1px solid $primary-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#node-dialog-library-load-preview {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#node-dialog-library-load-preview-text {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#node-dialog-library-load-preview-details {
|
||||||
|
box-sizing: border-box;
|
||||||
|
.node-info-node-row:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,17 +36,10 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin workspace-button {
|
@mixin reset-a-style {
|
||||||
@include disable-selection;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
color: $workspace-button-color !important;
|
color: $workspace-button-color !important;
|
||||||
background: $workspace-button-background;
|
background: $workspace-button-background;
|
||||||
border: 1px solid $form-input-border-color;
|
|
||||||
text-align: center;
|
|
||||||
margin:0;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor:pointer;
|
|
||||||
|
|
||||||
&.disabled, &:disabled {
|
&.disabled, &:disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
@ -67,6 +60,19 @@
|
|||||||
background: $workspace-button-background-active;
|
background: $workspace-button-background-active;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin workspace-button {
|
||||||
|
@include disable-selection;
|
||||||
|
@include reset-a-style;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid $form-input-border-color;
|
||||||
|
text-align: center;
|
||||||
|
margin:0;
|
||||||
|
cursor:pointer;
|
||||||
|
|
||||||
// &.selected:not(.disabled):not(:disabled) {
|
// &.selected:not(.disabled):not(:disabled) {
|
||||||
// color: $workspace-button-color-selected !important;
|
// color: $workspace-button-color-selected !important;
|
||||||
// background: $workspace-button-background-active;
|
// background: $workspace-button-background-active;
|
||||||
@ -150,12 +156,12 @@
|
|||||||
}
|
}
|
||||||
&:not(.single) {
|
&:not(.single) {
|
||||||
color: $workspace-button-toggle-color !important;
|
color: $workspace-button-toggle-color !important;
|
||||||
background:$workspace-button-background-active;
|
background:$workspace-button-background;
|
||||||
margin-bottom: 1px;
|
margin-bottom: 1px;
|
||||||
|
|
||||||
&.selected:not(.disabled):not(:disabled) {
|
&.selected:not(.disabled):not(:disabled) {
|
||||||
color: $workspace-button-toggle-color-selected !important;
|
color: $workspace-button-toggle-color-selected !important;
|
||||||
background: $workspace-button-background;
|
background: $workspace-button-background-active;
|
||||||
border-bottom-width: 2px;
|
border-bottom-width: 2px;
|
||||||
border-bottom-color: $form-input-border-selected-color;
|
border-bottom-color: $form-input-border-selected-color;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -41,13 +41,13 @@
|
|||||||
|
|
||||||
.red-ui-panels.red-ui-panels-horizontal {
|
.red-ui-panels.red-ui-panels-horizontal {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.red-ui-panel {
|
&>.red-ui-panel {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: calc(50% - 4px);
|
width: calc(50% - 4px);
|
||||||
}
|
}
|
||||||
.red-ui-panels-separator {
|
&>.red-ui-panels-separator {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
@ -247,7 +247,7 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
&.red-ui-tab-link-button {
|
&.red-ui-tab-link-button {
|
||||||
&:not(.active) {
|
&:not(.active) {
|
||||||
background: #eee;
|
// background: #eee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.red-ui-tab-link-button-menu {
|
&.red-ui-tab-link-button-menu {
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
.red-ui-treeList {
|
.red-ui-treeList {
|
||||||
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.red-ui-treeList-container {
|
.red-ui-treeList-container {
|
||||||
@ -49,45 +51,49 @@
|
|||||||
transition: transform 0.1s ease-in-out;
|
transition: transform 0.1s ease-in-out;
|
||||||
}
|
}
|
||||||
.red-ui-editableList {
|
.red-ui-editableList {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
}
|
||||||
&.expanded {
|
&.expanded {
|
||||||
& > .red-ui-treeList-label .fa-angle-right {
|
& > .red-ui-treeList-label .fa-angle-right {
|
||||||
transform: rotate(90deg)
|
transform: rotate(90deg)
|
||||||
}
|
}
|
||||||
& > .red-ui-editableList {
|
// & > .red-ui-editableList {
|
||||||
display: block
|
// display: block
|
||||||
}
|
// }
|
||||||
& > .red-ui-treeList-spinner {
|
// & > .red-ui-treeList-spinner {
|
||||||
display: block;
|
// display: block;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
label.red-ui-treeList-label {
|
|
||||||
display: block;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.red-ui-treeList-label {
|
.red-ui-treeList-label {
|
||||||
@include disable-selection;
|
@include disable-selection;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
display: block;
|
display: block;
|
||||||
color: $form-text-color;
|
color: $list-item-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
// &:hover {
|
||||||
background: #f9f9f9;
|
// background: $list-item-background-hover;
|
||||||
color: $form-text-color;
|
// color: $list-item-color;
|
||||||
text-decoration: none;
|
// text-decoration: none;
|
||||||
}
|
// }
|
||||||
&:focus {
|
&:focus {
|
||||||
|
background: $list-item-background-hover;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: $form-text-color;
|
color: $list-item-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
&.selected {
|
||||||
|
background: $list-item-background-selected;
|
||||||
|
outline: none;
|
||||||
|
color: $list-item-color;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@ -101,7 +107,6 @@ label.red-ui-treeList-label {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.red-ui-treeList-spinner {
|
.red-ui-treeList-spinner {
|
||||||
display: none;
|
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background: url(images/spin.svg) 50% 50% no-repeat;
|
background: url(images/spin.svg) 50% 50% no-repeat;
|
||||||
background-size: auto 20px;
|
background-size: auto 20px;
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var fspath = require('path');
|
var fspath = require('path');
|
||||||
var when = require('when');
|
|
||||||
|
|
||||||
var runtime;
|
var runtime;
|
||||||
|
|
||||||
@ -24,7 +23,7 @@ var exampleRoots = {};
|
|||||||
var exampleFlows = null;
|
var exampleFlows = null;
|
||||||
|
|
||||||
function getFlowsFromPath(path) {
|
function getFlowsFromPath(path) {
|
||||||
return when.promise(function(resolve,reject) {
|
return new Promise(function(resolve,reject) {
|
||||||
var result = {};
|
var result = {};
|
||||||
fs.readdir(path,function(err,files) {
|
fs.readdir(path,function(err,files) {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
@ -37,11 +36,11 @@ function getFlowsFromPath(path) {
|
|||||||
promises.push(getFlowsFromPath(fullPath));
|
promises.push(getFlowsFromPath(fullPath));
|
||||||
} else if (/\.json$/.test(file)){
|
} else if (/\.json$/.test(file)){
|
||||||
validFiles.push(file);
|
validFiles.push(file);
|
||||||
promises.push(when.resolve(file.split(".")[0]))
|
promises.push(Promise.resolve(file.split(".")[0]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
var i=0;
|
var i=0;
|
||||||
when.all(promises).then(function(results) {
|
Promise.all(promises).then(function(results) {
|
||||||
results.forEach(function(r) {
|
results.forEach(function(r) {
|
||||||
if (typeof r === 'string') {
|
if (typeof r === 'string') {
|
||||||
result.f = result.f||[];
|
result.f = result.f||[];
|
||||||
@ -62,21 +61,20 @@ function getFlowsFromPath(path) {
|
|||||||
function addNodeExamplesDir(module,path) {
|
function addNodeExamplesDir(module,path) {
|
||||||
exampleRoots[module] = path;
|
exampleRoots[module] = path;
|
||||||
return getFlowsFromPath(path).then(function(result) {
|
return getFlowsFromPath(path).then(function(result) {
|
||||||
exampleFlows = exampleFlows||{d:{}};
|
exampleFlows = exampleFlows||{};
|
||||||
exampleFlows.d[module] = result;
|
exampleFlows[module] = result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function removeNodeExamplesDir(module) {
|
function removeNodeExamplesDir(module) {
|
||||||
delete exampleRoots[module];
|
delete exampleRoots[module];
|
||||||
if (exampleFlows && exampleFlows.d) {
|
if (exampleFlows) {
|
||||||
delete exampleFlows.d[module];
|
delete exampleFlows[module];
|
||||||
}
|
}
|
||||||
if (exampleFlows && Object.keys(exampleFlows.d).length === 0) {
|
if (exampleFlows && Object.keys(exampleFlows).length === 0) {
|
||||||
exampleFlows = null;
|
exampleFlows = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
exampleRoots = {};
|
exampleRoots = {};
|
||||||
exampleFlows = null;
|
exampleFlows = null;
|
||||||
|
@ -29,6 +29,7 @@ var api = module.exports = {
|
|||||||
* Gets an entry from the library.
|
* Gets an entry from the library.
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* @param {User} opts.user - the user calling the api
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {String} opts.library - the library
|
||||||
* @param {String} opts.type - the type of entry
|
* @param {String} opts.type - the type of entry
|
||||||
* @param {String} opts.path - the path of the entry
|
* @param {String} opts.path - the path of the entry
|
||||||
* @return {Promise<String|Object>} - resolves when complete
|
* @return {Promise<String|Object>} - resolves when complete
|
||||||
@ -36,12 +37,12 @@ var api = module.exports = {
|
|||||||
*/
|
*/
|
||||||
getEntry: function(opts) {
|
getEntry: function(opts) {
|
||||||
return new Promise(function(resolve,reject) {
|
return new Promise(function(resolve,reject) {
|
||||||
runtime.library.getEntry(opts.type,opts.path).then(function(result) {
|
runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) {
|
||||||
runtime.log.audit({event: "library.get",type:opts.type,path:opts.path});
|
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path});
|
||||||
return resolve(result);
|
return resolve(result);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
runtime.log.warn(runtime.log._("api.library.error-load-entry",{path:opts.path,message:err.toString()}));
|
runtime.log.warn(runtime.log._("api.library.error-load-entry",{library:opts.library,type:opts.type,path:opts.path,message:err.toString()}));
|
||||||
if (err.code === 'forbidden') {
|
if (err.code === 'forbidden') {
|
||||||
err.status = 403;
|
err.status = 403;
|
||||||
return reject(err);
|
return reject(err);
|
||||||
@ -50,10 +51,10 @@ var api = module.exports = {
|
|||||||
} else {
|
} else {
|
||||||
err.status = 400;
|
err.status = 400;
|
||||||
}
|
}
|
||||||
runtime.log.audit({event: "library.get",type:opts.type,path:opts.path,error:err.code});
|
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code});
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
runtime.log.audit({event: "library.get",type:opts.type,error:"not_found"});
|
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"});
|
||||||
var error = new Error();
|
var error = new Error();
|
||||||
error.code = "not_found";
|
error.code = "not_found";
|
||||||
error.status = 404;
|
error.status = 404;
|
||||||
@ -66,6 +67,7 @@ var api = module.exports = {
|
|||||||
* Saves an entry to the library
|
* Saves an entry to the library
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* @param {User} opts.user - the user calling the api
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {String} opts.library - the library
|
||||||
* @param {String} opts.type - the type of entry
|
* @param {String} opts.type - the type of entry
|
||||||
* @param {String} opts.path - the path of the entry
|
* @param {String} opts.path - the path of the entry
|
||||||
* @param {Object} opts.meta - any meta data associated with the entry
|
* @param {Object} opts.meta - any meta data associated with the entry
|
||||||
@ -75,7 +77,7 @@ var api = module.exports = {
|
|||||||
*/
|
*/
|
||||||
saveEntry: function(opts) {
|
saveEntry: function(opts) {
|
||||||
return new Promise(function(resolve,reject) {
|
return new Promise(function(resolve,reject) {
|
||||||
runtime.library.saveEntry(opts.type,opts.path,opts.meta,opts.body).then(function() {
|
runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() {
|
||||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path});
|
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path});
|
||||||
return resolve();
|
return resolve();
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
@ -91,30 +93,5 @@ var api = module.exports = {
|
|||||||
return reject(error);
|
return reject(error);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Returns a complete listing of all entries of a given type in the library.
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {User} opts.user - the user calling the api
|
|
||||||
* @param {String} opts.type - the type of entry
|
|
||||||
* @return {Promise<Object>} - the entry listing
|
|
||||||
* @memberof @node-red/runtime_library
|
|
||||||
*/
|
|
||||||
getEntries: function(opts) {
|
|
||||||
return new Promise(function(resolve,reject) {
|
|
||||||
if (opts.type !== 'flows') {
|
|
||||||
return reject(new Error("API only supports flows"));
|
|
||||||
|
|
||||||
}
|
|
||||||
runtime.storage.getAllFlows().then(function(flows) {
|
|
||||||
runtime.log.audit({event: "library.get.all",type:"flow"});
|
|
||||||
var examples = runtime.nodes.getNodeExampleFlows();
|
|
||||||
if (examples) {
|
|
||||||
flows.d = flows.d||{};
|
|
||||||
flows.d._examples_ = examples;
|
|
||||||
}
|
|
||||||
return resolve(flows);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
101
packages/node_modules/@node-red/runtime/lib/library/examples.js
vendored
Normal file
101
packages/node_modules/@node-red/runtime/lib/library/examples.js
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var runtime;
|
||||||
|
|
||||||
|
function init(_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntry(type,path) {
|
||||||
|
var examples = runtime.nodes.getNodeExampleFlows();
|
||||||
|
var result = [];
|
||||||
|
if (path === "") {
|
||||||
|
return Promise.resolve(Object.keys(examples));
|
||||||
|
} else {
|
||||||
|
path = path.replace(/\/$/,"");
|
||||||
|
var parts = path.split("/");
|
||||||
|
var module = parts.shift();
|
||||||
|
if (module[0] === "@") {
|
||||||
|
module = module+"/"+parts.shift();
|
||||||
|
}
|
||||||
|
if (examples.hasOwnProperty(module)) {
|
||||||
|
examples = examples[module];
|
||||||
|
examples = parts.reduce(function(ex,k) {
|
||||||
|
if (ex) {
|
||||||
|
if (ex.d && ex.d[k]) {
|
||||||
|
return ex.d[k]
|
||||||
|
}
|
||||||
|
if (ex.f && ex.f.indexOf(k) > -1) {
|
||||||
|
return runtime.nodes.getNodeExampleFlowPath(module,parts.join("/"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},examples);
|
||||||
|
|
||||||
|
if (!examples) {
|
||||||
|
return new Promise(function (resolve,reject) {
|
||||||
|
var error = new Error("not_found");
|
||||||
|
error.code = "not_found";
|
||||||
|
return reject(error);
|
||||||
|
});
|
||||||
|
} else if (typeof examples === 'string') {
|
||||||
|
return new Promise(function(resolve,reject) {
|
||||||
|
try {
|
||||||
|
fs.readFile(examples,'utf8',function(err, data) {
|
||||||
|
runtime.log.audit({event: "library.get",library:"_examples",type:"flow",path:path});
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
return resolve(data);
|
||||||
|
})
|
||||||
|
} catch(err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (examples.d) {
|
||||||
|
for (var d in examples.d) {
|
||||||
|
if (examples.d.hasOwnProperty(d)) {
|
||||||
|
result.push(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (examples.f) {
|
||||||
|
examples.f.forEach(function(f) {
|
||||||
|
result.push({fn:f})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new Promise(function (resolve,reject) {
|
||||||
|
var error = new Error("not_found");
|
||||||
|
error.code = "not_found";
|
||||||
|
return reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: '_examples_',
|
||||||
|
init: init,
|
||||||
|
getEntry: getEntry
|
||||||
|
}
|
@ -14,18 +14,22 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var fs = require('fs');
|
|
||||||
var fspath = require('path');
|
|
||||||
|
|
||||||
var runtime;
|
|
||||||
var knownTypes = {};
|
var knownTypes = {};
|
||||||
|
|
||||||
var storage;
|
var libraries = {};
|
||||||
|
|
||||||
|
|
||||||
|
function init(runtime) {
|
||||||
|
knownTypes = {
|
||||||
|
'flows': 'node-red'
|
||||||
|
};
|
||||||
|
|
||||||
|
libraries["_examples_"] = require("./examples");
|
||||||
|
libraries["_examples_"].init(runtime);
|
||||||
|
libraries["local"] = require("./local");
|
||||||
|
libraries["local"].init(runtime);
|
||||||
|
|
||||||
function init(_runtime) {
|
|
||||||
runtime = _runtime;
|
|
||||||
storage = runtime.storage;
|
|
||||||
knownTypes = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerType(id,type) {
|
function registerType(id,type) {
|
||||||
@ -37,66 +41,34 @@ function registerType(id,type) {
|
|||||||
knownTypes[type] = id;
|
knownTypes[type] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// function getAllEntries(type) {
|
function getEntry(library,type,path) {
|
||||||
// if (!knownTypes.hasOwnProperty(type)) {
|
if (!knownTypes.hasOwnProperty(type)) {
|
||||||
// throw new Error(`Unknown library type '${type}'`);
|
throw new Error(`Unknown library type '${type}'`);
|
||||||
// }
|
}
|
||||||
// }
|
if (libraries.hasOwnProperty(library)) {
|
||||||
|
return libraries[library].getEntry(type,path);
|
||||||
function getEntry(type,path) {
|
|
||||||
if (type !== 'flows') {
|
|
||||||
if (!knownTypes.hasOwnProperty(type)) {
|
|
||||||
throw new Error(`Unknown library type '${type}'`);
|
|
||||||
}
|
|
||||||
return storage.getLibraryEntry(type,path);
|
|
||||||
} else {
|
} else {
|
||||||
return new Promise(function(resolve,reject) {
|
throw new Error(`Unknown library '${library}'`);
|
||||||
if (path.indexOf("_examples_/") === 0) {
|
|
||||||
var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(path);
|
|
||||||
if (m) {
|
|
||||||
var module = m[1];
|
|
||||||
var entryPath = m[2];
|
|
||||||
var fullPath = runtime.nodes.getNodeExampleFlowPath(module,entryPath);
|
|
||||||
if (fullPath) {
|
|
||||||
try {
|
|
||||||
fs.readFile(fullPath,'utf8',function(err, data) {
|
|
||||||
runtime.log.audit({event: "library.get",type:"flow",path:path});
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
return resolve(data);
|
|
||||||
})
|
|
||||||
} catch(err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// IF we get here, we didn't find the file
|
|
||||||
var error = new Error("not_found");
|
|
||||||
error.code = "not_found";
|
|
||||||
return reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(storage.getFlow(path));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function saveEntry(type,path,meta,body) {
|
function saveEntry(library,type,path,meta,body) {
|
||||||
if (type !== 'flows') {
|
if (!knownTypes.hasOwnProperty(type)) {
|
||||||
if (!knownTypes.hasOwnProperty(type)) {
|
throw new Error(`Unknown library type '${type}'`);
|
||||||
throw new Error(`Unknown library type '${type}'`);
|
}
|
||||||
|
if (libraries.hasOwnProperty(library)) {
|
||||||
|
if (libraries[library].hasOwnProperty("saveEntry")) {
|
||||||
|
return libraries[library].saveEntry(type,path,meta,body);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Library '${library}' is read-only`);
|
||||||
}
|
}
|
||||||
return storage.saveLibraryEntry(type,path,meta,body);
|
|
||||||
} else {
|
} else {
|
||||||
return storage.saveFlow(path,body);
|
throw new Error(`Unknown library '${library}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: init,
|
init: init,
|
||||||
register: registerType,
|
register: registerType,
|
||||||
// getAllEntries: getAllEntries,
|
|
||||||
getEntry: getEntry,
|
getEntry: getEntry,
|
||||||
saveEntry: saveEntry
|
saveEntry: saveEntry
|
||||||
|
|
||||||
|
37
packages/node_modules/@node-red/runtime/lib/library/local.js
vendored
Normal file
37
packages/node_modules/@node-red/runtime/lib/library/local.js
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var runtime;
|
||||||
|
var storage;
|
||||||
|
|
||||||
|
function init(_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
storage = runtime.storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntry(type,path) {
|
||||||
|
return storage.getLibraryEntry(type,path);
|
||||||
|
}
|
||||||
|
function saveEntry(type,path,meta,body) {
|
||||||
|
return storage.saveLibraryEntry(type,path,meta,body);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'local',
|
||||||
|
init: init,
|
||||||
|
getEntry: getEntry,
|
||||||
|
saveEntry: saveEntry
|
||||||
|
}
|
@ -31,60 +31,11 @@ describe("api/editor/library", function() {
|
|||||||
before(function() {
|
before(function() {
|
||||||
app = express();
|
app = express();
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
app.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,library.getEntry);
|
||||||
app.get("/library/flows",library.getAll);
|
app.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,library.saveEntry);
|
||||||
app.post(/library\/([^\/]+)\/(.*)/,library.saveEntry);
|
|
||||||
app.get(/library\/([^\/]+)(?:$|\/(.*))/,library.getEntry);
|
|
||||||
});
|
});
|
||||||
after(function() {
|
after(function() {
|
||||||
});
|
});
|
||||||
it('returns all flows', function(done) {
|
|
||||||
library.init({
|
|
||||||
library: {
|
|
||||||
getEntries: function(opts) {
|
|
||||||
return Promise.resolve({a:1,b:2});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
res.body.should.have.property('a',1);
|
|
||||||
res.body.should.have.property('b',2);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
it('returns an error on all flows', function(done) {
|
|
||||||
library.init({
|
|
||||||
library: {
|
|
||||||
getEntries: function(opts) {
|
|
||||||
var err = new Error("message");
|
|
||||||
err.code = "random_error";
|
|
||||||
err.status = 400;
|
|
||||||
var p = Promise.reject(err);
|
|
||||||
p.catch(()=>{});
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows')
|
|
||||||
.expect(400)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
res.body.should.have.property('code');
|
|
||||||
res.body.code.should.be.equal("random_error");
|
|
||||||
res.body.should.have.property('message');
|
|
||||||
res.body.message.should.be.equal("message");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an individual entry - flow type', function(done) {
|
it('returns an individual entry - flow type', function(done) {
|
||||||
var opts;
|
var opts;
|
||||||
@ -97,7 +48,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.get('/library/flows/abc')
|
.get('/library/local/flows/abc')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -105,6 +56,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
res.body.should.have.property('a',1);
|
res.body.should.have.property('a',1);
|
||||||
res.body.should.have.property('b',2);
|
res.body.should.have.property('b',2);
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','flows');
|
opts.should.have.property('type','flows');
|
||||||
opts.should.have.property('path','abc');
|
opts.should.have.property('path','abc');
|
||||||
done();
|
done();
|
||||||
@ -121,7 +73,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.get('/library/flows/abc/def')
|
.get('/library/local/flows/abc/def')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -129,6 +81,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
res.body.should.have.property('a',1);
|
res.body.should.have.property('a',1);
|
||||||
res.body.should.have.property('b',2);
|
res.body.should.have.property('b',2);
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','flows');
|
opts.should.have.property('type','flows');
|
||||||
opts.should.have.property('path','abc/def');
|
opts.should.have.property('path','abc/def');
|
||||||
done();
|
done();
|
||||||
@ -145,12 +98,13 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.get('/library/non-flow/abc')
|
.get('/library/local/non-flow/abc')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','non-flow');
|
opts.should.have.property('type','non-flow');
|
||||||
opts.should.have.property('path','abc');
|
opts.should.have.property('path','abc');
|
||||||
res.text.should.eql('{"a":1,"b":2}');
|
res.text.should.eql('{"a":1,"b":2}');
|
||||||
@ -168,7 +122,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.get('/library/non-flow/abc/def')
|
.get('/library/local/non-flow/abc/def')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -176,6 +130,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
res.body.should.have.property('a',1);
|
res.body.should.have.property('a',1);
|
||||||
res.body.should.have.property('b',2);
|
res.body.should.have.property('b',2);
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','non-flow');
|
opts.should.have.property('type','non-flow');
|
||||||
opts.should.have.property('path','abc/def');
|
opts.should.have.property('path','abc/def');
|
||||||
done();
|
done();
|
||||||
@ -198,12 +153,13 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.get('/library/flows/123')
|
.get('/library/local/flows/123')
|
||||||
.expect(400)
|
.expect(400)
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','flows');
|
opts.should.have.property('type','flows');
|
||||||
opts.should.have.property('path','123');
|
opts.should.have.property('path','123');
|
||||||
|
|
||||||
@ -227,13 +183,14 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.post('/library/flows/abc/def')
|
.post('/library/local/flows/abc/def')
|
||||||
.expect(204)
|
.expect(204)
|
||||||
.send({a:1,b:2,c:3})
|
.send({a:1,b:2,c:3})
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','flows');
|
opts.should.have.property('type','flows');
|
||||||
opts.should.have.property('path','abc/def');
|
opts.should.have.property('path','abc/def');
|
||||||
opts.should.have.property('meta',{});
|
opts.should.have.property('meta',{});
|
||||||
@ -253,13 +210,14 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.post('/library/non-flow/abc/def')
|
.post('/library/local/non-flow/abc/def')
|
||||||
.expect(204)
|
.expect(204)
|
||||||
.send({a:1,b:2,text:"123"})
|
.send({a:1,b:2,text:"123"})
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('type','non-flow');
|
opts.should.have.property('type','non-flow');
|
||||||
opts.should.have.property('path','abc/def');
|
opts.should.have.property('path','abc/def');
|
||||||
opts.should.have.property('meta',{a:1,b:2});
|
opts.should.have.property('meta',{a:1,b:2});
|
||||||
@ -284,7 +242,7 @@ describe("api/editor/library", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.post('/library/non-flow/abc/def')
|
.post('/library/local/non-flow/abc/def')
|
||||||
.send({a:1,b:2,text:"123"})
|
.send({a:1,b:2,text:"123"})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
.end(function(err,res) {
|
.end(function(err,res) {
|
||||||
@ -292,6 +250,7 @@ describe("api/editor/library", function() {
|
|||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
opts.should.have.property('type','non-flow');
|
opts.should.have.property('type','non-flow');
|
||||||
|
opts.should.have.property('library','local');
|
||||||
opts.should.have.property('path','abc/def');
|
opts.should.have.property('path','abc/def');
|
||||||
|
|
||||||
res.body.should.have.property('code');
|
res.body.should.have.property('code');
|
||||||
|
@ -38,7 +38,7 @@ describe("library api", function() {
|
|||||||
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
|
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
|
||||||
try {
|
try {
|
||||||
var flows = library.getExampleFlows();
|
var flows = library.getExampleFlows();
|
||||||
flows.should.deepEqual({"d":{"test-module":{"f":["one"]}}});
|
flows.should.deepEqual({"test-module":{"f":["one"]}});
|
||||||
|
|
||||||
var examplePath = library.getExampleFlowPath('test-module','one');
|
var examplePath = library.getExampleFlowPath('test-module','one');
|
||||||
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
|
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
|
||||||
|
@ -38,7 +38,7 @@ describe("runtime-api/library", function() {
|
|||||||
library.init({
|
library.init({
|
||||||
log: mockLog,
|
log: mockLog,
|
||||||
library: {
|
library: {
|
||||||
getEntry: function(type,path) {
|
getEntry: function(library, type,path) {
|
||||||
if (type === "known") {
|
if (type === "known") {
|
||||||
return Promise.resolve("known");
|
return Promise.resolve("known");
|
||||||
} else if (type === "forbidden") {
|
} else if (type === "forbidden") {
|
||||||
@ -67,13 +67,13 @@ describe("runtime-api/library", function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
it("returns a known entry", function(done) {
|
it("returns a known entry", function(done) {
|
||||||
library.getEntry({type: "known", path: "/abc"}).then(function(result) {
|
library.getEntry({library: "local",type: "known", path: "/abc"}).then(function(result) {
|
||||||
result.should.eql("known")
|
result.should.eql("known")
|
||||||
done();
|
done();
|
||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
it("rejects a forbidden entry", function(done) {
|
it("rejects a forbidden entry", function(done) {
|
||||||
library.getEntry({type: "forbidden", path: "/abc"}).then(function(result) {
|
library.getEntry({library: "local",type: "forbidden", path: "/abc"}).then(function(result) {
|
||||||
done(new Error("did not reject"));
|
done(new Error("did not reject"));
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
err.should.have.property("code","forbidden");
|
err.should.have.property("code","forbidden");
|
||||||
@ -82,7 +82,7 @@ describe("runtime-api/library", function() {
|
|||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
it("rejects an unknown entry", function(done) {
|
it("rejects an unknown entry", function(done) {
|
||||||
library.getEntry({type: "not_found", path: "/abc"}).then(function(result) {
|
library.getEntry({library: "local",type: "not_found", path: "/abc"}).then(function(result) {
|
||||||
done(new Error("did not reject"));
|
done(new Error("did not reject"));
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
err.should.have.property("code","not_found");
|
err.should.have.property("code","not_found");
|
||||||
@ -91,7 +91,7 @@ describe("runtime-api/library", function() {
|
|||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
it("rejects a blank (unknown) entry", function(done) {
|
it("rejects a blank (unknown) entry", function(done) {
|
||||||
library.getEntry({type: "blank", path: "/abc"}).then(function(result) {
|
library.getEntry({library: "local",type: "blank", path: "/abc"}).then(function(result) {
|
||||||
done(new Error("did not reject"));
|
done(new Error("did not reject"));
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
err.should.have.property("code","not_found");
|
err.should.have.property("code","not_found");
|
||||||
@ -100,7 +100,7 @@ describe("runtime-api/library", function() {
|
|||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
it("rejects unexpected error", function(done) {
|
it("rejects unexpected error", function(done) {
|
||||||
library.getEntry({type: "error", path: "/abc"}).then(function(result) {
|
library.getEntry({library: "local",type: "error", path: "/abc"}).then(function(result) {
|
||||||
done(new Error("did not reject"));
|
done(new Error("did not reject"));
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
err.should.have.property("status",400);
|
err.should.have.property("status",400);
|
||||||
@ -114,7 +114,7 @@ describe("runtime-api/library", function() {
|
|||||||
library.init({
|
library.init({
|
||||||
log: mockLog,
|
log: mockLog,
|
||||||
library: {
|
library: {
|
||||||
saveEntry: function(type,path,meta,body) {
|
saveEntry: function(library,type,path,meta,body) {
|
||||||
opts = {type,path,meta,body};
|
opts = {type,path,meta,body};
|
||||||
if (type === "known") {
|
if (type === "known") {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@ -137,7 +137,7 @@ describe("runtime-api/library", function() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("saves an entry", function(done) {
|
it("saves an entry", function(done) {
|
||||||
library.saveEntry({type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
library.saveEntry({library: "local",type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
||||||
opts.should.have.property("type","known");
|
opts.should.have.property("type","known");
|
||||||
opts.should.have.property("path","/abc");
|
opts.should.have.property("path","/abc");
|
||||||
opts.should.have.property("meta",{a:1});
|
opts.should.have.property("meta",{a:1});
|
||||||
@ -146,7 +146,7 @@ describe("runtime-api/library", function() {
|
|||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
it("rejects a forbidden entry", function(done) {
|
it("rejects a forbidden entry", function(done) {
|
||||||
library.saveEntry({type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
library.saveEntry({library: "local",type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
||||||
done(new Error("did not reject"));
|
done(new Error("did not reject"));
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
err.should.have.property("code","forbidden");
|
err.should.have.property("code","forbidden");
|
||||||
@ -155,7 +155,7 @@ describe("runtime-api/library", function() {
|
|||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
it("rejects an unknown entry", function(done) {
|
it("rejects an unknown entry", function(done) {
|
||||||
library.saveEntry({type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
library.saveEntry({library: "local",type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
||||||
done(new Error("did not reject"));
|
done(new Error("did not reject"));
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
err.should.have.property("status",400);
|
err.should.have.property("status",400);
|
||||||
@ -163,377 +163,5 @@ describe("runtime-api/library", function() {
|
|||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe("getEntries", function() {
|
|
||||||
var opts;
|
|
||||||
before(function() {
|
|
||||||
library.init({
|
|
||||||
log: mockLog,
|
|
||||||
storage: {
|
|
||||||
getAllFlows: function() {
|
|
||||||
return Promise.resolve({a:1});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nodes: {
|
|
||||||
getNodeExampleFlows: function() {
|
|
||||||
return {b:2};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("returns all flows", function(done) {
|
|
||||||
library.getEntries({type:"flows"}).then(function(result) {
|
|
||||||
result.should.eql({a:1,d:{_examples_:{b:2}}});
|
|
||||||
done();
|
|
||||||
}).catch(done)
|
|
||||||
});
|
|
||||||
it("fails for non-flows (currently)", function(done) {
|
|
||||||
library.getEntries({type:"functions"}).then(function(result) {
|
|
||||||
done(new Error("did not reject"));
|
|
||||||
}).catch(function(err) {
|
|
||||||
done();
|
|
||||||
}).catch(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
var should = require("should");
|
|
||||||
var sinon = require("sinon");
|
|
||||||
var fs = require("fs");
|
|
||||||
var fspath = require('path');
|
|
||||||
var request = require('supertest');
|
|
||||||
var express = require('express');
|
|
||||||
var bodyParser = require('body-parser');
|
|
||||||
|
|
||||||
var when = require('when');
|
|
||||||
|
|
||||||
var app;
|
|
||||||
var library = require("../../../../red/api/editor/library");
|
|
||||||
var auth = require("../../../../red/api/auth");
|
|
||||||
|
|
||||||
describe("api/editor/library", function() {
|
|
||||||
|
|
||||||
function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
|
|
||||||
var flows = _flows;
|
|
||||||
var libraryEntries = _libraryEntries;
|
|
||||||
library.init(app,{
|
|
||||||
log:{audit:function(){},_:function(){},warn:function(){}},
|
|
||||||
storage: {
|
|
||||||
init: function() {
|
|
||||||
return when.resolve();
|
|
||||||
},
|
|
||||||
getAllFlows: function() {
|
|
||||||
return when.resolve(flows);
|
|
||||||
},
|
|
||||||
getFlow: function(fn) {
|
|
||||||
if (flows[fn]) {
|
|
||||||
return when.resolve(flows[fn]);
|
|
||||||
} else if (fn.indexOf("..")!==-1) {
|
|
||||||
var err = new Error();
|
|
||||||
err.code = 'forbidden';
|
|
||||||
return when.reject(err);
|
|
||||||
} else {
|
|
||||||
return when.reject();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveFlow: function(fn,data) {
|
|
||||||
if (fn.indexOf("..")!==-1) {
|
|
||||||
var err = new Error();
|
|
||||||
err.code = 'forbidden';
|
|
||||||
return when.reject(err);
|
|
||||||
}
|
|
||||||
flows[fn] = data;
|
|
||||||
return when.resolve();
|
|
||||||
},
|
|
||||||
getLibraryEntry: function(type,path) {
|
|
||||||
if (path.indexOf("..")!==-1) {
|
|
||||||
var err = new Error();
|
|
||||||
err.code = 'forbidden';
|
|
||||||
return when.reject(err);
|
|
||||||
}
|
|
||||||
if (libraryEntries[type] && libraryEntries[type][path]) {
|
|
||||||
return when.resolve(libraryEntries[type][path]);
|
|
||||||
} else {
|
|
||||||
return when.reject();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveLibraryEntry: function(type,path,meta,body) {
|
|
||||||
if (path.indexOf("..")!==-1) {
|
|
||||||
var err = new Error();
|
|
||||||
err.code = 'forbidden';
|
|
||||||
return when.reject(err);
|
|
||||||
}
|
|
||||||
libraryEntries[type][path] = body;
|
|
||||||
return when.resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
on: function(){},
|
|
||||||
removeListener: function(){}
|
|
||||||
},
|
|
||||||
nodes: {
|
|
||||||
getNodeExampleFlows: function() {
|
|
||||||
return _examples;
|
|
||||||
},
|
|
||||||
getNodeExampleFlowPath: _exampleFlowPathFunction
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("flows", function() {
|
|
||||||
before(function() {
|
|
||||||
app = express();
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
app.get("/library/flows",library.getAll);
|
|
||||||
app.post(new RegExp("/library/flows\/(.*)"),library.post);
|
|
||||||
app.get(new RegExp("/library/flows\/(.*)"),library.get);
|
|
||||||
app.response.sendFile = function (path) {
|
|
||||||
app.response.json.call(this, {sendFile: path});
|
|
||||||
};
|
|
||||||
sinon.stub(fs,"statSync",function() { return true; });
|
|
||||||
});
|
|
||||||
after(function() {
|
|
||||||
fs.statSync.restore();
|
|
||||||
});
|
|
||||||
it('returns empty result', function(done) {
|
|
||||||
initLibrary({},{flows:{}});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.body.should.not.have.property('f');
|
|
||||||
res.body.should.not.have.property('d');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 404 for non-existent entry', function(done) {
|
|
||||||
initLibrary({},{flows:{}});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows/foo')
|
|
||||||
.expect(404)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('can store and retrieve item', function(done) {
|
|
||||||
initLibrary({},{flows:{}});
|
|
||||||
var flow = '[]';
|
|
||||||
request(app)
|
|
||||||
.post('/library/flows/foo')
|
|
||||||
.set('Content-Type', 'application/json')
|
|
||||||
.send(flow)
|
|
||||||
.expect(204).end(function (err, res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows/foo')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.text.should.equal(flow);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lists a stored item', function(done) {
|
|
||||||
initLibrary({f:["bar"]});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.body.should.have.property('f');
|
|
||||||
should.deepEqual(res.body.f,['bar']);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 403 for malicious get attempt', function(done) {
|
|
||||||
initLibrary({});
|
|
||||||
// without the userDir override the malicious url would be
|
|
||||||
// http://127.0.0.1:1880/library/flows/../../package to
|
|
||||||
// obtain package.json from the node-red root.
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows/../../../../../package')
|
|
||||||
.expect(403)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
it('returns 403 for malicious post attempt', function(done) {
|
|
||||||
initLibrary({});
|
|
||||||
// without the userDir override the malicious url would be
|
|
||||||
// http://127.0.0.1:1880/library/flows/../../package to
|
|
||||||
// obtain package.json from the node-red root.
|
|
||||||
request(app)
|
|
||||||
.post('/library/flows/../../../../../package')
|
|
||||||
.expect(403)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
it('includes examples flows if set', function(done) {
|
|
||||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
|
||||||
initLibrary({},{},examples);
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.body.should.have.property('d');
|
|
||||||
res.body.d.should.have.property('_examples_');
|
|
||||||
should.deepEqual(res.body.d._examples_,examples);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can retrieve an example flow', function(done) {
|
|
||||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
|
||||||
initLibrary({},{},examples,function(module,path) {
|
|
||||||
return module + ':' + path
|
|
||||||
});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows/_examples_/node-module/example-one')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.body.should.have.property('sendFile',
|
|
||||||
fspath.resolve('node-module') + ':example-one');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can retrieve an example flow in an org scoped package', function(done) {
|
|
||||||
var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}};
|
|
||||||
initLibrary({},{},examples,function(module,path) {
|
|
||||||
return module + ':' + path
|
|
||||||
});
|
|
||||||
request(app)
|
|
||||||
.get('/library/flows/_examples_/@org_scope/node_package/example-one')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.body.should.have.property('sendFile',
|
|
||||||
fspath.resolve('@org_scope/node_package') +
|
|
||||||
':example-one');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("type", function() {
|
|
||||||
before(function() {
|
|
||||||
|
|
||||||
app = express();
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
initLibrary({},{});
|
|
||||||
auth.init({settings:{}});
|
|
||||||
library.register("test");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty result', function(done) {
|
|
||||||
initLibrary({},{'test':{"":[]}});
|
|
||||||
request(app)
|
|
||||||
.get('/library/test')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.body.should.not.have.property('f');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 404 for non-existent entry', function(done) {
|
|
||||||
initLibrary({},{});
|
|
||||||
request(app)
|
|
||||||
.get('/library/test/foo')
|
|
||||||
.expect(404)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can store and retrieve item', function(done) {
|
|
||||||
initLibrary({},{'test':{}});
|
|
||||||
var flow = {text:"test content"};
|
|
||||||
request(app)
|
|
||||||
.post('/library/test/foo')
|
|
||||||
.set('Content-Type', 'application/json')
|
|
||||||
.send(flow)
|
|
||||||
.expect(204).end(function (err, res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
request(app)
|
|
||||||
.get('/library/test/foo')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
res.text.should.equal(flow.text);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lists a stored item', function(done) {
|
|
||||||
initLibrary({},{'test':{'a':['abc','def']}});
|
|
||||||
request(app)
|
|
||||||
.get('/library/test/a')
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
// This response isn't strictly accurate - but it
|
|
||||||
// verifies the api returns what storage gave it
|
|
||||||
should.deepEqual(res.body,['abc','def']);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('returns 403 for malicious access attempt', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/library/test/../../../../../../../../../../etc/passwd')
|
|
||||||
.expect(403)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 403 for malicious access attempt', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
|
|
||||||
.expect(403)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 403 for malicious access attempt', function(done) {
|
|
||||||
request(app)
|
|
||||||
.post('/library/test/../../../../../../../../../../etc/passwd')
|
|
||||||
.set('Content-Type', 'text/plain')
|
|
||||||
.send('root:x:0:0:root:/root:/usr/bin/tclsh')
|
|
||||||
.expect(403)
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
138
test/unit/@node-red/runtime/lib/library/examples_spec.js
Normal file
138
test/unit/@node-red/runtime/lib/library/examples_spec.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var should = require("should");
|
||||||
|
var sinon = require("sinon");
|
||||||
|
var fs = require("fs");
|
||||||
|
|
||||||
|
var NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
var examplesLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/examples")
|
||||||
|
|
||||||
|
var mockLog = {
|
||||||
|
log: sinon.stub(),
|
||||||
|
debug: sinon.stub(),
|
||||||
|
trace: sinon.stub(),
|
||||||
|
warn: sinon.stub(),
|
||||||
|
info: sinon.stub(),
|
||||||
|
metric: sinon.stub(),
|
||||||
|
audit: sinon.stub(),
|
||||||
|
_: function() { return "abc"}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("runtime/library/examples", function() {
|
||||||
|
describe("getEntry", function() {
|
||||||
|
before(function() {
|
||||||
|
examplesLibrary.init({
|
||||||
|
log: mockLog,
|
||||||
|
storage: {
|
||||||
|
getLibraryEntry: function(type,path) {
|
||||||
|
return Promise.resolve({type,path});
|
||||||
|
},
|
||||||
|
getFlow: function(path) {
|
||||||
|
return Promise.resolve({path});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
getNodeExampleFlows: function() {
|
||||||
|
return {
|
||||||
|
"test-module": {
|
||||||
|
f: ["abc"]
|
||||||
|
},
|
||||||
|
"@scope/test-module": {
|
||||||
|
f: ["abc","throw"]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getNodeExampleFlowPath: function(module,entryPath) {
|
||||||
|
if (module === "unknown") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return "/tmp/"+module+"/"+entryPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sinon.stub(fs,"readFile", function(path,opts,callback) {
|
||||||
|
if (path === "/tmp/test-module/abc") {
|
||||||
|
callback(null,"Example flow result");
|
||||||
|
} else if (path === "/tmp/@scope/test-module/abc") {
|
||||||
|
callback(null,"Example scope flow result");
|
||||||
|
} else if (path === "/tmp/test-module/throw") {
|
||||||
|
throw new Error("Instant error")
|
||||||
|
} else {
|
||||||
|
callback(new Error("Unexpected path:"+path))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
fs.readFile.restore();
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('returns a flow example entry', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","test-module/abc").then(function(result) {
|
||||||
|
result.should.eql("Example flow result");
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('returns a flow example listing - top level', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","").then(function(result) {
|
||||||
|
result.should.eql([ 'test-module', '@scope/test-module' ])
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it ('returns a flow example listing - in module', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","test-module").then(function(result) {
|
||||||
|
result.should.eql([{ fn: 'abc' }])
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it ('returns a flow example listing - in scoped module', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","@scope/test-module").then(function(result) {
|
||||||
|
result.should.eql([{ fn: 'abc' }, {fn: 'throw'}])
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it ('returns a flow example entry from scoped module', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","@scope/test-module/abc").then(function(result) {
|
||||||
|
result.should.eql("Example scope flow result");
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it ('returns an error for unknown flow example entry', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","unknown/abc").then(function(result) {
|
||||||
|
done(new Error("No error thrown"))
|
||||||
|
}).catch(function(err) {
|
||||||
|
err.should.have.property("code","not_found");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it ('returns an error for file load error - async', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","test-module/unknown").then(function(result) {
|
||||||
|
done(new Error("No error thrown"))
|
||||||
|
}).catch(function(err) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it ('returns an error for file load error - sync', function(done) {
|
||||||
|
examplesLibrary.getEntry("flows","test-module/throw").then(function(result) {
|
||||||
|
done(new Error("No error thrown"))
|
||||||
|
}).catch(function(err) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
var should = require("should");
|
var should = require("should");
|
||||||
var sinon = require("sinon");
|
var sinon = require("sinon");
|
||||||
var fs = require("fs");
|
|
||||||
|
|
||||||
var NR_TEST_UTILS = require("nr-test-utils");
|
var NR_TEST_UTILS = require("nr-test-utils");
|
||||||
var library = NR_TEST_UTILS.require("@node-red/runtime/lib/library/index")
|
var library = NR_TEST_UTILS.require("@node-red/runtime/lib/library/index")
|
||||||
|
var localLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/local")
|
||||||
|
var examplesLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/examples")
|
||||||
|
|
||||||
var mockLog = {
|
var mockLog = {
|
||||||
log: sinon.stub(),
|
log: sinon.stub(),
|
||||||
@ -34,6 +35,36 @@ var mockLog = {
|
|||||||
|
|
||||||
|
|
||||||
describe("runtime/library", function() {
|
describe("runtime/library", function() {
|
||||||
|
before(function() {
|
||||||
|
sinon.stub(localLibrary,"getEntry",function(type,path) {
|
||||||
|
return Promise.resolve({
|
||||||
|
library: "local",
|
||||||
|
type:type,
|
||||||
|
path:path
|
||||||
|
})
|
||||||
|
});
|
||||||
|
sinon.stub(localLibrary,"saveEntry",function(type, path, meta, body) {
|
||||||
|
return Promise.resolve({
|
||||||
|
library: "local",
|
||||||
|
type:type,
|
||||||
|
path:path,
|
||||||
|
meta:meta,
|
||||||
|
body:body
|
||||||
|
})
|
||||||
|
});
|
||||||
|
sinon.stub(examplesLibrary,"getEntry",function(type,path) {
|
||||||
|
return Promise.resolve({
|
||||||
|
library: "_examples_",
|
||||||
|
type:type,
|
||||||
|
path:path
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
localLibrary.getEntry.restore();
|
||||||
|
localLibrary.saveEntry.restore();
|
||||||
|
examplesLibrary.getEntry.restore();
|
||||||
|
})
|
||||||
describe("register", function() {
|
describe("register", function() {
|
||||||
// it("throws error for duplicate type", function() {
|
// it("throws error for duplicate type", function() {
|
||||||
// library.init({});
|
// library.init({});
|
||||||
@ -43,47 +74,19 @@ describe("runtime/library", function() {
|
|||||||
})
|
})
|
||||||
describe("getEntry", function() {
|
describe("getEntry", function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
library.init({
|
library.init({});
|
||||||
log: mockLog,
|
|
||||||
storage: {
|
|
||||||
getLibraryEntry: function(type,path) {
|
|
||||||
return Promise.resolve({type,path});
|
|
||||||
},
|
|
||||||
getFlow: function(path) {
|
|
||||||
return Promise.resolve({path});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nodes: {
|
|
||||||
getNodeExampleFlowPath: function(module,entryPath) {
|
|
||||||
if (module === "unknown") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return "/tmp/"+module+"/"+entryPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sinon.stub(fs,"readFile", function(path,opts,callback) {
|
|
||||||
if (path === "/tmp/test-module/abc") {
|
|
||||||
callback(null,"Example flow result");
|
|
||||||
} else if (path === "/tmp/@scope/test-module/abc") {
|
|
||||||
callback(null,"Example scope flow result");
|
|
||||||
} else if (path === "/tmp/test-module/throw") {
|
|
||||||
throw new Error("Instant error")
|
|
||||||
} else {
|
|
||||||
callback(new Error("Unexpected path:"+path))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
after(function() {
|
|
||||||
fs.readFile.restore();
|
|
||||||
})
|
|
||||||
it('throws error for unregistered type', function() {
|
it('throws error for unregistered type', function() {
|
||||||
should(()=>{library.getEntry("unknown","/abc")} ).throw();
|
should(()=>{library.getEntry("local","unknown","/abc")} ).throw();
|
||||||
|
});
|
||||||
|
it('throws error for unknown library', function() {
|
||||||
|
should(()=>{library.getEntry("unknown","unknown","/abc")} ).throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a registered non-flow entry', function(done) {
|
it('returns a registered non-flow entry', function(done) {
|
||||||
library.register("test-module","test-type");
|
library.register("test-module","test-type");
|
||||||
library.getEntry("test-type","/abc").then(function(result) {
|
library.getEntry("local","test-type","/abc").then(function(result) {
|
||||||
|
result.should.have.property("library","local")
|
||||||
result.should.have.property("type","test-type")
|
result.should.have.property("type","test-type")
|
||||||
result.should.have.property("path","/abc")
|
result.should.have.property("path","/abc")
|
||||||
done();
|
done();
|
||||||
@ -91,76 +94,37 @@ describe("runtime/library", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it ('returns a flow entry', function(done) {
|
it ('returns a flow entry', function(done) {
|
||||||
library.getEntry("flows","/abc").then(function(result) {
|
library.getEntry("local","flows","/abc").then(function(result) {
|
||||||
|
result.should.have.property("library","local")
|
||||||
result.should.have.property("path","/abc")
|
result.should.have.property("path","/abc")
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it ('returns a flow example entry', function(done) {
|
it ('returns a flow example entry', function(done) {
|
||||||
library.getEntry("flows","_examples_/test-module/abc").then(function(result) {
|
library.getEntry("_examples_","flows","/test-module/abc").then(function(result) {
|
||||||
result.should.eql("Example flow result");
|
result.should.have.property("library","_examples_")
|
||||||
|
result.should.have.property("path","/test-module/abc")
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it ('returns a flow example entry from scoped module', function(done) {
|
|
||||||
library.getEntry("flows","_examples_/@scope/test-module/abc").then(function(result) {
|
|
||||||
result.should.eql("Example scope flow result");
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
it ('returns an error for unknown flow example entry', function(done) {
|
|
||||||
library.getEntry("flows","_examples_/unknown/abc").then(function(result) {
|
|
||||||
done(new Error("No error thrown"))
|
|
||||||
}).catch(function(err) {
|
|
||||||
err.should.have.property("code","not_found");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it ('returns an error for file load error - async', function(done) {
|
|
||||||
library.getEntry("flows","_examples_/test-module/unknown").then(function(result) {
|
|
||||||
done(new Error("No error thrown"))
|
|
||||||
}).catch(function(err) {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it ('returns an error for file load error - sync', function(done) {
|
|
||||||
library.getEntry("flows","_examples_/test-module/throw").then(function(result) {
|
|
||||||
done(new Error("No error thrown"))
|
|
||||||
}).catch(function(err) {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("saveEntry", function() {
|
describe("saveEntry", function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
library.init({
|
library.init({});
|
||||||
log: mockLog,
|
});
|
||||||
storage: {
|
it('throws error for unknown library', function() {
|
||||||
saveLibraryEntry: function(type, path, meta, body) {
|
should(()=>{library.saveEntry("unknown","unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
|
||||||
return Promise.resolve({type,path,meta,body})
|
|
||||||
},
|
|
||||||
saveFlow: function(path,body) {
|
|
||||||
return Promise.resolve({path,body});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nodes: {
|
|
||||||
getNodeExampleFlowPath: function(module,entryPath) {
|
|
||||||
if (module === "unknown") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return "/tmp/"+module+"/"+entryPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it('throws error for unregistered type', function() {
|
it('throws error for unregistered type', function() {
|
||||||
should(()=>{library.saveEntry("unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
|
should(()=>{library.saveEntry("local","unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
|
||||||
|
});
|
||||||
|
it('throws error for save to readonly library', function() {
|
||||||
|
should(()=>{library.saveEntry("_examples_","unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
|
||||||
});
|
});
|
||||||
it('saves a flow entry', function(done) {
|
it('saves a flow entry', function(done) {
|
||||||
library.saveEntry('flows','/abc',{id:"meta"},{id:"body"}).then(function(result) {
|
library.saveEntry('local','flows','/abc',{id:"meta"},{id:"body"}).then(function(result) {
|
||||||
result.should.have.property("path","/abc");
|
result.should.have.property("path","/abc");
|
||||||
result.should.have.property("body",{id:"body"});
|
result.should.have.property("body",{id:"body"});
|
||||||
done();
|
done();
|
||||||
@ -168,7 +132,7 @@ describe("runtime/library", function() {
|
|||||||
})
|
})
|
||||||
it('saves a non-flow entry', function(done) {
|
it('saves a non-flow entry', function(done) {
|
||||||
library.register("test-module","test-type");
|
library.register("test-module","test-type");
|
||||||
library.saveEntry('test-type','/abc',{id:"meta"},{id:"body"}).then(function(result) {
|
library.saveEntry('local','test-type','/abc',{id:"meta"},{id:"body"}).then(function(result) {
|
||||||
result.should.have.property("type","test-type");
|
result.should.have.property("type","test-type");
|
||||||
result.should.have.property("path","/abc");
|
result.should.have.property("path","/abc");
|
||||||
result.should.have.property("meta",{id:"meta"});
|
result.should.have.property("meta",{id:"meta"});
|
||||||
|
93
test/unit/@node-red/runtime/lib/library/local_spec.js
Normal file
93
test/unit/@node-red/runtime/lib/library/local_spec.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var should = require("should");
|
||||||
|
var sinon = require("sinon");
|
||||||
|
|
||||||
|
var NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
var localLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/local")
|
||||||
|
|
||||||
|
var mockLog = {
|
||||||
|
log: sinon.stub(),
|
||||||
|
debug: sinon.stub(),
|
||||||
|
trace: sinon.stub(),
|
||||||
|
warn: sinon.stub(),
|
||||||
|
info: sinon.stub(),
|
||||||
|
metric: sinon.stub(),
|
||||||
|
audit: sinon.stub(),
|
||||||
|
_: function() { return "abc"}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("runtime/library/local", function() {
|
||||||
|
|
||||||
|
describe("getEntry", function() {
|
||||||
|
before(function() {
|
||||||
|
localLibrary.init({
|
||||||
|
log: mockLog,
|
||||||
|
storage: {
|
||||||
|
getLibraryEntry: function(type,path) {
|
||||||
|
return Promise.resolve({type,path});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a registered non-flow entry', function(done) {
|
||||||
|
localLibrary.getEntry("test-type","/abc").then(function(result) {
|
||||||
|
result.should.have.property("type","test-type")
|
||||||
|
result.should.have.property("path","/abc")
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('returns a flow entry', function(done) {
|
||||||
|
localLibrary.getEntry("flows","/abc").then(function(result) {
|
||||||
|
result.should.have.property("path","/abc")
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveEntry", function() {
|
||||||
|
before(function() {
|
||||||
|
localLibrary.init({
|
||||||
|
log: mockLog,
|
||||||
|
storage: {
|
||||||
|
saveLibraryEntry: function(type, path, meta, body) {
|
||||||
|
return Promise.resolve({type,path,meta,body})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('saves a flow entry', function(done) {
|
||||||
|
localLibrary.saveEntry('flows','/abc',{id:"meta"},{id:"body"}).then(function(result) {
|
||||||
|
result.should.have.property("path","/abc");
|
||||||
|
result.should.have.property("body",{id:"body"});
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
})
|
||||||
|
it('saves a non-flow entry', function(done) {
|
||||||
|
localLibrary.saveEntry('test-type','/abc',{id:"meta"},{id:"body"}).then(function(result) {
|
||||||
|
result.should.have.property("type","test-type");
|
||||||
|
result.should.have.property("path","/abc");
|
||||||
|
result.should.have.property("meta",{id:"meta"});
|
||||||
|
result.should.have.property("body",{id:"body"});
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user