Implement basic drag-drop
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
884b38dc96
commit
4c10b0f8d7
5 changed files with 105 additions and 26 deletions
|
@ -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();
|
||||
|
|
|
@ -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<IElementsSidebarProps>
|
|||
});
|
||||
}
|
||||
|
||||
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<IElementsSidebarProps>
|
|||
}
|
||||
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 }
|
||||
</motion.button>
|
||||
);
|
||||
|
@ -148,7 +213,7 @@ 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>
|
||||
|
|
|
@ -8,14 +8,21 @@ interface ISidebarProps {
|
|||
buttonOnClick: (type: string) => void
|
||||
}
|
||||
|
||||
function handleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
|
||||
event.dataTransfer.setData('type', (event.target as HTMLButtonElement).id);
|
||||
}
|
||||
|
||||
export default class Sidebar extends React.PureComponent<ISidebarProps> {
|
||||
public render(): JSX.Element {
|
||||
const listElements = this.props.componentOptions.map(componentOption =>
|
||||
<button
|
||||
className='justify-center transition-all sidebar-component'
|
||||
key={componentOption.Type}
|
||||
id={componentOption.Type}
|
||||
title={componentOption.Type}
|
||||
onClick={() => this.props.buttonOnClick(componentOption.Type)}
|
||||
draggable={true}
|
||||
onDragStart={(event) => handleDragStart(event)}
|
||||
>
|
||||
{truncateString(componentOption.Type, 5)}
|
||||
</button>
|
||||
|
|
|
@ -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<IUIProps, IUIState> {
|
|||
<Sidebar
|
||||
componentOptions={this.props.AvailableContainers}
|
||||
isOpen={this.state.isSidebarOpen}
|
||||
buttonOnClick={(type: string) => this.props.AddContainer(type)}
|
||||
buttonOnClick={(type: string) => this.props.AddContainerToSelectedContainer(type)}
|
||||
/>
|
||||
<ElementsSidebar
|
||||
MainContainer={this.props.current.MainContainer}
|
||||
|
@ -97,8 +98,9 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
|
|||
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}
|
||||
/>
|
||||
<History
|
||||
history={this.props.history}
|
||||
|
|
|
@ -205,10 +205,10 @@ class Editor extends React.Component<IEditorProps> {
|
|||
}
|
||||
|
||||
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<IEditorProps> {
|
|||
);
|
||||
|
||||
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<IEditorProps> {
|
|||
);
|
||||
|
||||
// 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<IEditorProps> {
|
|||
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)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue