import * as GoldenLayout from "golden-layout"; import isNil from "lodash-es/isNil"; import pullAll from "lodash-es/pullAll"; import * as React from "react"; import { IModuleDrawerProps, ModuleDrawer } from "./drawer"; import { createItemConfigurationFromProps, IModuleProps, Module } from "./module"; import { convertModuleInTray, idOfModuleDrawer } from "./utils"; import { extractIdsFromContentItem, isElementClassEqualTo } from "../utils"; import { Workbench } from "../workbench"; /** * Props of a module tray component. */ export interface IModuleTrayProps { allowMultipleSelection?: boolean; children?: React.ReactNode; onChange?: (id: string, open: boolean) => void; openDrawers?: string[]; style?: React.CSSProperties; vertical?: boolean; workbench: Workbench; } /** * State of a module tray component. */ export interface IModuleTrayState { openDrawers: string[]; visibleIds: string[]; } /** * A module tray that holds multiple module drawers, each of which can be * opened in order to reveal a set of draggable placeholders that can be used * to place new components in a workbench. */ export class ModuleTray extends React.Component { private _visibleIds: string[]; private _workbench: Workbench | undefined; public static getDerivedStateFromProps(props: IModuleTrayProps) { const { openDrawers } = props; // If the component is controlled, openDrawers in state should be copied // from props return (openDrawers !== undefined) ? { openDrawers } : null; } public constructor(props: IModuleTrayProps) { super(props); this._visibleIds = []; this.state = { openDrawers: (React.Children.map(this.props.children || [], (child, index: number) => { if (isElementClassEqualTo(ModuleDrawer, child) && child.props.open) { return idOfModuleDrawer(child, index); } else { return ""; } } ) || []).filter(value => !isNil(value) && value !== ""), visibleIds: [] }; } public componentDidMount() { this._setWorkbench(this.props.workbench); } public componentDidUpdate() { this._setWorkbench(this.props.workbench); } public componentWillUnmount() { this._setWorkbench(undefined); } public render() { const { allowMultipleSelection, style, vertical, workbench } = this.props; const { openDrawers } = this.state; const isModuleEnabled = this._isModuleNotVisible; const drawers = React.Children.map(this.props.children, (child, index: number) => { if (isElementClassEqualTo(ModuleDrawer, child)) { const id = idOfModuleDrawer(child, index); const newProps: Partial = { onClick: this._addNewItemToWorkbench, onClose: this._onDrawerClosed.bind(this, id), onOpen: this._onDrawerOpened.bind(this, id), open: openDrawers.includes(id), workbench }; if (child.props.closeAfterDragging === undefined) { newProps.closeAfterDragging = !this._isControlled() && !allowMultipleSelection; } if (child.props.isModuleEnabled === undefined) { newProps.isModuleEnabled = isModuleEnabled; } child = React.cloneElement(child as any, newProps); } else if (isElementClassEqualTo(Module, child)) { return convertModuleInTray( { isModuleEnabled, onClick: this._addNewItemToWorkbench, workbench }, child ); } return child; }); const classes = ["wb-module-tray"]; if (vertical) { classes.push("wb-module-tray-vertical"); } return (
{drawers}
); } /** * Finds a place for a new item in the workbench if the user decides to * click on a module in the tray instead of dragging it to the workbench. */ private _addNewItemToWorkbench = (props: IModuleProps): void => { if (this._workbench === undefined) { throw new Error("Cannot add new item when the tray is not associated " + "to a workbench yet"); } const config = createItemConfigurationFromProps({ ...props, workbench: this._workbench })(); this._workbench.addNewItemWithConfiguration(config); } /** * Commits the list of item IDs visible in the workbench to the state of * this component from the corresponding local variable. */ private _commitVisibleIdsToState = (): void => { this.setState({ visibleIds: this._visibleIds.concat() }); } /** * Returns whether the tray is controlled. */ private _isControlled = (): boolean => this.props.openDrawers !== undefined; /** * Returns whether the module with the given props is not visible in * the workbench corresponding to the tray. */ private _isModuleNotVisible = (props: IModuleProps): boolean => { const { id } = props; return (id === undefined || !this._visibleIds.includes(id)); } /** * Sets the Workbench object associated to the module drawer. * * Also takes care of registering or deregistering all the event handlers * that the ModuleTray instance might be interested in. */ private _setWorkbench(value: Workbench | undefined): void { if (this._workbench === value) { return; } if (this._workbench !== undefined) { this._workbench.off("itemCreated", this._onItemCreated); this._workbench.off("itemDestroyed", this._onItemDestroyed); this._workbench.off("itemDropped", this._onItemDropped); } this._workbench = value; this._visibleIds = []; if (this._workbench !== undefined) { this._workbench.on("itemCreated", this._onItemCreated); this._workbench.on("itemDestroyed", this._onItemDestroyed); this._workbench.on("itemDropped", this._onItemDropped); this._updateVisibleIds(); } this._commitVisibleIdsToState(); } private _onDrawerOpened(id: string): void { if (this.props.onChange) { this.props.onChange(id, true); } if (!this._isControlled()) { const { allowMultipleSelection } = this.props; const openDrawers = allowMultipleSelection ? this.state.openDrawers.concat() : []; openDrawers.push(id); this.setState({ openDrawers }); } } private _onDrawerClosed(id: string): void { if (this.props.onChange) { this.props.onChange(id, false); } if (!this._isControlled()) { const { allowMultipleSelection } = this.props; const openDrawers = allowMultipleSelection ? this.state.openDrawers.filter(x => x !== id) : []; this.setState({ openDrawers }); } } private _onItemCreated = (item: GoldenLayout.ContentItem): void => { const ids = extractIdsFromContentItem(item); const isDragging = document.body.classList.contains("lm_dragging"); if (!isDragging) { this._visibleIds.push(...ids); this._commitVisibleIdsToState(); } } private _onItemDestroyed = (item: GoldenLayout.ContentItem): void => { const ids = extractIdsFromContentItem(item); if (ids.length > 0) { pullAll(this._visibleIds, ids); this._commitVisibleIdsToState(); } } private _onItemDropped = (): void => { this._updateVisibleIds(); this._commitVisibleIdsToState(); } /** * Updates the list of visible IDs by scanning the workbench. * * Note that this only updates the _visibleIds private variable but it does * not commit it ot the state yet. */ private _updateVisibleIds = (): void => { this._visibleIds.length = 0; if (this._workbench !== undefined) { this._workbench.forEach(item => { this._visibleIds.push.apply(this._visibleIds, extractIdsFromContentItem(item)); }); } } }