Skip to content

Commit

Permalink
* Fixed up React_CreateElement hook, for BaseTable, to not add duplic…
Browse files Browse the repository at this point in the history
…ate 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: electron/electron#21457

Comments at end (eg. electron/electron#21457 (comment)) 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+ (electron/electron#21457 (comment)). 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) nodejs/modules#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.
  • Loading branch information
Venryx committed Dec 2, 2020
1 parent d2b5a30 commit e91a984
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 23 deletions.
12 changes: 10 additions & 2 deletions Dist/Hooks/React_CreateElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down
8 changes: 8 additions & 0 deletions Dist/ImportHelper.js
Original file line number Diff line number Diff line change
@@ -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"));
})();
1 change: 1 addition & 0 deletions Dist/Start.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
61 changes: 58 additions & 3 deletions Dist/UI/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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([
Expand Down
1 change: 1 addition & 0 deletions Dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()}`;
Expand Down
2 changes: 1 addition & 1 deletion Source/Hooks/Object_scanLocalFoldersFailed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
},
Expand Down
15 changes: 13 additions & 2 deletions Source/Hooks/React_CreateElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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);
}
Expand Down
7 changes: 7 additions & 0 deletions Source/ImportHelper.js
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions Source/Start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
73 changes: 70 additions & 3 deletions Source/UI/Root.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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() {
Expand Down Expand Up @@ -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`);
}}/>
<Button ml={5} text="Import" enabled={false} onClick={()=> {
// todo
<Button ml={5} text="Import" onClick={async()=> {
/*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 as typeof import("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 (
<Column style={{padding: "10px 0", width: 600}}>
<Row>
<Text>JSON:</Text>
<TextArea ml={5} style={{flex: 1}} value={json} onChange={val=>Change(json = val)}/>
</Row>
</Column>
);
},
onOK: ()=>{
ImportConfig(JSON.parse(json))
},
});

function ImportConfig(data) {
console.log("Importing config. @old:", JSON.parse(JSON.stringify(store)), "@new:", data);
for (const [key, value] of Object.entries(data)) {
store[key] = value;
}
}
}}/>
</Row>
</DropDownContent>
Expand Down
22 changes: 11 additions & 11 deletions Source/Utils/General/General.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
export function ObserveStore(store, onChange: (oldState, newState)=>void): ()=>void;
export function ObserveStore(store, pathSelector: (store)=>any, onChange: (oldState, newState)=>void): ()=>void;
export function ObserveStore(...args): ()=>void {
let store, pathSelector: (store)=>any = rootState=>rootState, onChange: (oldState, newState)=>void;
export function ObserveStore(store, onChange: (oldState, newState) => void): () => void;
export function ObserveStore(store, pathSelector: (store) => any, onChange: (oldState, newState) => void): () => void;
export function ObserveStore(...args): () => void {
let store, pathSelector: (store) => any = rootState => rootState, onChange: (oldState, newState) => void;
if (args.length == 2) [store, onChange] = args;
else if (args.length == 3) [store, pathSelector, onChange] = args;

let currentState;

function HandleChange() {
let nextState = pathSelector(store.getState());
if (nextState !== currentState) {
onChange(currentState, nextState);
currentState = nextState;
let nextState = pathSelector(store.getState());
if (nextState !== currentState) {
onChange(currentState, nextState);
currentState = nextState;
}
}

let unsubscribe = store.subscribe(HandleChange);
HandleChange();
return unsubscribe;
Expand All @@ -24,7 +24,7 @@ export function AddPropToImmutableJSMap(obj, propName: string, propValue: any) {
// if BitmapIndexedNode
if (obj._root.nodes) {
const newProp = {entry: [propName, propValue], keyHash: Math.random(), ownerID: undefined};
Object.setPrototypeOf(newProp, Object.getPrototypeOf(obj._root.nodes.find(b=>b.constructor.name == "ValueNode")));
Object.setPrototypeOf(newProp, Object.getPrototypeOf(obj._root.nodes.find(b => b.constructor.name == "ValueNode")));
obj._root.nodes.push(newProp);
} else {
// if normal node
Expand Down
2 changes: 2 additions & 0 deletions Source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export = {
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()}`;
Expand Down
Loading

0 comments on commit e91a984

Please sign in to comment.