Improves lazy loading.

This commit is contained in:
Henning Dieterichs
2020-10-08 10:50:19 +02:00
parent 5512c0b244
commit cf382c71c9
33 changed files with 498 additions and 280 deletions

View File

@@ -1,7 +1,12 @@
import * as React from "react";
import * as ReactDom from "react-dom";
import { Visualization, Theme } from "./index";
import { Visualizer, VisualizationId, asVisualizationId } from "./Visualizer";
import {
Visualizer,
VisualizationId,
asVisualizationId,
VisualizationRenderOptions,
} from "./Visualizer";
interface Options {
id?: string;
@@ -27,30 +32,91 @@ export abstract class BaseReactVisualization implements Visualization<unknown> {
public render(
target: HTMLDivElement,
theme: Theme,
previous: unknown | undefined
): unknown {
const node = this.getReactNode(theme);
options: VisualizationRenderOptions<unknown>
): { renderState: unknown } {
const node = this.getReactNode({
theme: options.theme,
readyCallback: options.readyCallback,
});
// We could use this to get access to theme vars:
// getComputedStyle(target).getPropertyValue("--background-color")
//getComputedStyle(target).getPropertyValue("--background-color")
ReactDom.render(<>{node}</>, target);
return undefined;
return { renderState: undefined };
}
protected abstract getReactNode(theme: Theme): React.ReactChild;
public abstract preload(): Promise<void>;
protected abstract getReactNode(
options: ReactVisualizationRenderOptions
): React.ReactChild;
}
export class ReactVisualization extends BaseReactVisualization {
export interface ReactVisualizationRenderOptions {
theme: Theme;
readyCallback: () => void;
}
class ReactVisualization extends BaseReactVisualization {
private readonly _preload?: () => Promise<void>;
constructor(
sourceVisualizer: Visualizer,
options: Options,
private readonly renderReactNode: (theme: Theme) => React.ReactChild
options: Options & { preload?: () => Promise<void> },
private readonly renderReactNode: (
options: ReactVisualizationRenderOptions
) => React.ReactChild
) {
super(sourceVisualizer, options);
this._preload = options.preload;
}
protected getReactNode(theme: Theme): React.ReactChild {
return this.renderReactNode(theme);
protected getReactNode(
options: ReactVisualizationRenderOptions
): React.ReactChild {
return this.renderReactNode(options);
}
public async preload(): Promise<void> {
if (this._preload) {
return await this._preload();
}
}
}
export function createReactVisualization(
sourceVisualizer: Visualizer,
options: Options & { preload?: () => Promise<void> },
renderReactNode: (options: { theme: Theme }) => React.ReactChild
) {
return new ReactVisualization(sourceVisualizer, options, options => (
<ComponentWithOnMount
onDidMount={options.readyCallback}
child={renderReactNode(options)}
/>
));
}
class ComponentWithOnMount extends React.Component<{
child: React.ReactChild;
onDidMount: () => void;
}> {
componentDidMount() {
this.props.onDidMount();
}
render() {
return this.props.child;
}
}
export function createLazyReactVisualization(
sourceVisualizer: Visualizer,
options: Options & { preload?: () => Promise<void> },
renderReactNode: (
options: ReactVisualizationRenderOptions
) => React.ReactChild
) {
return new ReactVisualization(sourceVisualizer, options, options =>
renderReactNode(options)
);
}

View File

@@ -2,8 +2,22 @@ export class Theme {
public static readonly light = new Theme("light", "light");
public static readonly dark = new Theme("dark", "dark");
private readonly divElement = document.createElement("div");
constructor(
public readonly id: string,
public readonly kind: "light" | "dark"
) {}
) {
this.divElement.classList.add(
"themeable",
`theme-${this.id}`,
"visualization"
);
this.divElement.style.display = "none";
document.body.append(this.divElement);
}
public resolveVarToColor(varName: string): string {
return getComputedStyle(this.divElement).getPropertyValue(varName);
}
}

View File

@@ -5,6 +5,7 @@ import { Theme } from "./Theme";
export class VisualizationView extends React.Component<{
visualization: Visualization;
theme: Theme;
onReady?: () => void;
}> {
private readonly ref = React.createRef<HTMLDivElement>();
private visualizationRenderState: unknown = undefined;
@@ -19,11 +20,18 @@ export class VisualizationView extends React.Component<{
renderVisualization() {
const { visualization, theme } = this.props;
this.visualizationRenderState = visualization.render(
this.ref.current!,
const { renderState } = visualization.render(this.ref.current!, {
theme,
this.visualizationRenderState
);
previousRenderState: this.visualizationRenderState,
readyCallback: () => {
const r = this.props.onReady;
if (r) {
r();
}
},
});
this.visualizationRenderState = renderState;
}
render() {

View File

@@ -23,6 +23,12 @@ export function asVisualizationId(id: string): VisualizationId {
return (id as unknown) as VisualizationId;
}
export interface VisualizationRenderOptions<TRenderState> {
theme: Theme;
previousRenderState: TRenderState | undefined;
readyCallback: () => void;
}
export interface Visualization<TRenderState = any> {
readonly id: VisualizationId;
readonly name: string;
@@ -31,7 +37,11 @@ export interface Visualization<TRenderState = any> {
render(
target: HTMLDivElement,
theme: Theme,
previousRenderState: TRenderState | undefined
): TRenderState;
options: VisualizationRenderOptions<TRenderState>
): { renderState: TRenderState };
/**
* Preloads resources to speed up first rendering.
*/
preload(): Promise<void>;
}