Implement deletion + context menu
This commit is contained in:
parent
7b23283201
commit
49a558589c
6 changed files with 169 additions and 1 deletions
|
@ -3,6 +3,8 @@ import { motion } from 'framer-motion';
|
|||
import { Properties } from '../Properties/Properties';
|
||||
import { IContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { getDepth, MakeIterator } from '../../utils/itertools';
|
||||
import { Menu } from '../Menu/Menu';
|
||||
import { MenuItem } from '../Menu/MenuItem';
|
||||
|
||||
interface IElementsSidebarProps {
|
||||
MainContainer: IContainerModel | null
|
||||
|
@ -11,9 +13,77 @@ interface IElementsSidebarProps {
|
|||
SelectedContainer: IContainerModel | null
|
||||
onPropertyChange: (key: string, value: string) => void
|
||||
selectContainer: (container: IContainerModel) => void
|
||||
deleteContainer: (containerid: string) => void
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface IElementsSidebarState {
|
||||
isContextMenuOpen: boolean
|
||||
contextMenuPosition: Point
|
||||
onClickContainerId: string
|
||||
}
|
||||
|
||||
export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps> {
|
||||
public state: IElementsSidebarState;
|
||||
public elementRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: IElementsSidebarProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isContextMenuOpen: false,
|
||||
contextMenuPosition: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
onClickContainerId: ''
|
||||
};
|
||||
this.elementRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.elementRef.current?.addEventListener('contextmenu', (event) => this.handleRightClick(event));
|
||||
window.addEventListener('click', (event) => this.handleLeftClick(event));
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.elementRef.current?.removeEventListener('contextmenu', (event) => this.handleRightClick(event));
|
||||
window.removeEventListener('click', (event) => this.handleLeftClick(event));
|
||||
}
|
||||
|
||||
public handleRightClick(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
if (!(event.target instanceof HTMLButtonElement)) {
|
||||
this.setState({
|
||||
isContextMenuOpen: false,
|
||||
onClickContainerId: ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const contextMenuPosition: Point = { x: event.pageX, y: event.pageY };
|
||||
this.setState({
|
||||
isContextMenuOpen: true,
|
||||
contextMenuPosition,
|
||||
onClickContainerId: event.target.id
|
||||
});
|
||||
}
|
||||
|
||||
public handleLeftClick(event: MouseEvent): void {
|
||||
if (!this.state.isContextMenuOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isContextMenuOpen: false,
|
||||
onClickContainerId: ''
|
||||
});
|
||||
}
|
||||
|
||||
public iterateChilds(handleContainer: (container: IContainerModel) => void): React.ReactNode {
|
||||
if (this.props.MainContainer == null) {
|
||||
return null;
|
||||
|
@ -56,6 +126,7 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
|
|||
`w-full elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||
}
|
||||
id={key}
|
||||
key={key}
|
||||
onClick={() => this.props.selectContainer(container)}>
|
||||
{ text }
|
||||
|
@ -68,9 +139,17 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
|
|||
<div className='bg-slate-100 font-bold sidebar-title'>
|
||||
Elements
|
||||
</div>
|
||||
<div className='overflow-y-auto overflow-x-hidden text-gray-800 flex-grow'>
|
||||
<div ref={this.elementRef} className='overflow-y-auto overflow-x-hidden text-gray-800 flex-grow'>
|
||||
{ containerRows }
|
||||
</div>
|
||||
<Menu
|
||||
className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'
|
||||
x={this.state.contextMenuPosition.x}
|
||||
y={this.state.contextMenuPosition.y}
|
||||
isOpen={this.state.isContextMenuOpen}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
|
|
23
src/Components/Menu/Menu.tsx
Normal file
23
src/Components/Menu/Menu.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react';
|
||||
|
||||
interface IMenuProps {
|
||||
className?: string
|
||||
x: number
|
||||
y: number
|
||||
isOpen: boolean
|
||||
children: React.ReactNode[] | React.ReactNode
|
||||
}
|
||||
|
||||
export const Menu: React.FC<IMenuProps> = (props) => {
|
||||
const visible = props.isOpen ? 'visible opacity-1' : 'invisible opacity-0';
|
||||
return (
|
||||
<div
|
||||
className={`fixed ${props.className ?? ''} ${visible}`}
|
||||
style={{
|
||||
left: props.x,
|
||||
top: props.y
|
||||
}}>
|
||||
{ props.children }
|
||||
</div>
|
||||
);
|
||||
};
|
16
src/Components/Menu/MenuItem.tsx
Normal file
16
src/Components/Menu/MenuItem.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import * as React from 'react';
|
||||
|
||||
interface IMenuItemProps {
|
||||
className?: string
|
||||
text: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const MenuItem: React.FC<IMenuItemProps> = (props) => {
|
||||
return (
|
||||
<button
|
||||
className={props.className}
|
||||
onClick={() => props.onClick()}>{props.text}
|
||||
</button>
|
||||
);
|
||||
};
|
|
@ -15,6 +15,7 @@ interface IUIProps {
|
|||
historyCurrentStep: number
|
||||
AvailableContainers: AvailableContainer[]
|
||||
SelectContainer: (container: ContainerModel) => void
|
||||
DeleteContainer: (containerId: string) => void
|
||||
OnPropertyChange: (key: string, value: string) => void
|
||||
AddContainer: (type: string) => void
|
||||
SaveEditorAsJSON: () => void
|
||||
|
@ -97,6 +98,7 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
|
|||
isHistoryOpen={this.state.isHistoryOpen}
|
||||
onPropertyChange={this.props.OnPropertyChange}
|
||||
selectContainer={this.props.SelectContainer}
|
||||
deleteContainer={this.props.DeleteContainer}
|
||||
/>
|
||||
<History
|
||||
history={this.props.history}
|
||||
|
|
|
@ -91,6 +91,49 @@ class Editor extends React.Component<IEditorProps> {
|
|||
});
|
||||
}
|
||||
|
||||
public DeleteContainer(containerId: string): void {
|
||||
const history = this.getCurrentHistory();
|
||||
const current = history[this.state.historyCurrentStep];
|
||||
|
||||
if (current.MainContainer === null) {
|
||||
throw new Error('[DeleteContainer] Error: Tried to delete a container without a main container');
|
||||
}
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||
const container = findContainerById(mainContainerClone, containerId);
|
||||
|
||||
if (container === undefined) {
|
||||
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
|
||||
}
|
||||
|
||||
if (container === mainContainerClone) {
|
||||
// TODO: Implement alert
|
||||
throw new Error('[DeleteContainer] Tried to delete the main container! Deleting the main container is not allowed !');
|
||||
}
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
if (container.parent != null) {
|
||||
const index = container.parent.children.indexOf(container);
|
||||
if (index > -1) {
|
||||
container.parent.children.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
history: history.concat([{
|
||||
SelectedContainer: null,
|
||||
SelectedContainerId: '',
|
||||
MainContainer: mainContainerClone,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]),
|
||||
historyCurrentStep: history.length
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handled the property change event in the properties form
|
||||
* @param key Property name
|
||||
|
@ -286,6 +329,7 @@ class Editor extends React.Component<IEditorProps> {
|
|||
historyCurrentStep={this.state.historyCurrentStep}
|
||||
AvailableContainers={this.state.configuration.AvailableContainers}
|
||||
SelectContainer={(container) => this.SelectContainer(container)}
|
||||
DeleteContainer={(containerId: string) => this.DeleteContainer(containerId)}
|
||||
OnPropertyChange={(key, value) => this.OnPropertyChange(key, value)}
|
||||
AddContainer={(type) => this.AddContainer(type)}
|
||||
SaveEditorAsJSON={() => this.SaveEditorAsJSON()}
|
||||
|
|
|
@ -44,4 +44,8 @@
|
|||
text-xs font-bold
|
||||
transition-all duration-100 scale-0 origin-left;
|
||||
}
|
||||
|
||||
.contextmenu-item {
|
||||
@apply px-2 py-1 hover:bg-slate-300 text-left
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue