Implement drag and drop (#21)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: https://git.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react/pulls/21
This commit is contained in:
Siklos 2022-08-09 06:08:04 -04:00
parent f1e2326073
commit 1fc11adbaa
5 changed files with 172 additions and 45 deletions

View file

@ -2,7 +2,7 @@ import * as React from 'react';
import { motion } from 'framer-motion';
import { Properties } from '../Properties/Properties';
import { IContainerModel } from '../../Interfaces/ContainerModel';
import { getDepth, MakeIterator } from '../../utils/itertools';
import { findContainerById, getDepth, MakeIterator } from '../../utils/itertools';
import { Menu } from '../Menu/Menu';
import { MenuItem } from '../Menu/MenuItem';
@ -12,8 +12,9 @@ interface IElementsSidebarProps {
isHistoryOpen: boolean
SelectedContainer: IContainerModel | null
onPropertyChange: (key: string, value: string) => void
selectContainer: (container: IContainerModel) => void
deleteContainer: (containerid: string) => void
SelectContainer: (container: IContainerModel) => void
DeleteContainer: (containerid: string) => void
AddContainer: (index: number, type: string, parent: string) => void
}
interface Point {
@ -84,6 +85,97 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
});
}
public handleDragOver(event: React.DragEvent): 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 (this.props.MainContainer === null) {
throw new Error('[handleOnDrop] Tried to drop into the tree without a required MainContainer!');
}
if (target.id === this.props.MainContainer.properties.id) {
target.classList.add('border-8');
target.classList.remove('border-t-8');
target.classList.remove('border-b-8');
return;
}
if (y < 12) {
target.classList.add('border-t-8');
target.classList.remove('border-b-8');
target.classList.remove('border-8');
} else if (y < 24) {
target.classList.add('border-8');
target.classList.remove('border-t-8');
target.classList.remove('border-b-8');
} else {
target.classList.add('border-b-8');
target.classList.remove('border-8');
target.classList.remove('border-t-8');
}
}
public handleOnDrop(event: React.DragEvent): void {
event.preventDefault();
const type = event.dataTransfer.getData('type');
const target: HTMLButtonElement = event.target as HTMLButtonElement;
removeBorderClasses(target);
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;
@ -111,7 +203,7 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
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'
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
containerRows.push(
<motion.button
@ -123,12 +215,16 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
duration: 0.150
}}
className={
`w-full elements-sidebar-row whitespace-pre
`w-full border-blue-500 elements-sidebar-row whitespace-pre
text-left text-sm font-medium transition-all ${selectedClass}`
}
id={key}
key={key}
onClick={() => this.props.selectContainer(container)}>
onDrop={(event) => this.handleOnDrop(event)}
onDragOver={(event) => this.handleDragOver(event)}
onDragLeave={(event) => handleDragLeave(event)}
onClick={() => this.props.SelectContainer(container)}
>
{ text }
</motion.button>
);
@ -148,10 +244,21 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
y={this.state.contextMenuPosition.y}
isOpen={this.state.isContextMenuOpen}
>
<MenuItem className='contextmenu-item' text='Delete' onClick={() => this.props.deleteContainer(this.state.onClickContainerId)} />
<MenuItem className='contextmenu-item' text='Delete' onClick={() => this.props.DeleteContainer(this.state.onClickContainerId)} />
</Menu>
<Properties properties={this.props.SelectedContainer?.properties} onChange={this.props.onPropertyChange}></Properties>
</div>
);
}
}
function removeBorderClasses(target: HTMLButtonElement): void {
target.classList.remove('border-t-8');
target.classList.remove('border-8');
target.classList.remove('border-b-8');
}
function handleDragLeave(event: React.DragEvent): void {
const target: HTMLButtonElement = event.target as HTMLButtonElement;
removeBorderClasses(target);
}