import path from "path";
import { ipcRenderer } from "electron";
import React from "react";
import {
observable,
makeObservable,
autorun,
action,
IReactionDisposer,
runInAction
} from "mobx";
import { observer } from "mobx-react";
import {
registerClass,
makeDerivedClassInfo,
ProjectType,
PropertyType,
IMessage,
EezObject,
ClassInfo,
PropertyProps,
PropertyInfo,
findPropertyByNameInClassInfo
} from "project-editor/core/object";
import {
makeDataPropertyInfo,
makeExpressionProperty,
makeStylePropertyInfo,
Widget
} from "project-editor/flow/component";
import { IFlowContext } from "project-editor/flow/flow-interfaces";
import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups";
import { Button } from "eez-studio-ui/button";
import { EMBEDDED_DASHBOARD_WIDGET_ICON } from "project-editor/ui-components/icons";
import {
getObjectPathAsString,
ProjectStore,
propertyNotSetMessage
} from "project-editor/store";
import { ProjectContext } from "project-editor/project/context";
import { ProjectEditorView } from "project-editor/project/ui/ProjectEditor";
import { evalProperty, getStringValue } from "project-editor/flow/helper";
import { createWasmValue } from "project-editor/flow/runtime/wasm-value";
import type { WasmRuntime } from "project-editor/flow/runtime/wasm-runtime";
import { isValidUrl } from "project-editor/core/util";
import { evalConstantExpression } from "project-editor/flow/expression";
////////////////////////////////////////////////////////////////////////////////
class LoadDashboard {
projectStore: ProjectStore | undefined;
loadError: string | undefined;
autorunDispose: IReactionDisposer | undefined;
unmounted: boolean = false;
constructor(
public flowContext: IFlowContext,
public widget: EmbeddedDashboardWidget,
public dashboardFilePath: string
) {
makeObservable(this, {
projectStore: observable,
loadError: observable,
unmount: action
});
}
async load() {
const projectStore = ProjectStore.create({
type: "run-embedded",
parentProjectStore: this.flowContext.projectStore,
dashboardPath: getObjectPathAsString(this.widget)
});
projectStore.mount();
try {
await projectStore.openFile(this.dashboardFilePath);
if (this.unmounted) {
await projectStore.closeWindow();
projectStore.unmount();
} else {
projectStore.project._fullyLoaded = true;
projectStore.setRuntimeMode(false);
const WasmRuntime = projectStore!.runtime! as WasmRuntime;
WasmRuntime.onInitialized = async () => {
runInAction(() => {
this.projectStore = projectStore;
this.loadError = undefined;
});
this.updateGlobalVariablesWithDashboardParameters();
};
}
} catch (err) {
await projectStore.closeWindow();
projectStore.unmount();
if (!this.unmounted) {
let loadError = err.toString();
let i = loadError.indexOf("ENOENT:");
if (i != -1) {
loadError = `Failed to load: ${this.dashboardFilePath}`;
}
runInAction(() => (this.loadError = loadError));
}
}
}
updateGlobalVariablesWithDashboardParameters() {
this.autorunDispose = autorun(() => {
for (let i = 0; i < this.widget.dashboardParameters.length; i++) {
const value = evalProperty(
this.flowContext,
this.widget,
`dashboardParameters[${i}].value`
);
const WasmRuntime = this.projectStore!.runtime! as WasmRuntime;
const WasmFlowRuntime = WasmRuntime.worker.wasm;
const assetsMap = WasmRuntime.assetsMap;
const globalVariable = assetsMap.globalVariables.find(
globalVariable =>
globalVariable.name ==
this.widget.dashboardParameters[i].name
);
if (globalVariable != undefined) {
const valuePtr = createWasmValue(
WasmFlowRuntime,
value,
parseInt(assetsMap.typeIndexes[globalVariable.type])
);
WasmFlowRuntime._setGlobalVariable(
globalVariable.index,
valuePtr
);
WasmFlowRuntime._valueFree(valuePtr);
} else {
// TODO
console.error(
"Invalid dashboard parameter",
this.widget.dashboardParameters[i].name
);
}
}
});
}
async unmount() {
if (this.autorunDispose) {
this.autorunDispose();
}
if (this.projectStore) {
await this.projectStore.closeWindow();
this.projectStore.unmount();
this.projectStore = undefined;
}
this.unmounted = true;
}
}
////////////////////////////////////////////////////////////////////////////////
const EmbeddedDashboardElement = observer(
class EmbeddedDashboardElement extends React.Component<{
widget: EmbeddedDashboardWidget;
flowContext: IFlowContext;
width: number;
height: number;
}> {
loadDashboard: LoadDashboard | undefined;
error: string | undefined;
constructor(props: any) {
super(props);
makeObservable(this, {
loadDashboard: observable,
error: observable,
doLoadDashboard: action
});
}
doLoadDashboard() {
if (!this.props.flowContext.flowState) {
if (this.loadDashboard) {
this.loadDashboard.unmount();
this.loadDashboard = undefined;
}
return;
}
const dashboardFilePath = this.props.widget.getDashboard(
this.props.flowContext
);
if (this.loadDashboard) {
if (this.loadDashboard.dashboardFilePath == dashboardFilePath) {
return;
}
this.loadDashboard.unmount();
this.loadDashboard = undefined;
}
if (dashboardFilePath) {
const parentProjectStore = this.props.flowContext.projectStore;
const isCycleDetected = (
parentProjectStore: ProjectStore
): boolean => {
if (dashboardFilePath == parentProjectStore.filePath) {
return true;
}
if (parentProjectStore.context.type == "run-embedded") {
return isCycleDetected(
parentProjectStore.context.parentProjectStore
);
}
return false;
};
if (isCycleDetected(parentProjectStore)) {
this.error = "Cycle detected in Embedded Dashboard widget";
} else {
this.error = undefined;
this.loadDashboard = new LoadDashboard(
this.props.flowContext,
this.props.widget,
dashboardFilePath
);
this.loadDashboard.load();
}
} else {
if (!this.props.flowContext.projectStore.runtime) {
this.error = "Dashboard not specified";
}
}
}
componentDidMount() {
this.doLoadDashboard();
}
componentDidUpdate() {
this.doLoadDashboard();
}
componentWillUnmount(): void {
if (this.loadDashboard) {
this.loadDashboard.unmount();
}
}
render() {
let style: React.CSSProperties = {
display: "flex",
height: "100%"
};
let content;
if (this.props.flowContext.projectStore.runtime) {
this.props.widget.getDashboard(this.props.flowContext);
if (this.error) {
content = this.error;
style.alignItems = "center";
style.justifyContent = "center";
} else if (this.loadDashboard?.loadError) {
content = this.loadDashboard.loadError;
style.alignItems = "center";
style.justifyContent = "center";
} else if (this.loadDashboard?.projectStore) {
content = (
Embedded dashboard:
{this.props.widget.getDashboardInfo(
this.props.flowContext
)}
>
);
style.flexDirection = "column";
style.alignItems = "center";
style.justifyContent = "center";
style.overflow = "hidden";
}
return