From e91a9847e34264f00074b435c30e228885eef12c Mon Sep 17 00:00:00 2001 From: Venryx Date: Tue, 1 Dec 2020 17:32:58 -0800 Subject: [PATCH] * Fixed up React_CreateElement hook, for BaseTable, to not add duplicate columns. * Tried to implement Import button, using react-vmessagebox, but hit issue of esmodules not being supported in electron. For esmodules point, I read this thread: https://github.com/electron/electron/issues/21457 Comments at end (eg. https://github.com/electron/electron/issues/21457#issuecomment-730563454) seemed to indicate you could use dynamic-import calls; however, from my own tests (in lf-desktop), I found that even with latest electron (v12 beta-5), the dynamic-imports only work for the initial file that electron is pointed to. Dynamic-imports from console, event-handlers, etc. always have a "Failed to fetch dynamically imported module" error. There was also the old solution of using the "esm" module, but apparently that approach stopped working in Electron 10+ (https://github.com/electron/electron/issues/21457#issuecomment-683128696). Comment suggests the "esm" module/transpiler works again, if you remove the "type:module" field (presumably using .mjs extensions instead), but I don't want to do that. Thus it seems that, for my code's pathway (as a nuclear plugin, called through eval), there is no way to use esmodules right now. ---------- By the way, the above is all based around the idea that importing esmodules from commonjs modules, requires the async-function wrapper. This is because esmodules are allowed to contain "top-level awaits". Despite the great majority of esmodules not using this, this feature makes it impossible to reliably have commonjs "require" (synchronously) an esmodule file. See these for background: Overview) https://redfin.engineering/node-modules-at-war-why-commonjs-and-es-modules-cant-get-along-9617135eeca1 Discussion) https://github.com/nodejs/modules/issues/454 My opinion aligns with the overview article's author, that NodeJS (and by extension Electron) should allow commonjs to just "require" esmodule files -- but that if that esmodule tree contains a "top level await", then an error is generated. However, as of now, that idea doesn't seem to have picked up much momentum, so it's unlikely it would get implemented any time soon. Thus, we have to fall back to the only existing cjs->mjs approach, of an async wrapper. Which electron (even latest versions) only supports for the module-tree pointed to at electron launch... Thus, for now (and the foreseeable future), esmodules will not be possible within this nuclear-vplugin package... My options are thus: 1) Avoid use of esmodules, by extracting needed code blocks from those libraries, or using different libraries. (what I'm currently during, through FromJSVE, and using something like electron-prompt instead of react-vmessagebox) 2) Use a bundler like Webpack to convert esmodules into a simple bundle (or a set of commonjs files). (I think the "esm" transpiler thing fits into this category, just doing so seamlessly/magically -- however, the magic appears to not work so well in the latest electron versions, as mentioned above) 3) Avoid use of esmodules, by updating your libraries to not output esmodules -- or to output both esmodules and commonjs versions. This could work, but I don't really like it, as it's forcing me to spend maintenance on the commonjs format, which is "the past", merely for this nuclear plugin pathway. (since my libraries are not used used by others atm) Of the three options, option 2 is probably the one I'll end up going with, as it lets me keep the nuclear-vplugin pathway's fix restricted to the nuclear-vplugin repo itself. Also, it may come in handy for making the install process easier. (ie. users may not need to do "npm install" anymore, as long as I use only pure-javascript libraries) But anyway, atm I'm doing the lazy approach (option 1): just avoiding those esmodule libraries for now. --- Dist/Hooks/React_CreateElement.js | 12 ++- Dist/ImportHelper.js | 8 ++ Dist/Start.js | 1 + Dist/UI/Root.js | 61 +++++++++++++++- Dist/index.js | 1 + Source/Hooks/Object_scanLocalFoldersFailed.ts | 2 +- Source/Hooks/React_CreateElement.ts | 15 +++- Source/ImportHelper.js | 7 ++ Source/Start.ts | 1 + Source/UI/Root.tsx | 73 ++++++++++++++++++- Source/Utils/General/General.ts | 22 +++--- Source/index.ts | 2 + package-lock.json | 46 ++++++++++++ package.json | 4 +- 14 files changed, 232 insertions(+), 23 deletions(-) create mode 100644 Dist/ImportHelper.js create mode 100644 Source/ImportHelper.js diff --git a/Dist/Hooks/React_CreateElement.js b/Dist/Hooks/React_CreateElement.js index eba0f9a..c324510 100644 --- a/Dist/Hooks/React_CreateElement.js +++ b/Dist/Hooks/React_CreateElement.js @@ -3,6 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); const Store_1 = require("../Store"); const DistributionColumn_1 = require("../UI/DistributionColumn"); //const require_app = window.require; +// class-checks, for use in React.createElement hook (can't use class-name, since minified in prod builds) +// ========== +function ArgsMatchForClass_BaseTable(args) { + return args[1] && args[1].columns && args[1].data && args[1].rowHeight; +} +// hook +// ========== let appReact; let oldCreateElement; //export function AddHook_React_CreateElement(require_app) { @@ -11,7 +18,8 @@ function AddHook_React_CreateElement(appReact_new) { appReact = appReact_new; oldCreateElement = appReact.createElement; appReact.createElement = function (...args) { - if (args[0].name == "BaseTable") { + //if (args[0].name == "BaseTable") { + if (ArgsMatchForClass_BaseTable(args)) { const props = args[1]; let { columns, data, rowHeight } = props; if (columns.orig == null) @@ -44,7 +52,7 @@ function AddHook_React_CreateElement(appReact_new) { } } // apply own column - const oldColumnEntryIndex = columns.findIndex(a => a.name == DistributionColumn_1.distributionColumn.title); + const oldColumnEntryIndex = columns.findIndex(a => a.title == DistributionColumn_1.distributionColumn.title); if (oldColumnEntryIndex != -1) { columns.splice(oldColumnEntryIndex, 1); } diff --git a/Dist/ImportHelper.js b/Dist/ImportHelper.js new file mode 100644 index 0000000..6ed093d --- /dev/null +++ b/Dist/ImportHelper.js @@ -0,0 +1,8 @@ +//export const react_vMessageBox = await import("react-vmessagebox"); + +module.exports.react_vMessageBox = {}; + +(async()=>{ + Object.assign(module.exports.react_vMessageBox, await import("react-vmessagebox")); + //Object.assign(module.exports.react_vMessageBox, await import("./Proxy1.mjs")); +})(); \ No newline at end of file diff --git a/Dist/Start.js b/Dist/Start.js index 0f224d5..a1a42c0 100644 --- a/Dist/Start.js +++ b/Dist/Start.js @@ -18,6 +18,7 @@ function Start(api, rootPath, require_app) { } // set up unloader function for the current launch window["vplugin_unloadLastLaunch"] = Unload; + console.log("VPlugin starting. @api:", api, "@rootPath", rootPath, "@require_app", require_app); exports.nuclearAPI = api; //AddHook_Store_ReactReduxGrid(api); //AddHook_React_CreateElement(require_app); diff --git a/Dist/UI/Root.js b/Dist/UI/Root.js index 131a764..9b139df 100644 --- a/Dist/UI/Root.js +++ b/Dist/UI/Root.js @@ -28,6 +28,21 @@ const mobx_react_1 = require("mobx-react"); const mobx_sync_1 = require("mobx-sync"); const FromVWAF_1 = require("../Utils/General/FromVWAF"); const Playlists_1 = require("../Utils/Managers/Playlists"); +//import {ShowMessageBox} from "react-vmessagebox"; +/*let {ShowMessageBox}: typeof import("react-vmessagebox") = {} as any; +(async()=>{ + ({ShowMessageBox} = await import("react-vmessagebox")); + //({ShowMessageBox} = await eval(`import("react-vmessagebox")`)); + /*const importFunc = eval("import"); + ({ShowMessageBox} = await importFunc("react-vmessagebox"));*#/ +})();*/ +/*import esmModules from "../ImportHelper"; +const react_vMessageBox = esmModules.react_vMessageBox as typeof import("react-vmessagebox");*/ +//import {react_vMessageBox} from "../ImportHelper"; +/*const {ShowMessageBox} = react_vMessageBox; +console.log("ShowMessageBox:", ShowMessageBox);*/ +/*const require_esm = require("esm")(module); +let {react_vMessageBox} = require_esm("../ImportHelper");*/ let RootUIWrapper = class RootUIWrapper extends react_vextensions_1.BaseComponentPlus({}, {}) { constructor() { super(...arguments); @@ -97,9 +112,49 @@ let RootUI = class RootUI extends react_vextensions_1.BaseComponentPlus({}, {}) const dateStr = date_local.toISOString().slice(0, "2020-01-01T01:01:01".length).replace("T", " ").replace(/:/g, "-"); FromJSVE_1.StartDownload(JSON.stringify(Store_1.store), `NuclearVPluginStoreBackup_${dateStr}.json`); } }), - react_1.default.createElement(react_vcomponents_1.Button, { ml: 5, text: "Import", enabled: false, onClick: () => { - // todo - } }))))))); + react_1.default.createElement(react_vcomponents_1.Button, { ml: 5, text: "Import", onClick: () => __awaiter(this, void 0, void 0, function* () { + /*const json = prompt("Paste JSON from export/backup file below", ""); + if (json == null) return; + ImportConfig(JSON.parse(json));*/ + let json = ""; + const Change = (..._) => boxController.UpdateUI(); + /*import("fs"); + //import("react-vmessagebox"); + debugger; + require("../ImportHelper"); + return;*/ + //let react_vMessageBox = await import("react-vmessagebox"); + //let {react_vMessageBox} = require("../ImportHelper"); + /*const require_esm = require("esm")(module); + let {react_vMessageBox} = require_esm("react-vmessagebox");*/ + /*const require_esm = require("esm")(module); + let react_vMessageBox = require_esm("../ImportHelper").react_vMessageBox as typeof import("react-vmessagebox");*/ + debugger; + let react_vMessageBox = require("../ImportHelper").react_vMessageBox; + setTimeout(() => { + console.log("ShowMessageBox", react_vMessageBox.ShowMessageBox); + }, 1000); + return; + let boxController = react_vMessageBox.ShowMessageBox({ + title: "Import config JSON", cancelButton: true, + message: () => { + //boxController.options.okButtonProps = {enabled: error == null}; + return (react_1.default.createElement(react_vcomponents_1.Column, { style: { padding: "10px 0", width: 600 } }, + react_1.default.createElement(react_vcomponents_1.Row, null, + react_1.default.createElement(react_vcomponents_1.Text, null, "JSON:"), + react_1.default.createElement(react_vcomponents_1.TextArea, { ml: 5, style: { flex: 1 }, value: json, onChange: val => Change(json = val) })))); + }, + onOK: () => { + ImportConfig(JSON.parse(json)); + }, + }); + function ImportConfig(data) { + console.log("Importing config. @old:", JSON.parse(JSON.stringify(Store_1.store)), "@new:", data); + for (const [key, value] of Object.entries(data)) { + Store_1.store[key] = value; + } + } + }) }))))))); } }; RootUI = __decorate([ diff --git a/Dist/index.js b/Dist/index.js index 5d2fd44..9d96864 100644 --- a/Dist/index.js +++ b/Dist/index.js @@ -71,6 +71,7 @@ module.exports = { Start(rootPath, require_app);*/ const distPath = `${rootPath}/Dist`; const distProxiesPath = `${rootPath}/Dist_Proxies`; + // todo: try using this cache-clearing call instead, rather than using the proxy-folder approach: delete require.cache[require.resolve(moduleName)] // if plugin just (re)installed, load the plugin through a proxy/sym-link, so that the "require" function doesn't use cached versions of the plugin scripts if (justInstalled) { const proxyPath = `${distProxiesPath}/Proxy_${Date.now()}`; diff --git a/Source/Hooks/Object_scanLocalFoldersFailed.ts b/Source/Hooks/Object_scanLocalFoldersFailed.ts index ed31d4a..65a4c04 100644 --- a/Source/Hooks/Object_scanLocalFoldersFailed.ts +++ b/Source/Hooks/Object_scanLocalFoldersFailed.ts @@ -8,7 +8,7 @@ export function AddHook_Object_scanLocalFoldersFailed() { console.log("scanLocalFoldersFailed @this:", this, "@arg1", arg1); debugger; }; - }, + }, set(val: any) { Object.defineProperty(this, "scanLocalFoldersFailed", {value: val, writable: true, configurable: true}); }, diff --git a/Source/Hooks/React_CreateElement.ts b/Source/Hooks/React_CreateElement.ts index 4e553cd..f0b1eee 100644 --- a/Source/Hooks/React_CreateElement.ts +++ b/Source/Hooks/React_CreateElement.ts @@ -3,6 +3,16 @@ import {distributionColumn} from "../UI/DistributionColumn"; //const require_app = window.require; +// class-checks, for use in React.createElement hook (can't use class-name, since minified in prod builds) +// ========== + +function ArgsMatchForClass_BaseTable(args: any[]) { + return args[1] && args[1].columns && args[1].data && args[1].rowHeight; +} + +// hook +// ========== + let appReact; let oldCreateElement; //export function AddHook_React_CreateElement(require_app) { @@ -12,7 +22,8 @@ export function AddHook_React_CreateElement(appReact_new) { oldCreateElement = appReact.createElement; appReact.createElement = function (...args) { - if (args[0].name == "BaseTable") { + //if (args[0].name == "BaseTable") { + if (ArgsMatchForClass_BaseTable(args)) { const props = args[1]; let {columns, data, rowHeight} = props; if (columns.orig == null) columns.orig = columns.slice(); @@ -46,7 +57,7 @@ export function AddHook_React_CreateElement(appReact_new) { } // apply own column - const oldColumnEntryIndex = columns.findIndex(a=>a.name == distributionColumn.title); + const oldColumnEntryIndex = columns.findIndex(a=>a.title == distributionColumn.title); if (oldColumnEntryIndex != -1) { columns.splice(oldColumnEntryIndex, 1); } diff --git a/Source/ImportHelper.js b/Source/ImportHelper.js new file mode 100644 index 0000000..3830df0 --- /dev/null +++ b/Source/ImportHelper.js @@ -0,0 +1,7 @@ +/** @type {typeof import("react-vmessagebox")} */ +/*export let {ShowMessageBox} = {}; +(async()=>{ + ({ShowMessageBox} = await import("react-vmessagebox")); +})();*/ + +// intentionally empty; actual code is in Dist/ImportHelper.js \ No newline at end of file diff --git a/Source/Start.ts b/Source/Start.ts index 1945a32..d33ce50 100644 --- a/Source/Start.ts +++ b/Source/Start.ts @@ -21,6 +21,7 @@ export function Start(api, rootPath: string, require_app: (path: string)=>any) { // set up unloader function for the current launch window["vplugin_unloadLastLaunch"] = Unload; + console.log("VPlugin starting. @api:", api, "@rootPath", rootPath, "@require_app", require_app); nuclearAPI = api; //AddHook_Store_ReactReduxGrid(api); //AddHook_React_CreateElement(require_app); diff --git a/Source/UI/Root.tsx b/Source/UI/Root.tsx index a0dfa9b..cd25819 100644 --- a/Source/UI/Root.tsx +++ b/Source/UI/Root.tsx @@ -1,6 +1,6 @@ import React from "react"; import {BaseComponentPlus} from "react-vextensions"; -import {Row, Button, DropDown, DropDownTrigger, DropDownContent, Text, Spinner, CheckBox} from "react-vcomponents"; +import {Row, Button, DropDown, DropDownTrigger, DropDownContent, Text, Spinner, CheckBox, Column, TextInput, TextArea} from "react-vcomponents"; import {store} from "../Store"; import {StartDownload} from "../Utils/General/FromJSVE"; import {observable, runInAction} from "mobx"; @@ -9,6 +9,23 @@ import {AsyncTrunk} from "mobx-sync"; import {Observer} from "../Utils/General/FromVWAF"; import {ClearPlaylist, GeneratePlaylist} from "../Utils/Managers/Playlists"; +//import {ShowMessageBox} from "react-vmessagebox"; +/*let {ShowMessageBox}: typeof import("react-vmessagebox") = {} as any; +(async()=>{ + ({ShowMessageBox} = await import("react-vmessagebox")); + //({ShowMessageBox} = await eval(`import("react-vmessagebox")`)); + /*const importFunc = eval("import"); + ({ShowMessageBox} = await importFunc("react-vmessagebox"));*#/ +})();*/ +/*import esmModules from "../ImportHelper"; +const react_vMessageBox = esmModules.react_vMessageBox as typeof import("react-vmessagebox");*/ +//import {react_vMessageBox} from "../ImportHelper"; +/*const {ShowMessageBox} = react_vMessageBox; +console.log("ShowMessageBox:", ShowMessageBox);*/ + +/*const require_esm = require("esm")(module); +let {react_vMessageBox} = require_esm("../ImportHelper");*/ + @observer export class RootUIWrapper extends BaseComponentPlus({}, {}) { async ComponentWillMount() { @@ -80,8 +97,58 @@ export class RootUI extends BaseComponentPlus({}, {}) { const dateStr = date_local.toISOString().slice(0, "2020-01-01T01:01:01".length).replace("T", " ").replace(/:/g, "-"); StartDownload(JSON.stringify(store), `NuclearVPluginStoreBackup_${dateStr}.json`); }}/> -