Skip to content

Commit

Permalink
Replace JSX parser component by babel standalone
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianromero committed Sep 18, 2023
1 parent 3d0bd3f commit b10a901
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 109 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"homepage": "https://adrianromero.github.io/myhelloiot/",
"dependencies": {
"@babel/standalone": "^7.22.19",
"@fortawesome/fontawesome-svg-core": "^1.3.0",
"@fortawesome/free-regular-svg-icons": "^6.0.0",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
Expand All @@ -21,8 +22,6 @@
"mqtt-match": "^2.0.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-jsx-parser": "^1.28.3",
"react-redux": "^7.2.4",
"react-scripts": "4.0.3",
"typescript": "^4.1.2",
Expand Down Expand Up @@ -77,5 +76,7 @@
"last 1 safari version"
]
},
"devDependencies": {}
"devDependencies": {
"@types/babel__standalone": "^7.1.4"
}
}
164 changes: 110 additions & 54 deletions src/AppDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
MYHELLOIOT
Copyright (C) 2021-2022 Adrián Romero
Copyright (C) 2021-2023 Adrián Romero
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
Expand All @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { ComponentType, ExoticComponent } from "react";
import React, { createElement, Fragment } from "react";
import { Card } from "antd";
import {
faUpload,
Expand All @@ -38,10 +38,8 @@ import {
faSpinner,
} from "@fortawesome/free-solid-svg-icons";

import { ErrorBoundary } from "react-error-boundary";
import { ReactComponent as Themes } from "./assets/svg/themes.svg";
import JsxParser from "react-jsx-parser";

import * as Babel from '@babel/standalone';
import { PanelFlex } from "./dashboard/PanelFlex";
import Dashboard from "./dashboard/Dashboard";
import DashboardContent from "./dashboard/DashboardContent";
Expand Down Expand Up @@ -112,11 +110,39 @@ import { ChartIconFormat } from "./format/ChartFormat";
import { ImageIconFormat } from "./format/ImageFormat";
import AppError from "./AppError";

const ErrorFallback: React.FC<{ error: Error }> = ({ error }) => (
<AppError title="Failed to execute JSX code" error={error.message} />
);
const JSXCONTEXT = {
//React JSX
createElement,
Fragment,

//NodeObject
Buffer,

const bindings = {
// HelloIOT Components
Dashboard,
DashboardContent,
PanelFlex,
InputUnit,
Publisher,
ButtonMessage,
ButtonUnit,
ButtonTopic,
SwitchUnit,
ViewUnit,
SliderUnit,
SoundUnit,
LogUnit,
LogTool,
NotifyUnit,
KeypadUnit,
DisconnectUnit,
SoundAlarmUnit,
ModalUnit,

// Antd Components
Card,

// Icons
faUpload,
faDownload,
faCodeBranch,
Expand All @@ -135,22 +161,23 @@ const bindings = {
faCircleExclamation,
faPowerOff,
faSpinner,

Themes,
Buffer,

// Format types
ToIconFormat,
ToIconValueFormat,
onoffnum,
onoffst,

// String Formats
StringValueFormat,
JSONValueFormat,
HEXValueFormat,
Base64ValueFormat,
SwitchValueFormat,
NumberValueFormat,

// Icon formats
DimIconFormat,
SwitchIconFormat,
BulbIconFormat,
Expand All @@ -162,13 +189,15 @@ const bindings = {
MapJSONBuffer,
MapJSONIconFormat,

// Icon value formats
SwitchIconValueFormat,
BulbIconValueFormat,
ThuderboltIconValueFormat,
StarIconValueFormat,
StringIconValueFormat,
NumberIconValueFormat,

// Gauge Formats
DashboardIconFormat,
LinearIconFormat,
SimpleIconFormat,
Expand All @@ -180,31 +209,78 @@ const bindings = {
DialIconFormat,
FuelIconFormat,
ControlIconFormat,

// Chart Format
ChartIconFormat,

// Image Format
ImageIconFormat,
};

const components: Record<string, ComponentType<{}> | ExoticComponent<{}>> = {
Card,
Dashboard,
DashboardContent,
PanelFlex,
InputUnit,
Publisher,
ButtonMessage,
ButtonUnit,
ButtonTopic,
SwitchUnit,
ViewUnit,
SliderUnit,
SoundUnit,
LogUnit,
LogTool,
NotifyUnit,
KeypadUnit,
DisconnectUnit,
SoundAlarmUnit,
ModalUnit,
const JSXCONTEXTKEYS: string[] = [];
const JSXCONTEXTVALUES: any[] = [];
for (const [key, value] of Object.entries(JSXCONTEXT)) {
JSXCONTEXTKEYS.push(key);
JSXCONTEXTVALUES.push(value);
}

const getErrorMessage = (error: unknown, msg: string = "Unknown error") => {
if (error instanceof Error) {
return error.message;
}
return msg;
}

const JSXRender: React.FC<{ jsx: string }> = ({ jsx }) => {

let output;
try {
output = Babel.transform(jsx, {
presets: [[
"react",
{
pragma: "createElement",
pragmaFrag: "Fragment"
},
]]
}).code;
} catch (error) {
return <AppError
title="Failed to compile JSX code"
error={getErrorMessage(error, "Unknown compilation error")}
jsx={jsx}
/>;
}

if (!output) {
return <AppError
title="Failed to execute JSX code"
error="JSX compiled code is empty"
jsx={jsx}
/>;
}

let fn;
try {
// eslint-disable-next-line no-new-func
fn = new Function(...JSXCONTEXTKEYS, "output", "return eval(output);");
} catch (error) {
return <AppError
title="Failed to execute JSX code"
error={getErrorMessage(error, "Unknown JSX evaluation error")}
jsx={jsx}
/>;
}

try {
return fn(...JSXCONTEXTVALUES, output);
} catch (error) {
return <AppError
title="Failed to execute JSX code"
error={getErrorMessage(error, "Unknown JSX execution error")}
jsx={jsx}
/>;
}
};

const AppDashboard: React.FC<{ jsx: string; css?: string }> = React.memo(
Expand All @@ -217,29 +293,9 @@ const AppDashboard: React.FC<{ jsx: string; css?: string }> = React.memo(
href={`data:text/css;base64,${btoa(css)}`}
></link>
)}
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
<JsxParser
renderInWrapper={false}
bindings={bindings}
components={components}
jsx={jsx}
renderError={({ error }) => (
<AppError
title="Failed to compile JSX code"
error={error}
jsx={jsx}
/>
)}
renderUnrecognized={(tagname) => (
<AppError
title="Failed to execute JSX code"
error={`Unrecognized tag: ${tagname}`}
/>
)}
/>
</ErrorBoundary>
<JSXRender jsx={jsx} />
</>
)
);

export default AppDashboard;

10 changes: 5 additions & 5 deletions src/connection/ConnectStored.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
MYHELLOIOT
Copyright (C) 2021-2022 Adrián Romero
Copyright (C) 2021-2023 Adrián Romero
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
Expand Down Expand Up @@ -374,10 +374,10 @@ const ConnectStored: React.FC<{
value?.data?.trim()
? Promise.resolve()
: Promise.reject(
new Error(
"Please upload a dashboard definition file."
)
),
new Error(
"Please upload a dashboard definition file."
)
),
},
]}
>
Expand Down
6 changes: 3 additions & 3 deletions src/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
MYHELLOIOT
Copyright (C) 2021-2022 Adrián Romero
Copyright (C) 2021-2023 Adrián Romero
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
Expand Down Expand Up @@ -79,9 +79,9 @@ const Dashboard: React.FC<DashboardProps> = ({
}

const menus: React.ReactElement<any, any>[] = [];
const dcchildren: React.ReactElement<DashboardContentProps, any>[] = [];
const dcchildren: React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>>[] = [];
const remainingchildren: React.ReactElement<any, any>[] = [];
let cvisible: React.ReactElement<DashboardContentProps, any> | undefined;
let cvisible: React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | undefined;
let keyvisible: string | undefined;
let menDisabled: boolean = false;
let disDisabled: boolean = false;
Expand Down
Loading

0 comments on commit b10a901

Please sign in to comment.