UNPKG

terriajs

Version:

Geospatial data visualization platform.

252 lines (224 loc) 6.95 kB
import { action, computed, makeObservable } from "mobx"; import { ComponentType } from "react"; import createGuid from "terriajs-cesium/Source/Core/createGuid"; import TerriaError from "../../Core/TerriaError"; import ViewerMode from "../../Models/ViewerMode"; import ViewState from "../../ReactViewModels/ViewState"; import { IconGlyph } from "../../Styled/Icon"; import MapNavigationItemController from "./MapNavigationItemController"; import { NavigationItemLocation } from "./MapNavigationModel"; export interface ToolConfig { /** * Optional `id` for the tool. If no `id` is passed we generate a random * one and return it as the result of the `addTool()` call. * * `id` is required for other operations on the tool like opening, closing or * removing it from the toolbar. */ id?: string; /** * A human readable name for the tool. */ name: string; /** * A loader function returning the react component module to mount when user activates the tool. * * example: ``` * { * toolComponentLoader: () => import("./MyToolModule.tsx"), * } * ``` */ toolComponentLoader: () => Promise<{ default: ComponentType<any> }>; /** * The tool button configuration */ toolButton: ToolButton; /** * Shows the tool button only for the specified map viewer mode. Defaults to * showing the tool button in both Cesium (3D) and Leaflet (2D) modes. * * eg: Setting this to `ViewerMode.Cesium` will result in the tool button * being shown only when the map is in Cesium 3D mode. */ viewerMode?: ViewerMode; } export interface ToolButton { /** * Tool button text */ text: string; /** * Tool button icon */ icon: IconGlyph; /** * Tooltip to show when hovering over the tool button */ tooltip?: string; /** * The toolBarSection to place the tool button. Defaults to `TOP`. * * The toolbar has 2 sections - TOP and BOTTOM * TOP - contains the compass and other buttons * BOTTOM - contains the feedback button */ section?: ToolbarSection; /** * A number used to determine the order of the button in the toolbar */ order?: number; } export type ToolbarSection = NavigationItemLocation; export { default as ViewerMode } from "../../Models/ViewerMode"; /** * Adds a new tool to the map toolbar * * @param viewState The {@link ViewState} instance * @param config The tool configuration * @returns `id` of the tool. This will be the same as `config.id` if it was set. Otherwise, a random `id` is generated and returned. * * Example: * const toolId = addTool(viewState, { * name: "X Tool", * toolComponentLoader: () => import("./XTool.tsx"), * toolButton: { * text: "Open X tool", * tooltip: "X Tool", * icon: X_ICON * } * }) */ export function addTool(viewState: ViewState, config: ToolConfig): string { const id = config.id ?? createGuid(); const controller = new ToolController(viewState, id, config); const terria = viewState.terria; terria.mapNavigationModel.addItem({ id, name: config.toolButton.text, title: config.toolButton.tooltip, location: config.toolButton.section ?? "TOP", order: config.toolButton.order, controller }); return id; } /** * Function to programatically open the tool with given ID. * * Note that in normal operation, a tool will be opened when user clicks the * tool button in the map toolbar. This function is useful in situations where * the tool needs to be opened through other means - like clicking a workbench * viewing-controls button. * * If no tool exists with the given `toolId` then this function does nothing * and returns `false`. The caller can also check if the call was successful * by checking the result of {@link isToolOpen()}. * * @param viewState The `ViewState` instance. * @param toolId ID of the tool to open. See {@link addTool}. * @param props Optional props to pass to the Tool component when activating it. * @returns `true` if the tool was successfully opened. */ export function openTool(viewState: ViewState, toolId: string, props?: any) { const controller = findToolController(viewState, toolId); controller?.openTool(props); return controller?.isToolOpen === true; } /** * Closes the tool with given id. * * @param viewState The `ViewState` instance. * @param toolId ID of the tool to close. */ export function closeTool(viewState: ViewState, toolId: string) { findToolController(viewState, toolId)?.closeTool(); } /** * Check if tool with given `id` is currently open. * * @param viewState The `ViewState` instance. * @param toolId ID of the tool to check. * @returns `true` if the tool is currently open, otherwise returns `false`. */ export function isToolOpen(viewState: ViewState, toolId: string): boolean { return findToolController(viewState, toolId)?.active === true; } /** * Removes the tool with the given id from the toolbar. * * @param viewState The `ViewState` instance. * @param toolId ID of the tool to close. */ export function removeTool(viewState: ViewState, toolId: string) { viewState.terria.mapNavigationModel.remove(toolId); } /** * Find `ToolController` by id. */ function findToolController( viewState: ViewState, toolId: string ): ToolController | undefined { const navItem = viewState.terria.mapNavigationModel.items.find( (it) => it.id === toolId ); return navItem?.controller instanceof ToolController ? navItem.controller : undefined; } export class ToolController extends MapNavigationItemController { constructor( readonly viewState: ViewState, readonly toolId: string, readonly toolConfig: ToolConfig ) { super(); makeObservable(this); } get glyph(): { id: string } { return this.toolConfig.toolButton.icon; } get viewerMode(): ViewerMode | undefined { return this.toolConfig.viewerMode; } @computed get isToolOpen() { return this.viewState.currentTool?.toolName === this.toolConfig.name; } @computed get active(): boolean { return super.active && this.isToolOpen; } openTool(props: any = {}) { const toolConfig = this.toolConfig; const toolId = this.toolId; try { this.viewState.openTool({ toolName: toolConfig.name, getToolComponent: () => toolConfig.toolComponentLoader().then((m) => m.default), params: { ...props, // Pass toolId as an extra prop to the component. // TODO: Maybe we should use react contexts to do this instead of a magic prop? toolId } }); } catch (err) { this.viewState.terria.raiseErrorToUser(TerriaError.from(err)); } super.activate(); } activate() { this.openTool(); } deactivate() { this.closeTool(); } @action closeTool() { if (this.isToolOpen) this.viewState.closeTool(); super.deactivate(); } }