187 lines
5.9 KiB
TypeScript
187 lines
5.9 KiB
TypeScript
import * as React from 'react';
|
|
import { FixedSizeList as List } from 'react-window';
|
|
import { Properties } from '../ContainerProperties/ContainerProperties';
|
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
|
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
|
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
|
import { PropertyType } from '../../Enums/PropertyType';
|
|
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
|
|
|
interface IElementsListProps {
|
|
mainContainer: IContainerModel
|
|
symbols: Map<string, ISymbolModel>
|
|
selectedContainer: IContainerModel | undefined
|
|
onPropertyChange: (
|
|
key: string,
|
|
value: string | number | boolean | number[],
|
|
type?: PropertyType
|
|
) => void
|
|
selectContainer: (containerId: string) => void
|
|
addContainer: (index: number, type: string, parent: string) => 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,
|
|
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(
|
|
mainContainer,
|
|
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;
|
|
}
|
|
|
|
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);
|
|
addContainer(
|
|
index,
|
|
type,
|
|
targetContainer.parent.properties.id
|
|
);
|
|
} else if (y < 24) {
|
|
addContainer(
|
|
targetContainer.children.length,
|
|
type,
|
|
targetContainer.properties.id);
|
|
} else {
|
|
const index = targetContainer.parent.children.indexOf(targetContainer);
|
|
addContainer(
|
|
index + 1,
|
|
type,
|
|
targetContainer.parent.properties.id
|
|
);
|
|
}
|
|
}
|
|
|
|
export function ElementsList(props: IElementsListProps): JSX.Element {
|
|
// Render
|
|
const it = MakeRecursionDFSIterator(props.mainContainer, 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 tabs = '|\t'.repeat(depth);
|
|
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;
|
|
|
|
const selectedClass: string = isSelected
|
|
? 'border-l-4 bg-blue-500 shadow-lg shadow-blue-500/60 hover:bg-blue-600 hover:shadow-blue-500 text-slate-50'
|
|
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
|
|
|
|
return (
|
|
<button type="button"
|
|
className={`transition-all w-full border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre
|
|
text-left text-sm font-medium inline-flex ${container.properties.type} ${selectedClass}`}
|
|
id={key}
|
|
key={key}
|
|
style={style}
|
|
title={container.properties.warning}
|
|
onClick={() => props.selectContainer(container.properties.id)}
|
|
onDrop={(event) => HandleOnDrop(event, props.mainContainer, props.addContainer)}
|
|
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
|
|
onDragLeave={(event) => HandleDragLeave(event)}
|
|
>
|
|
{tabs}
|
|
{text}
|
|
{container.properties.warning.length > 0 &&
|
|
<ExclamationTriangleIcon className='w-8'/>
|
|
}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='h-full flex flex-col'>
|
|
<div className='h-48'>
|
|
<List
|
|
className="List divide-y divide-black overflow-y-auto"
|
|
itemCount={containers.length}
|
|
itemSize={35}
|
|
height={192}
|
|
width={'100%'}
|
|
>
|
|
{Row}
|
|
</List>
|
|
</div>
|
|
<div className='grow overflow-auto'>
|
|
<Properties
|
|
properties={props.selectedContainer?.properties}
|
|
symbols={props.symbols}
|
|
onChange={props.onPropertyChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|