import path from "path"; import { shell } from "electron"; import { dialog, getCurrentWindow } from "@electron/remote"; import type { FileFilter } from "electron"; import React from "react"; import type { IDashboardComponentContext } from "eez-studio-types"; import { registerActionComponents } from "project-editor/flow/component"; import { SHOW_FILE_IN_FOLDER_ICON } from "project-editor/ui-components/icons"; //////////////////////////////////////////////////////////////////////////////// const readIcon: any = ( ); const writeIcon: any = ( ); const appendIcon: any = writeIcon; // const fileOpenDialogIcon: any = ( // // // // ); // const fileSaveDialogIcon: any = ( // // // // ); const componentHeaderColor = "#f1ffc4"; registerActionComponents("File", [ { name: "FileRead", icon: readIcon, componentHeaderColor, bodyPropertyName: "filePath", inputs: [], outputs: [ { name: "content", type: "any", isSequenceOutput: false, isOptionalOutput: false } ], properties: [ { name: "filePath", type: "expression", valueType: "string" }, { name: "encoding", type: "expression", valueType: "string", formText: `"ascii", "base64", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", "utf8", "utf-8", "binary" or "latin1"` } ], execute: (context: IDashboardComponentContext) => { const filePathValue = context.evalProperty("filePath"); if (typeof filePathValue != "string") { context.throwError("filePath is not a string"); return; } const encodingValue = context.evalProperty("encoding"); if (typeof encodingValue != "string") { context.throwError("encoding is not a string"); return; } const encodings = [ "ascii", "base64", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", "utf8", "utf-8", "binary", "latin1" ]; if (encodings.indexOf(encodingValue) == -1) { context.throwError( `Unsupported encoding value ${encodingValue}, supported: ${encodings.join( ", " )}` ); return; } context = context.startAsyncExecution(); (async function () { try { const fs = await import("fs"); let content = await fs.promises.readFile( filePathValue as string, encodingValue == "binary" ? undefined : (encodingValue as any) ); context.propagateValue("content", content); context.propagateValueThroughSeqout(); } catch (err) { context.throwError(err.toString()); } finally { context.endAsyncExecution(); } })(); } }, { name: "FileWrite", icon: writeIcon, componentHeaderColor, bodyPropertyName: "filePath", inputs: [], outputs: [], properties: [ { name: "filePath", type: "expression", valueType: "string" }, { name: "content", type: "expression", valueType: "string" }, { name: "encoding", type: "expression", valueType: "string", formText: `"ascii", "base64", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", "utf8", "utf-8", "utf8-bom", "binary" or "latin1"` } ], execute: (context: IDashboardComponentContext) => { const filePathValue = context.evalProperty("filePath"); if (typeof filePathValue != "string") { context.throwError("filePath is not a string"); return; } let encodingValue = context.evalProperty("encoding"); if (typeof encodingValue != "string") { context.throwError("${encoding} is not a string"); return; } let contentValue = context.evalProperty("content"); const encodings = [ "ascii", "base64", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", "utf8", "utf-8", "binary", "latin1" ]; if (encodingValue == "utf8-bom") { encodingValue = "utf8"; contentValue = "\ufeff" + contentValue; } if (encodings.indexOf(encodingValue) == -1) { context.throwError( `Unsupported encoding value ${encodingValue}, supported: ${encodings.join( ", " )}` ); return; } context = context.startAsyncExecution(); (async function () { try { const fs = await import("fs"); await fs.promises.writeFile( filePathValue, contentValue, encodingValue as any ); context.propagateValueThroughSeqout(); } catch (err) { context.throwError(err.toString()); } finally { context.endAsyncExecution(); } })(); return undefined; } }, { name: "FileAppend", icon: appendIcon, componentHeaderColor, bodyPropertyName: "filePath", inputs: [], outputs: [], properties: [ { name: "filePath", type: "expression", valueType: "string" }, { name: "content", type: "expression", valueType: "string" }, { name: "encoding", type: "expression", valueType: "string", formText: `"ascii", "base64", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", "utf8", "utf-8", "binary" or "latin1"` } ], execute: (context: IDashboardComponentContext) => { const filePathValue = context.evalProperty("filePath"); if (typeof filePathValue != "string") { context.throwError("filePath is not a string"); return; } const encodingValue = context.evalProperty("encoding"); if (typeof encodingValue != "string") { context.throwError("${encoding} is not a string"); return; } const encodings = [ "ascii", "base64", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", "utf8", "utf-8", "binary", "latin1" ]; if (encodings.indexOf(encodingValue) == -1) { context.throwError( `Unsupported encoding value ${encodingValue}, supported: ${encodings.join( ", " )}` ); return; } const contentValue = context.evalProperty("content"); context = context.startAsyncExecution(); (async function () { try { const fs = await import("fs"); await fs.promises.appendFile( filePathValue, contentValue, encodingValue as any ); context.propagateValueThroughSeqout(); } catch (err) { context.throwError(err.toString()); } finally { context.endAsyncExecution(); } })(); return undefined; } }, { name: "FileOpenDialog", icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAABHhJREFUWIXdlktslVUQx39zvvvobcsFSmmxPCoEKkYwRhrwhQ1oYqML2enCxJgY48aVS9GkbDQhhkSiwTfRjbjRBGPUGARFIwVFKSAp7yoWyO3jPtrS3u/MuPgAS/q6FywLJ5nFd87M/P8z38w5B/4v0nJo9YaWjjWfl+vn/isC4nhEhMfXdjQ/UZZfOcYPH75/UTyIPRri7zDVdGg6YKKnRHS/efeuiCwzswvehm7fe2dHXykxY6UYtXa2zo1buEXxT6pZICaYCM5ATcAC5HIqIlIfSMVm4NlSYk9ZgQ0nH2rCu28VW+hNUVO8edQUVY9iY3zMzMCv37Pyl91TxZ+0B1o7W9OOxJeIWzhVoNEiIoIEb7d811gxle2kv6A6Fjzv1W90ibB9eFCqAtE6xRZjrARba3AXE1RRkCY/t/4+OLvrugi0nG6pEJVPPlv6VddENg8cal4SiHtFRJ4evW5mhtnH3X/1/TgZOBOxL0daDjU/Iy74YBT4r4q+8MPKAz+V4l/SFEwm5oJ1EiFnVOSl71e0v4egpfpPSKBQKDwGmKq2p9PpzER2YrrK4E1vl17eu7K02b/Gf7zFrq6uVE1NTVZE4tFIcRrYZ2btqtqezWYPLlq0aAhgaefS5ImmE8Oj/a1zaTIsxB4Usbrx4nsA08PJu4//fg0BM5sJLM7lcvO9918457ii2cIQvblBatIpKpOJopl2iMg+73070L5169ZjbW1tGh5sesojr4vIuOBXscAQfU76PmycFU+kNgm2PLF+e1NQv6bxMhnCMMR7j/eenv4ClRVxujM5+nKDJOIOVWNGVQVz0pUk4kGuIr/9aDL3/hqRUpvbjsRckNimqtEFEqu8uiUixONxMtlB+vKDBCK4GMyfV8uyxqh1VJXBoWF6swNIf0e6sfjRPWDY2MNxIgJdMTVtFosIS6p+jEmmP8/s6jjg6DrfS8PcWfzc0c2Culn0ZAeYnU4hWmC5vobY8Bj/SeGNXU699nmveHNIqhaAgaKy7XCOY71RwONnurjQ20/d7GoaatPUpFMc6TxCKhlDiudZopuQYhfR+VO6hqq7Yqo+A4KrvAWR6Go4P6QcPXeAVclhan2BofAiM/xsZlKN5VNUFrtpqOimQfdQZ1/jNF9W5gAGvVU7T/4WU68ZQ3Cpf5t2Qf4NNte9BYXou6EKCIGeSG9zwJwoStTO5YuZ7ZY2NOa99iAgqXnRxsg5uPgOZXTSdYnALoBYqGFGcEhlRCC8uB2zkWkFBwjFIgIWkjGnuKp5mM/jMzuwac4eozu9+uwfEQEJM1hAUN1AmNmBhbnpBY8YXH0jxAKVjHcQVNXjL7yKXVdL3QCBEdWewDkIO9DhP28COARaHFUBCTK4GJbbeVOyF7NTqXsvnLny7QrZTE+QwnxhfzR606w2qvwAbkUbI8k6zZd7jF6vjoxcSyAGEK+RfrD0dJe/GNqloWH3zRgC/QXZkoiz0YnMmS5wM8uPFOXFW1v/7hm9fu2L6FOC3Udu/KU8nqxrwzPOtfEPmju7fm01yQcAAAAASUVORK5CYII=", componentHeaderColor, properties: [ { name: "filters", type: "expression", valueType: "array:string", optional: () => true } ], inputs: [], outputs: [ { name: "file_path", type: "string", isSequenceOutput: false, isOptionalOutput: false } ], execute: (context: IDashboardComponentContext) => { context = context.startAsyncExecution(); (async () => { const filtersValue = context.evalProperty("filters"); let filters: FileFilter[]; if (filtersValue != undefined) { if (!Array.isArray(filtersValue)) { context.endAsyncExecution(); context.throwError("filters is not an array"); return; } if (filtersValue.length == 0) { context.endAsyncExecution(); context.throwError("filters empty"); return; } filters = []; for (let i = 0; i < filtersValue.length; i++) { if (typeof filtersValue[i] != "string") { context.endAsyncExecution(); context.throwError( "filters is not an array of strings" ); return; } const parts = filtersValue[i].split("|"); if (parts.length < 2) { context.endAsyncExecution(); context.throwError( `invalid filter at position ${i + 1}` ); return; } filters.push({ name: parts[0], extensions: parts.slice(1) }); } console.log(filters); } else { filters = [{ name: "All Files", extensions: ["*"] }]; } const result = await dialog.showOpenDialog(getCurrentWindow(), { properties: ["openFile"], filters }); // workaround: for some reason electron returs "\\" as path separator on windows, probably bug const filePath = result.filePaths && result.filePaths[0] ? result.filePaths[0].replace(/\\/g, "/") : undefined; if (!result.canceled) { context.propagateValue("file_path", filePath); } context.propagateValueThroughSeqout(); context.endAsyncExecution(); })(); } }, { name: "FileSaveDialog", icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAABRBJREFUWIWtll+I3NUVxz/nzszOJlnzZ3A3m40ma8x/rIipAVEqFFIoaUQMixoKhbagD74U6oP0wYdCqaIPakvtQwgtISlLC7Wk+tSHQmmLwcQU6ibZ9U80upttdnc25s/M/XNOH34zszO76+7M6oELv/ndM/d8ft9z7rlXqNmRP17YVnTyi4nJzzduv72H1w7vY6X2wtv/4Z8fXWWgd2NZyf1lZObG8Xef/mZYzDdffxCz4/mce9A0EdOc76E3/sZ1HwE4fN9Wnv3W7mUBUkrEGOjuctwM8tieUs+ze/409t3jh7dPzvd19QfVtEc1EYInhjkAHwLee7z3pBjbUiDGmPknRTWhmu5PsXrqwO/fW7MEgKKm+OgJTYFC8PhQxYcqKaU2AQI+VGvBFVXFVB8oFfInh4aHc4sCWOZE8J4YmxXwBJ+NmNpUIEWC96SUUNPGMEuHJOz41ZcokNGGGFoAYvD42kjangIpRnzwja9vGcYzQ8fPHKn7NopQrSaVGWbWWOyJ/buohCzwA4Mblw0+dv0UA4Nnebhnlv7eSZJJY67qK1z67ABRCz8DTrQC1AgBRq+UGZsss71vPT/5Tmfb8V+zr9O/tUr/1oVzTopMzw4wM3vv3oNH39n21x/t/7ABkFVsBnArRB57/c+sKXZ1FBzgqcMXyeUW3fKZWUBVyZncAXzYpEDCR9jcV2Ls0wnU4IuK7xhADcSWmrfsQwWBeSmoeKWv1Mem3r6WOujEBIc15X0BgNZ6Q239piJMiAkT09dXFLhuOxUcywEoiMwD0FRX5SuZmiBLKWBWU4BWAFPFvg4AlSU/RLVeA64VINsBXxOALA+gposALPHHTgCWWmcu+DwFkuqS5O1aUkdTh18EwFqaXosCIrC3fwPf+8ad5N3KYE7fdCRdLgUJyKqw4fngS2/OiLD+5A+/zZZST1vBonpeOvMQlXRtRbCYjbduQxHWdGWvpsvXmZ5dvifsyH2f96pvrDC+/XReCuakq3hP+dryAHcVDjK1+hwfffHvjoIL/OHFR86faDmOxYR6Ax7oKzHQV2prsZ3VX/LK6ce5Ecpt+Vf9WkZGH/0tPD+vCIF6cYxdmuCDTybaWhBg860jjLjlU2EII6OHmJq+22BBJ5yzgb4NrF+7um2AfWzjrU/HOT1+akm/icl9TE0PAtmR3XIfaG6E3cUCXV15OrHHdz3Hhatnma6MLzrf3zNIvutJLugVTBccRjWAmgzvj13mv6OfdAQAcFfuKf6XXsNqrbZuOZfnB/f+nF9fmUJVSSw4jiM0FeE9O7dwz84tHQPAw5RGyrw1+ruWtwd3/JjB9Xsx/XvWiOZ3QrOshV87+xsK3a30ndojGjmrOcZddpm9Q/M8NDNF+Z2X8VO3AcWG71yShY+B+94+d5E1rvOr2Hy7ze3m8qbzYELPxC7e/Ow8AJdndmPWm8TZxwAyPDzclVK68x9X/IFzs+mFaLIWxAnmJLveZf3JTBpV+uUHvmU5NBCx20sXMXN2dXq7GaJgCuY3d8uJRze5V8vl8iU5evRob6FQeEJE9ovIALDBzHqAooh0mVkeyIlI/ZgTq136pNY6rekCKSJa+60iEoFoZgGoADeAMvA5cAY4KYAcO3asGGNcvW7dulUhhNUisirGuMrMup1zdZCCcy6vqnkzcyIiZpZ3zmFm0TlnMUbL5/NBVaOIBDPzqloVkQpQMbNbzrmbxWLxJnBraGio8n+BRf2XIiOGEwAAAABJRU5ErkJggg==", componentHeaderColor, inputs: [], outputs: [ { name: "file_path", type: "string", isSequenceOutput: false, isOptionalOutput: false } ], properties: [ { name: "fileName", type: "expression", valueType: "string" }, { name: "filters", type: "expression", valueType: "array:string", optional: () => true } ], execute: (context: IDashboardComponentContext) => { const fileNameValue = context.evalProperty("fileName"); if (fileNameValue && typeof fileNameValue != "string") { context.throwError("fileName is not a string"); return; } context = context.startAsyncExecution(); (async () => { const filtersValue = context.evalProperty("filters"); let filters: FileFilter[] | undefined; if (filtersValue != undefined) { if (!Array.isArray(filtersValue)) { context.endAsyncExecution(); context.throwError("filters is not an array"); return; } if (filtersValue.length == 0) { context.endAsyncExecution(); context.throwError("filters empty"); return; } filters = []; for (let i = 0; i < filtersValue.length; i++) { if (typeof filtersValue[i] != "string") { context.endAsyncExecution(); context.throwError( "filters is not an array of strings" ); return; } const parts = filtersValue[i].split("|"); if (parts.length < 2) { context.endAsyncExecution(); context.throwError( `invalid filter at position ${i + 1}` ); return; } filters.push({ name: parts[0], extensions: parts.slice(1) }); } console.log(filters); } else { filters = undefined; } const result = await dialog.showSaveDialog(getCurrentWindow(), { defaultPath: fileNameValue, filters }); // workaround: for some reason electron returs "\\" as path separator on windows, probably bug const filePath = result.filePath ? result.filePath.replace(/\\/g, "/") : undefined; if (!result.canceled) { context.propagateValue("file_path", filePath); } context.propagateValueThroughSeqout(); context.endAsyncExecution(); })(); } }, { name: "ShowFileInFolder", icon: SHOW_FILE_IN_FOLDER_ICON, componentHeaderColor, inputs: [], outputs: [], properties: [ { name: "filePath", type: "expression", valueType: "string" } ], execute: (context: IDashboardComponentContext) => { const filePathValue = context.evalProperty("filePath"); if (typeof filePathValue != "string") { context.throwError("filePathValue is not a string"); return; } // workaround: on windows, showItemInFolder will not work if path.sep is / const filePath = filePathValue.replace(/\//g, path.sep); shell.showItemInFolder(filePath); context.propagateValueThroughSeqout(); } } ]);