import * as React from 'react'; import useSize from '@react-hook/size'; import { FixedSizeList as List } from 'react-window'; import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; import { ContainerProperties } from '../ContainerProperties/ContainerProperties'; import { type IContainerModel } from '../../Interfaces/IContainerModel'; import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel'; import { type PropertyType } from '../../Enums/PropertyType'; import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar'; import { Text } from '../Text/Text'; import { ExtendedSidebar } from '../UI/UI'; interface IElementsSidebarProps { containers: Map mainContainer: IContainerModel symbols: Map selectedContainer: IContainerModel | undefined selectedExtendedSidebar: ExtendedSidebar onPropertyChange: ( key: string, value: string | number | boolean | number[], type?: PropertyType ) => void selectContainer: (containerId: string) => void addContainer: (index: number, type: string, parent: string) => void onExpandChange: (value: ExtendedSidebar) => void } function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void { const bordersClasses = ['border-t-8', 'border-8', 'border-b-8'].filter(className => className !== exception); target.classList.remove(...bordersClasses); } function HandleDragLeave(event: React.DragEvent): void { const target: HTMLButtonElement = event.target as HTMLButtonElement; RemoveBorderClasses(target); } function HandleDragOver( event: React.DragEvent, mainContainer: IContainerModel ): void { event.preventDefault(); const target: HTMLButtonElement = event.target as HTMLButtonElement; const rect = target.getBoundingClientRect(); const y = event.clientY - rect.top; // y position within the element. if (target.id === mainContainer.properties.id) { target.classList.add('border-8'); return; } if (y < 12) { RemoveBorderClasses(target, 'border-t-8'); target.classList.add('border-t-8'); } else if (y < 24) { RemoveBorderClasses(target, 'border-8'); target.classList.add('border-8'); } else { RemoveBorderClasses(target, 'border-b-8'); target.classList.add('border-b-8'); } } function HandleOnDrop( event: React.DragEvent, containers: Map, mainContainer: IContainerModel, addContainer: (index: number, type: string, parent: string) => void ): void { event.preventDefault(); const type = event.dataTransfer.getData('type'); const target: HTMLButtonElement = event.target as HTMLButtonElement; RemoveBorderClasses(target); const targetContainer: IContainerModel | undefined = FindContainerById( containers, target.id ); if (targetContainer === undefined) { throw new Error('[handleOnDrop] Tried to drop onto a unknown container!'); } if (targetContainer === mainContainer) { // if the container is the root, only add type as child addContainer( targetContainer.children.length, type, targetContainer.properties.id ); return; } const parent = FindContainerById(containers, targetContainer.properties.parentId); if (parent === null || 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 = parent.children.indexOf(targetContainer.properties.id); addContainer( index, type, parent.properties.id ); } else if (y < 24) { addContainer( targetContainer.children.length, type, targetContainer.properties.id ); } else { const index = parent.children.indexOf(targetContainer.properties.id); addContainer( index + 1, type, parent.properties.id ); } } export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element { // States const divRef = React.useRef(null); const [,height] = useSize(divRef); // Render const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true); const containers = [...it]; function Row({ index, style }: { index: number style: React.CSSProperties }): JSX.Element { const { container, depth } = containers[index]; const key = container.properties.id.toString(); const text = container.properties.displayedText === key ? `${key}` : `${container.properties.displayedText}`; const isSelected = props.selectedContainer !== undefined && props.selectedContainer !== null && props.selectedContainer.properties.id === container.properties.id; return ( ElementsListRow( key, container, depth, isSelected, style, text, props.containers, props.mainContainer, props.addContainer, props.selectContainer ) ); } return (
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
}
{ const newValue = props.selectedExtendedSidebar !== ExtendedSidebar.Property ? ExtendedSidebar.Property : ExtendedSidebar.None; props.onExpandChange(newValue); }} />
{Row}
); } function ElementsListRow( key: string, container: IContainerModel, depth: number, isSelected: boolean, style: React.CSSProperties, text: string, containers: Map, mainContainer: IContainerModel, addContainer: (index: number, type: string, parent: string) => void, selectContainer: (containerId: string) => void ): JSX.Element { const verticalBars: JSX.Element[] = []; const verticalBarSelectedClass = isSelected ? 'border-l-blue-400 group-hover:border-l-blue-300' : 'border-l-slate-400 group-hover:border-l-slate-300'; for (let i = 0; i < depth; i++) { verticalBars.push(); } const buttonSelectedClass: string = isSelected ? 'bg-blue-500 shadow-lg shadow-blue-500/60 hover:bg-blue-600 border-blue-600 hover:shadow-blue-500 text-slate-50' : 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400'; return ; }