The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/client/reactAdapters.js

222 lines
5.2 KiB

import { Blaze } from 'meteor/blaze';
import { HTML } from 'meteor/htmljs';
import { Random } from 'meteor/random';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
let rootNode;
let invalidatePortals = () => {};
const portalsMap = new Map();
const mountRoot = async () => {
rootNode = document.getElementById('react-root');
if (!rootNode) {
rootNode = document.createElement('div');
rootNode.id = 'react-root';
document.body.appendChild(rootNode);
}
const [
{ PureComponent, Suspense, createElement, lazy, useLayoutEffect, useState },
{ render },
] = await Promise.all([
import('react'),
import('react-dom'),
import('@rocket.chat/fuselage-hooks'),
]);
const LazyMeteorProvider = lazy(() => import('./providers/MeteorProvider'));
class PortalWrapper extends PureComponent {
state = { errored: false }
static getDerivedStateFromError = () => ({ errored: true })
componentDidCatch = () => {}
render = () => (this.state.errored ? null : this.props.portal)
}
function AppRoot() {
const [portals, setPortals] = useState(() => Tracker.nonreactive(() => Array.from(portalsMap.values())));
useLayoutEffect(() => {
invalidatePortals = () => {
setPortals(Array.from(portalsMap.values()));
};
invalidatePortals();
return () => {
invalidatePortals = () => {};
};
}, []);
return createElement(Suspense, { fallback: null },
createElement(LazyMeteorProvider, {},
portals.map(({ key, portal }) => createElement(PortalWrapper, { key, portal })),
),
);
}
render(createElement(AppRoot), rootNode);
};
const unregisterPortal = (key) => {
portalsMap.delete(key);
invalidatePortals();
};
export const registerPortal = (key, portal) => {
if (!rootNode) {
mountRoot();
}
portalsMap.set(key, { portal, key: Random.id() });
invalidatePortals();
return () => unregisterPortal(key);
};
const createLazyElement = async (importFn, propsFn) => {
const { createElement, lazy, useEffect, useState, memo, Suspense } = await import('react');
const LazyComponent = lazy(importFn);
if (!propsFn) {
return createElement(LazyComponent);
}
const WrappedComponent = memo(() => {
const [props, setProps] = useState(() => Tracker.nonreactive(propsFn));
useEffect(() => {
const computation = Tracker.autorun(() => {
setProps(propsFn());
});
return () => {
computation.stop();
};
}, []);
return createElement(Suspense, { fallback: null }, createElement(LazyComponent, props));
});
return createElement(WrappedComponent);
};
const createLazyPortal = async (importFn, propsFn, node) => {
const { createPortal } = await import('react-dom');
return createPortal(await createLazyElement(importFn, propsFn), node);
};
export const createEphemeralPortal = async (importFn, propsFn, node) => {
const portal = await createLazyPortal(importFn, propsFn, node);
return registerPortal(node, portal);
};
export const createTemplateForComponent = (
name,
importFn,
{
renderContainerView = () => HTML.DIV(), // eslint-disable-line new-cap
} = {},
) => {
if (Template[name]) {
return name;
}
const template = new Blaze.Template(name, renderContainerView);
let unregister;
template.onRendered(async function() {
const props = new ReactiveVar(this.data);
this.autorun(() => {
props.set(Template.currentData());
});
const portal = await createLazyPortal(importFn, () => props.get(), this.firstNode);
if (!this.firstNode) {
return;
}
unregister = await registerPortal(this, portal);
});
template.onDestroyed(function() {
unregister && unregister();
});
Template[name] = template;
return name;
};
export const renderRouteComponent = (importFn, {
template,
region,
propsFn = () => ({}),
} = {}) => {
const routeName = FlowRouter.getRouteName();
if (portalsMap.has(routeName)) {
return;
}
Tracker.autorun(async (computation) => {
if (routeName !== FlowRouter.getRouteName()) {
unregisterPortal(routeName);
computation.stop();
return;
}
if (!computation.firstRun) {
return;
}
if (!template || !region) {
BlazeLayout.reset();
const element = await createLazyElement(importFn, propsFn);
if (routeName !== FlowRouter.getRouteName()) {
return;
}
registerPortal(routeName, element);
return;
}
if (!Template[routeName]) {
const blazeTemplate = new Blaze.Template(routeName, () => HTML.DIV()); // eslint-disable-line new-cap
blazeTemplate.onRendered(async function() {
const node = this.firstNode.parentElement;
this.firstNode.remove();
const portal = await createLazyPortal(importFn, propsFn, node);
if (routeName !== FlowRouter.getRouteName()) {
return;
}
registerPortal(routeName, portal);
const handleMainContentDestroyed = () => {
unregisterPortal(routeName);
document.removeEventListener('main-content-destroyed', handleMainContentDestroyed);
};
document.addEventListener('main-content-destroyed', handleMainContentDestroyed);
});
Template[routeName] = blazeTemplate;
}
Tracker.afterFlush(() => {
BlazeLayout.render(template, { [region]: routeName });
});
});
};