mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 15:22:55 +01:00
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
356 lines
11 KiB
JavaScript
356 lines
11 KiB
JavaScript
import React from 'react';
|
|
|
|
import { NoHomeError } from './components/common/NoHomeError';
|
|
import GraphWarnings from './graph-warnings';
|
|
import { Highlighter } from './highlighter';
|
|
import Inspector from './inspector';
|
|
import NoodlJSAPI from './noodl-js-api';
|
|
import projectSettings from './project-settings';
|
|
import { createNodeFromReactComponent } from './react-component-node';
|
|
import registerNodes from './register-nodes';
|
|
import Styles from './styles';
|
|
|
|
if (typeof window !== 'undefined' && window.NoodlEditor) {
|
|
window.NoodlEditorInspectorAPI = {
|
|
enabled: false,
|
|
inspector: null,
|
|
setInspector(inspector) {
|
|
this.inspector = inspector;
|
|
this.enabled ? this.inspector.enable() : this.inspector.disable();
|
|
},
|
|
setEnabled(enabled) {
|
|
this.enabled = enabled;
|
|
if (this.inspector) {
|
|
this.enabled ? this.inspector.enable() : this.inspector.disable();
|
|
}
|
|
|
|
if (window.NoodlEditorHighlightAPI.highlighter) {
|
|
window.NoodlEditorHighlightAPI.highlighter.setWindowSelected(enabled);
|
|
}
|
|
}
|
|
};
|
|
|
|
window.NoodlEditorHighlightAPI = {
|
|
highlighter: null,
|
|
setHighlighter(highlighter) {
|
|
this.highlighter = highlighter;
|
|
|
|
this.highlighter.setWindowSelected(window.NoodlEditorInspectorAPI.enabled);
|
|
},
|
|
selectNode(nodeId) {
|
|
this.highlighter.deselectNodes();
|
|
|
|
if (nodeId && nodeId !== 'null') {
|
|
this.highlighter.selectNodesWithId(nodeId);
|
|
} else if (window.NoodlEditorInspectorAPI.enabled) {
|
|
this.highlighter.setWindowSelected(true);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export function ssrSetupRuntime(noodlRuntime, noodlModules, projectData) {
|
|
registerNodes(noodlRuntime);
|
|
|
|
// Noodl static API
|
|
NoodlJSAPI(noodlRuntime);
|
|
|
|
noodlRuntime.setProjectSettings(projectSettings);
|
|
|
|
// Register module nodes
|
|
if (noodlModules) {
|
|
for (const module of noodlModules) {
|
|
if (module.reactNodes) {
|
|
const reactNodes = [];
|
|
for (const nodeDefinition of module.reactNodes) {
|
|
reactNodes.push(createNodeFromReactComponent(nodeDefinition));
|
|
}
|
|
const nodes = module.nodes || [];
|
|
module.nodes = nodes.concat(reactNodes);
|
|
}
|
|
noodlRuntime.registerModule(module);
|
|
}
|
|
}
|
|
|
|
const styles = new Styles({
|
|
graphModel: noodlRuntime.graphModel,
|
|
getNodeScope: () => noodlRuntime.context.rootComponent && noodlRuntime.context.rootComponent.nodeScope,
|
|
nodeRegister: noodlRuntime.context.nodeRegister
|
|
});
|
|
|
|
//make the styles available to all nodes via `this.context.styles`
|
|
noodlRuntime.context.styles = styles;
|
|
|
|
noodlRuntime.setData(projectData);
|
|
noodlRuntime._disableLoad = true;
|
|
}
|
|
|
|
export default class Viewer extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
popups: []
|
|
};
|
|
|
|
const { noodlRuntime } = props;
|
|
this.runningDeployed = this.props.projectData !== undefined;
|
|
this.focusedNoodlNodes = [];
|
|
|
|
noodlRuntime.context.setNodeFocused = this.setNodeFocused.bind(this);
|
|
|
|
const enableDebugInspectors =
|
|
(typeof document !== 'undefined' && document.location.href.indexOf('forceDebugger=true') !== -1) ||
|
|
Noodl.enableDebugInspectors;
|
|
noodlRuntime.setDebugInspectorsEnabled(enableDebugInspectors);
|
|
|
|
noodlRuntime.context.setPopupCallbacks({
|
|
onShow: (popup) => {
|
|
const newPopupArray = this.state.popups.concat([popup]);
|
|
|
|
const bodyScroll = noodlRuntime.getProjectSettings().bodyScroll;
|
|
|
|
//Disable body scroll when showing a popup
|
|
if (bodyScroll && newPopupArray.length === 1) {
|
|
document.body.style.width = document.body.clientWidth + 'px';
|
|
document.body.style.top = `-${window.scrollY}px`;
|
|
document.body.style.position = 'fixed';
|
|
}
|
|
|
|
this.setState({
|
|
popups: newPopupArray
|
|
});
|
|
},
|
|
onClose: (popup) => {
|
|
const newPopupArray = this.state.popups.filter((p) => p !== popup);
|
|
|
|
this.setState({
|
|
popups: newPopupArray
|
|
});
|
|
|
|
const bodyScroll = noodlRuntime.getProjectSettings().bodyScroll;
|
|
|
|
//Enable body scroll when hiding all popups
|
|
if (bodyScroll && newPopupArray.length === 0) {
|
|
const scrollY = document.body.style.top;
|
|
document.body.style.position = '';
|
|
document.body.style.top = '';
|
|
document.body.style.width = '100%';
|
|
window.scrollTo(0, parseInt(scrollY || '0') * -1);
|
|
}
|
|
}
|
|
});
|
|
|
|
registerNodes(noodlRuntime);
|
|
|
|
// Noodl static API
|
|
NoodlJSAPI(noodlRuntime);
|
|
|
|
noodlRuntime.setProjectSettings(projectSettings);
|
|
|
|
// Register module nodes
|
|
if (this.props.noodlModules) {
|
|
for (const module of this.props.noodlModules) {
|
|
if (module.reactNodes) {
|
|
const reactNodes = [];
|
|
for (const nodeDefinition of module.reactNodes) {
|
|
reactNodes.push(createNodeFromReactComponent(nodeDefinition));
|
|
}
|
|
const nodes = module.nodes || [];
|
|
module.nodes = nodes.concat(reactNodes);
|
|
}
|
|
noodlRuntime.registerModule(module);
|
|
}
|
|
}
|
|
|
|
noodlRuntime.eventEmitter.on('rootComponentUpdated', () => {
|
|
//wait until next frame to trigger a react update, so inputs etc have a chance to settle
|
|
//(forceUpdate is synchronous)
|
|
requestAnimationFrame(() => this.forceUpdate());
|
|
});
|
|
|
|
noodlRuntime.graphModel.on('projectSettingsChanged', (settings) => {
|
|
// Suppport SSR
|
|
if (typeof document === 'undefined') return;
|
|
|
|
if (settings.bodyScroll) {
|
|
document.body.classList.add('body-scroll');
|
|
} else {
|
|
document.body.classList.remove('body-scroll');
|
|
}
|
|
});
|
|
|
|
this.styles = new Styles({
|
|
graphModel: noodlRuntime.graphModel,
|
|
getNodeScope: () => noodlRuntime.context.rootComponent && noodlRuntime.context.rootComponent.nodeScope,
|
|
nodeRegister: noodlRuntime.context.nodeRegister
|
|
});
|
|
|
|
//make the styles available to all nodes via `this.context.styles`
|
|
noodlRuntime.context.styles = this.styles;
|
|
|
|
this.state.waitingForExport = !this.runningDeployed;
|
|
|
|
if (this.runningDeployed) {
|
|
this.props.noodlRuntime.setData(this.props.projectData);
|
|
|
|
//start pre-fetching the rest of the bundles after a while, if there arent too many of them
|
|
const allBundles = Object.keys(this.props.projectData.componentIndex);
|
|
if (allBundles.length < 30) {
|
|
setTimeout(() => {
|
|
this.props.noodlRuntime.prefetchBundles(allBundles, 3);
|
|
}, 10000);
|
|
}
|
|
} else {
|
|
noodlRuntime.graphModel.on('editorImportComplete', () => {
|
|
this.setState({ waitingForExport: false });
|
|
});
|
|
this.connectToEditor();
|
|
}
|
|
|
|
this.focusedNoodlNodes = [];
|
|
}
|
|
|
|
connectToEditor() {
|
|
const { noodlRuntime } = this.props;
|
|
|
|
// Remove hash if it is in location href
|
|
var href =
|
|
(Noodl.host || location.protocol + '//' + location.host) +
|
|
location.pathname +
|
|
(location.search ? location.search : '');
|
|
|
|
const address = href.replace('http', 'ws');
|
|
noodlRuntime.connectToEditor(address);
|
|
|
|
this.highlightedNodes = new Map();
|
|
this.isUpdatingHighlights = false;
|
|
|
|
if (typeof window !== 'undefined' && window.NoodlEditor) {
|
|
this.highlighter = new Highlighter(noodlRuntime);
|
|
NoodlEditorHighlightAPI.setHighlighter(this.highlighter);
|
|
|
|
noodlRuntime.editorConnection.on('hoverStart', (id) => {
|
|
this.highlighter.highlightNodesWithId(id);
|
|
});
|
|
noodlRuntime.editorConnection.on('hoverEnd', (id) => {
|
|
this.highlighter.disableHighlight();
|
|
});
|
|
|
|
this.inspector = new Inspector({
|
|
onDisableHighlight: () => this.highlighter.disableHighlight(),
|
|
onHighlight: (id) => this.highlighter.highlightNodesWithId(id),
|
|
onInspect: (ids) => {
|
|
NoodlEditor.inspectNodes(ids);
|
|
}
|
|
});
|
|
NoodlEditorInspectorAPI.setInspector(this.inspector);
|
|
}
|
|
|
|
noodlRuntime.editorConnection.on('debuggingEnabledChanged', (enabled) => {
|
|
noodlRuntime.setDebugInspectorsEnabled(enabled);
|
|
});
|
|
|
|
this.graphWarnings = new GraphWarnings(noodlRuntime.graphModel, noodlRuntime.editorConnection);
|
|
}
|
|
|
|
setNodeFocused(node, focused) {
|
|
if (focused && this.focusedNoodlNodes.indexOf(node) === -1) {
|
|
//blur nodes that don't contain this new node
|
|
this.focusedNoodlNodes
|
|
.filter((focusedNode) => !focusedNode.contains(node))
|
|
.forEach((blurredNode) => {
|
|
blurredNode._blur();
|
|
});
|
|
|
|
node._focus();
|
|
this.focusedNoodlNodes.push(node);
|
|
} else if (!focused) {
|
|
const index = this.focusedNoodlNodes.indexOf(node);
|
|
if (index !== -1) {
|
|
return;
|
|
}
|
|
|
|
node._blur();
|
|
|
|
//also blur nodes that contain this node
|
|
this.focusedNoodlNodes
|
|
.filter((focusedNode) => focusedNode.contains(node))
|
|
.forEach((blurredNode) => {
|
|
blurredNode._blur();
|
|
});
|
|
|
|
this.focusedNoodlNodes.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
onClickCapture(e) {
|
|
const focusedNoodlNodes = [];
|
|
|
|
//walk up the dom tree and collect all noodl nodes
|
|
let elem = e.target;
|
|
while (elem) {
|
|
if (elem.noodlNode && elem.noodlNode._focus) focusedNoodlNodes.push(elem.noodlNode);
|
|
elem = elem.parentNode;
|
|
}
|
|
|
|
//blur nodes that weren't part of this click
|
|
this.focusedNoodlNodes.filter((node) => focusedNoodlNodes.indexOf(node) === -1).forEach((node) => node._blur());
|
|
|
|
//focus all new focused nodes
|
|
focusedNoodlNodes.filter((node) => this.focusedNoodlNodes.indexOf(node) === -1).forEach((node) => node._focus());
|
|
|
|
this.focusedNoodlNodes = focusedNoodlNodes;
|
|
}
|
|
|
|
render() {
|
|
const rootComponent = this.props.noodlRuntime.rootComponent;
|
|
if (this.state.waitingForExport) return null;
|
|
|
|
if (!rootComponent) {
|
|
if (this.runningDeployed) {
|
|
return null;
|
|
} else {
|
|
return NoHomeError();
|
|
}
|
|
}
|
|
|
|
const bodyScroll = this.props.noodlRuntime.getProjectSettings().bodyScroll;
|
|
|
|
if (bodyScroll) {
|
|
const style = {
|
|
margin: 0,
|
|
padding: 0,
|
|
minHeight: '100vh',
|
|
alignSelf: 'stretch',
|
|
display: 'flex',
|
|
flexDirection: 'column'
|
|
};
|
|
return (
|
|
<div style={style} onClickCapture={(e) => this.onClickCapture(e)}>
|
|
<div style={{ ...style, isolation: 'isolate' }}>{rootComponent.render()}</div>
|
|
{this.state.popups.length ? (
|
|
<div style={{ ...style, isolation: 'isolate' }}>{this.state.popups.map((p) => p.render())}</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
} else {
|
|
return (
|
|
<div
|
|
style={{
|
|
margin: 0,
|
|
padding: 0,
|
|
overflow: 'hidden',
|
|
width: '100%',
|
|
height: '100%'
|
|
}}
|
|
onClickCapture={(e) => this.onClickCapture(e)}
|
|
>
|
|
{rootComponent.render()}
|
|
{this.state.popups.map((p) => p.render())}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
}
|