import * as React from 'react'; import { motion } from 'framer-motion'; import { Properties } from '../Properties/Properties'; import { IContainerModel } from '../../Interfaces/ContainerModel'; import { findContainerById, getDepth, MakeIterator } from '../../utils/itertools'; import { Menu } from '../Menu/Menu'; import { MenuItem } from '../Menu/MenuItem'; interface IElementsSidebarProps { MainContainer: IContainerModel | null isOpen: boolean isHistoryOpen: boolean SelectedContainer: IContainerModel | null onPropertyChange: (key: string, value: string) => void SelectContainer: (container: IContainerModel) => void DeleteContainer: (containerid: string) => void AddContainer: (index: number, type: string, parent: string) => void } interface Point { x: number y: number } interface IElementsSidebarState { isContextMenuOpen: boolean contextMenuPosition: Point onClickContainerId: string } export class ElementsSidebar extends React.PureComponent { public state: IElementsSidebarState; public elementRef: React.RefObject; constructor(props: IElementsSidebarProps) { super(props); this.state = { isContextMenuOpen: false, contextMenuPosition: { x: 0, y: 0 }, onClickContainerId: '' }; this.elementRef = React.createRef(); } componentDidMount(): void { this.elementRef.current?.addEventListener('contextmenu', (event) => this.handleRightClick(event)); window.addEventListener('click', (event) => this.handleLeftClick(event)); } componentWillUnmount(): void { this.elementRef.current?.removeEventListener('contextmenu', (event) => this.handleRightClick(event)); window.removeEventListener('click', (event) => this.handleLeftClick(event)); } public handleRightClick(event: MouseEvent): void { event.preventDefault(); if (!(event.target instanceof HTMLButtonElement)) { this.setState({ isContextMenuOpen: false, onClickContainerId: '' }); return; } const contextMenuPosition: Point = { x: event.pageX, y: event.pageY }; this.setState({ isContextMenuOpen: true, contextMenuPosition, onClickContainerId: event.target.id }); } public handleLeftClick(event: MouseEvent): void { if (!this.state.isContextMenuOpen) { return; } this.setState({ isContextMenuOpen: false, onClickContainerId: '' }); } public handleDragOver(event: React.DragEvent): void { event.preventDefault(); } public handleOnDrop(event: React.DragEvent): void { event.preventDefault(); const type = event.dataTransfer.getData('type'); const target: HTMLButtonElement = event.target as HTMLButtonElement; if (this.props.MainContainer === null) { throw new Error('[handleOnDrop] Tried to drop into the tree without a required MainContainer!'); } const targetContainer: IContainerModel | undefined = findContainerById( this.props.MainContainer, target.id ); if (targetContainer === undefined) { throw new Error('[handleOnDrop] Tried to drop onto a unknown container!'); } if (targetContainer === this.props.MainContainer) { // if the container is the root, only add type as child this.props.AddContainer( targetContainer.children.length, type, targetContainer.properties.id); return; } if (targetContainer.parent === null || targetContainer.parent === undefined) { throw new Error('[handleDrop] Tried to drop into a child container without a parent!'); } const rect = target.getBoundingClientRect(); const y = event.clientY - rect.top; // y position within the element. // locate the hitboxes if (y < 12) { const index = targetContainer.parent.children.indexOf(targetContainer); this.props.AddContainer( index, type, targetContainer.parent.properties.id ); } else if (y < 24) { this.props.AddContainer( targetContainer.children.length, type, targetContainer.properties.id); } else { const index = targetContainer.parent.children.indexOf(targetContainer); this.props.AddContainer( index + 1, type, targetContainer.parent.properties.id ); } } public iterateChilds(handleContainer: (container: IContainerModel) => void): React.ReactNode { if (this.props.MainContainer == null) { return null; } const it = MakeIterator(this.props.MainContainer); for (const container of it) { handleContainer(container); } } public render(): JSX.Element { let isOpenClasses = '-right-64'; if (this.props.isOpen) { isOpenClasses = this.props.isHistoryOpen ? 'right-64' : 'right-0'; } const containerRows: React.ReactNode[] = []; this.iterateChilds((container: IContainerModel) => { const depth: number = getDepth(container); const key = container.properties.id.toString(); const text = '|\t'.repeat(depth) + key; const selectedClass: string = this.props.SelectedContainer !== undefined && this.props.SelectedContainer !== null && this.props.SelectedContainer.properties.id === container.properties.id ? 'border-l-4 border-blue-500 bg-slate-400/60 hover:bg-slate-400' : 'bg-slate-300/60 hover:bg-slate-300'; containerRows.push( this.handleOnDrop(event)} onDragOver={(event) => this.handleDragOver(event)} onClick={() => this.props.SelectContainer(container)}> { text } ); }); return (
Elements
{ containerRows }
this.props.DeleteContainer(this.state.onClickContainerId)} />
); } }