diff --git a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx index 0e58142..bca12ba 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx @@ -12,8 +12,8 @@ describe.concurrent('Elements sidebar', () => { isHistoryOpen={false} SelectedContainer={null} onPropertyChange={() => {}} - selectContainer={() => {}} - deleteContainer={() => {}} + SelectContainer={() => {}} + DeleteContainer={() => {}} />); expect(screen.getByText(/Elements/i)); @@ -40,8 +40,8 @@ describe.concurrent('Elements sidebar', () => { isHistoryOpen={false} SelectedContainer={null} onPropertyChange={() => {}} - selectContainer={() => {}} - deleteContainer={() => {}} + SelectContainer={() => {}} + DeleteContainer={() => {}} />); expect(screen.getByText(/Elements/i)); @@ -70,8 +70,8 @@ describe.concurrent('Elements sidebar', () => { isHistoryOpen={false} SelectedContainer={MainContainer} onPropertyChange={() => {}} - selectContainer={() => {}} - deleteContainer={() => {}} + SelectContainer={() => {}} + DeleteContainer={() => {}} />); expect(screen.getByText(/Elements/i)); @@ -155,8 +155,8 @@ describe.concurrent('Elements sidebar', () => { isHistoryOpen={false} SelectedContainer={MainContainer} onPropertyChange={() => {}} - selectContainer={() => {}} - deleteContainer={() => {}} + SelectContainer={() => {}} + DeleteContainer={() => {}} />); expect(screen.getByText(/Elements/i)); @@ -208,8 +208,8 @@ describe.concurrent('Elements sidebar', () => { isHistoryOpen={false} SelectedContainer={SelectedContainer} onPropertyChange={() => {}} - selectContainer={selectContainer} - deleteContainer={() => {}} + SelectContainer={selectContainer} + DeleteContainer={() => {}} />); expect(screen.getByText(/Elements/i)); @@ -230,8 +230,8 @@ describe.concurrent('Elements sidebar', () => { isHistoryOpen={false} SelectedContainer={SelectedContainer} onPropertyChange={() => {}} - selectContainer={selectContainer} - deleteContainer={() => {}} + SelectContainer={selectContainer} + DeleteContainer={() => {}} />); expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy(); diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index 621316b..47a2379 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -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,68 @@ export class ElementsSidebar extends React.PureComponent }); } + 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; @@ -128,7 +191,9 @@ export class ElementsSidebar extends React.PureComponent } id={key} key={key} - onClick={() => this.props.selectContainer(container)}> + onDrop={(event) => this.handleOnDrop(event)} + onDragOver={(event) => this.handleDragOver(event)} + onClick={() => this.props.SelectContainer(container)}> { text } ); @@ -148,7 +213,7 @@ export class ElementsSidebar extends React.PureComponent y={this.state.contextMenuPosition.y} isOpen={this.state.isContextMenuOpen} > - this.props.deleteContainer(this.state.onClickContainerId)} /> + this.props.DeleteContainer(this.state.onClickContainerId)} /> diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx index c50730e..de06e61 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Sidebar/Sidebar.tsx @@ -8,14 +8,21 @@ interface ISidebarProps { buttonOnClick: (type: string) => void } +function handleDragStart(event: React.DragEvent): void { + event.dataTransfer.setData('type', (event.target as HTMLButtonElement).id); +} + export default class Sidebar extends React.PureComponent { public render(): JSX.Element { const listElements = this.props.componentOptions.map(componentOption => diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 46ae454..e0d9b6c 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -17,7 +17,8 @@ interface IUIProps { SelectContainer: (container: ContainerModel) => void DeleteContainer: (containerId: string) => void OnPropertyChange: (key: string, value: string) => void - AddContainer: (type: string) => void + AddContainerToSelectedContainer: (type: string) => void + AddContainer: (index: number, type: string, parentId: string) => void SaveEditorAsJSON: () => void SaveEditorAsSVG: () => void LoadState: (move: number) => void @@ -89,7 +90,7 @@ export class UI extends React.PureComponent { this.props.AddContainer(type)} + buttonOnClick={(type: string) => this.props.AddContainerToSelectedContainer(type)} /> { isOpen={this.state.isElementsSidebarOpen} isHistoryOpen={this.state.isHistoryOpen} onPropertyChange={this.props.OnPropertyChange} - selectContainer={this.props.SelectContainer} - deleteContainer={this.props.DeleteContainer} + SelectContainer={this.props.SelectContainer} + DeleteContainer={this.props.DeleteContainer} + AddContainer={this.props.AddContainer} /> { } const parent = current.SelectedContainer; - this.AddContainer(type, parent.properties.id); + this.AddContainer(parent.children.length, type, parent.properties.id); } - public AddContainer(type: string, parentId: string): void { + public AddContainer(index: number, type: string, parentId: string): void { const history = this.getCurrentHistory(); const current = history[history.length - 1]; @@ -244,7 +244,7 @@ class Editor extends React.Component { ); if (parentClone === null || parentClone === undefined) { - throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); + throw new Error('[AddContainer] Container model was not found among children of the main container!'); } let x = 0; @@ -272,7 +272,11 @@ class Editor extends React.Component { ); // And push it the the parent children - parentClone.children.push(newContainer); + if (index === parentClone.children.length) { + parentClone.children.push(newContainer); + } else { + parentClone.children.splice(index, 0, newContainer); + } // Update the state this.setState({ @@ -335,7 +339,8 @@ class Editor extends React.Component { SelectContainer={(container) => this.SelectContainer(container)} DeleteContainer={(containerId: string) => this.DeleteContainer(containerId)} OnPropertyChange={(key, value) => this.OnPropertyChange(key, value)} - AddContainer={(type) => this.AddContainerToSelectedContainer(type)} + AddContainerToSelectedContainer={(type) => this.AddContainerToSelectedContainer(type)} + AddContainer={(index, type, parentId) => this.AddContainer(index, type, parentId)} SaveEditorAsJSON={() => this.SaveEditorAsJSON()} SaveEditorAsSVG={() => this.SaveEditorAsSVG()} LoadState={(move) => this.LoadState(move)}