Skip to content

Instantly share code, notes, and snippets.

@ggazzo
Last active May 3, 2023 22:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ggazzo/9dde077e75383470c0519e3ec497fa7f to your computer and use it in GitHub Desktop.
Save ggazzo/9dde077e75383470c0519e3ec497fa7f to your computer and use it in GitHub Desktop.
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { appLayout } from '../../lib/appLayout';
import { blazePortals, useBlazePortals } from '../../lib/portals/blazePortals';
import PageLoading from './PageLoading';
import { useEscapeKeyStroke } from './hooks/useEscapeKeyStroke';
import { useGoogleTagManager } from './hooks/useGoogleTagManager';
export const AppLayout = (): ReactElement => {
const layout = useSyncExternalStore(appLayout.subscribe, appLayout.getSnapshot);
const [portals] = useBlazePortals(blazePortals);
return (
<>
<Suspense fallback={<PageLoading />}>{layout}</Suspense>
{portals}
</>
);
};
import { Box } from '@rocket.chat/fuselage';
import { Blaze } from 'meteor/blaze';
import { ReactiveVar } from 'meteor/reactive-var';
import type { BlazeTemplates } from 'meteor/templating';
import { Template } from 'meteor/templating';
import type { ComponentProps } from 'react';
import React, { memo, useLayoutEffect, useRef } from 'react';
import { useBlazePortals } from '../../../lib/portals/blazePortals';
import { useRoom } from '../contexts/RoomContext';
import { useRoomMessageContext } from './body/hooks/useRoomMessageContext';
type PropsFromBox = 'className' | 'display' | 'flexGrow' | 'flexShrink' | 'flexDirection' | 'overflow' | 'w' | 'onClick' | 'onMouseEnter';
type AllBlazeTemplateProps = {
[TTemplateName in keyof BlazeTemplates]: BlazeTemplates[TTemplateName] extends Blaze.Template<infer D, any>
? symbol extends D
? {
name: TTemplateName;
} & Pick<ComponentProps<typeof Box>, PropsFromBox>
: {
name: TTemplateName;
} & Pick<ComponentProps<typeof Box>, Exclude<PropsFromBox, keyof D>> &
D
: never;
};
type BlazeTemplateProps<TTemplateName extends keyof AllBlazeTemplateProps> = TTemplateName extends any
? AllBlazeTemplateProps[TTemplateName]
: never;
const BlazeTemplate = <TTemplateName extends keyof AllBlazeTemplateProps>({
name,
className,
display,
flexGrow,
flexShrink,
flexDirection,
overflow,
w,
onClick,
onMouseEnter,
...props
}: BlazeTemplateProps<TTemplateName>) => {
const [portals, portalsSubscription] = useBlazePortals();
const roomMessageContext = useRoomMessageContext(useRoom());
const reactiveDataContextRef = useRef(new ReactiveVar(props));
useLayoutEffect(() => {
reactiveDataContextRef.current.set({ ...roomMessageContext, ...props });
});
const ref = useRef<HTMLDivElement>();
useLayoutEffect(() => {
if (!ref.current) {
return;
}
const view = Blaze.renderWithData(
Template[name],
() => ({ ...reactiveDataContextRef.current.get(), portalsSubscription: () => portalsSubscription }),
ref.current,
);
return (): void => {
Blaze.remove(view);
};
}, [name, portalsSubscription]);
return (
<>
<Box
rcx-blaze-template
className={className}
display={display ?? 'flex'}
flexGrow={flexGrow ?? 1}
flexShrink={flexShrink}
flexDirection={flexDirection ?? 'column'}
ref={ref}
w={w}
overflow={overflow}
onClick={onClick}
onMouseEnter={onMouseEnter}
/>
{portals}
</>
);
};
export default memo(BlazeTemplate);
import { Blaze } from 'meteor/blaze';
import { HTML } from 'meteor/htmljs';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import type { ComponentType, PropsWithoutRef, Attributes, ReactNode, PropsWithRef } from 'react';
import { Suspense, createElement, lazy } from 'react';
import { createPortal } from 'react-dom';
import { useReactiveValue } from '../../hooks/useReactiveValue';
import { getClosestBlazePortals } from './blazePortals';
export const createTemplateForComponent = <Props extends object>(
name: string,
factory: () => Promise<{ default: ComponentType<Props> }>,
options:
| {
renderContainerView?: () => unknown;
}
| {
attachment: 'at-parent';
props?: () => PropsWithoutRef<Props>;
} = {
renderContainerView: (): unknown => HTML.DIV(),
},
): string => {
if (Template[name]) {
return name;
}
const renderFunction =
('renderContainerView' in options && options.renderContainerView) ||
('attachment' in options && options.attachment === 'at-parent' && ((): unknown => HTML.Comment('anchor'))) ||
((): unknown => HTML.DIV());
const template = new Blaze.Template(name, renderFunction);
template.onRendered(function (this: Blaze.TemplateInstance) {
const reactiveProps = new ReactiveVar(this.data as PropsWithRef<Props & { children?: ReactNode }> & Attributes);
this.autorun(() => {
reactiveProps.set({
...('props' in options && typeof options.props === 'function' && options.props()),
...Template.currentData(),
});
});
const container =
('renderContainerView' in options && (this.firstNode as Element)) ||
('attachment' in options && options.attachment === 'at-parent' && (this.firstNode as Node).parentElement) ||
null;
if (!container) {
return;
}
const LazyComponent = lazy(factory);
const WrappedComponent = () => {
const props = useReactiveValue(() => reactiveProps.get());
return createElement(Suspense, { fallback: null }, createElement(LazyComponent, props));
};
const children = createElement(WrappedComponent);
const portal = createPortal(children, container);
const portalsSubscription = getClosestBlazePortals(this.view as Blaze.View);
portalsSubscription.register(this, portal);
});
template.onDestroyed(function (this: Blaze.TemplateInstance) {
const portalsSubscription = getClosestBlazePortals(this.view as Blaze.View);
portalsSubscription.unregister(this);
});
Template[name] = template;
return name;
};
import 'meteor/templating';
import type { Blaze } from 'meteor/blaze';
import type { Meteor } from 'meteor/meteor';
declare module 'meteor/blaze' {
namespace Blaze {
interface Template<D = any, T = Blaze.TemplateInstance<D>> {
events(eventsMap: Record<string, (this: any, event: Meteor.Event, instance: T) => void>): void;
}
}
}
declare module 'meteor/templating' {
type BlazeTemplate<TData = any, TInstanceExtras = Record<string, never>> = Blaze.Template<
TData,
Blaze.TemplateInstance<TData> & TInstanceExtras
>;
type BlazeTemplates = {};
interface TemplateStatic extends BlazeTemplates {
instance<TTemplateName extends keyof TemplateStatic>(): TemplateStatic[TTemplateName] extends Blaze.Template<any, infer I> ? I : never;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment