terriajs
Version:
Geospatial data visualization platform.
143 lines (126 loc) • 4.12 kB
text/typescript
import { ReactElement } from "react";
import TerriaFeature from "../../Models/Feature/Feature";
import { BaseModel } from "../../Models/Definition/Model";
import Terria from "../../Models/Terria";
import ViewState from "../../ReactViewModels/ViewState";
/**
* DomElement type from @types/domhandler
*/
export interface DomElement {
attribs?: { [s: string]: string };
children?: DomElement[];
data?: any;
name?: string;
next?: DomElement;
parent?: DomElement;
prev?: DomElement;
type?: string;
}
/**
* The context for a transformation of custom components to React.
* Note: these will need to be passed into appropriate function parameters
* For example `parseCustomMarkdownToReact(someHtml, context)`
* - where context: ProcessNodeContext
*/
export interface ProcessNodeContext {
/**
* The Terria instance for which this HTML is being processed.
*/
readonly terria?: Terria;
/**
* The Terria instance for which this HTML is being processed.
*/
readonly viewState?: ViewState;
/**
* The catalog item for which this HTML is being processed.
*/
readonly catalogItem?: BaseModel;
/**
* The feature for which this HTML is being processed.
*/
readonly feature?: TerriaFeature;
}
/**
* A custom component type, e.g. `<chart>`.
*/
export default abstract class CustomComponent {
/**
* Gets the name of the DOM element for this custom component. For example,
* if this custom component is derived from a `<chart>` tag, the name property's
* value would be `"chart"`.
*/
abstract get name(): string;
/**
* Gets the custom attributes for this tag, eg. ["src-preview"].
* Used so that when the user-supplied html is sanitized, these attributes are not stripped.
*/
abstract get attributes(): string[];
/**
* Determine if a given DOM node should be processed by this component. By
* default, this method returns `true` if the node name matches the
* {@link CustomComponent#name} property. If this method returns `true`,
* {@link CustomComponent#processNode} will be called.
*
* @param context The context for the custom component
* @param node The node that should possibly be processed.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
shouldProcessNode(context: ProcessNodeContext, node: DomElement): boolean {
return this.name === node.name;
}
/**
* Transforms a DOM element into a React element. This function is passed to
* to html-to-react's `processingInstructions`.
*
* @param context Context for the custom component.
* @param node The DOM node to transformed to React.
* @param children The already-transformed React elements that are this node's children.
* @param index The index of this node in its parent's list of children.
*/
abstract processNode(
context: ProcessNodeContext,
node: DomElement,
children: ReactElement[],
index: number
): ReactElement | undefined;
private static readonly _types = new Map<string, CustomComponent>();
/**
* Registers a custom component.
* @param component The component to register.
*/
static register(component: CustomComponent) {
this._types.set(component.name, component);
}
/**
* Unregister all components
*/
static unregisterAll() {
this._types.clear();
}
/**
* Checks if a custom component with a given name is registered.
* @param name The name of the custom component.
* @returns True if the custom component is registered, otherwise false.
*/
static isRegistered(name: string): boolean {
return this._types.has(name);
}
/**
* Gets the names of the registered custom components.
*/
static get names(): string[] {
return Array.from(this._types.keys());
}
/**
* Gets the registered custom components.
*/
static get values(): CustomComponent[] {
return Array.from(this._types.values());
}
/**
* Gets all attributes of all custom components.
*/
static get attributes(): string[] {
return this.values.reduce((p, c) => p.concat(c.attributes), [] as string[]);
}
}