dev.delete #19
7 changed files with 172 additions and 4 deletions
|
@ -3,6 +3,8 @@ import { motion } from 'framer-motion';
|
||||||
import { Properties } from '../Properties/Properties';
|
import { Properties } from '../Properties/Properties';
|
||||||
import { IContainerModel } from '../../Interfaces/ContainerModel';
|
import { IContainerModel } from '../../Interfaces/ContainerModel';
|
||||||
import { getDepth, MakeIterator } from '../../utils/itertools';
|
import { getDepth, MakeIterator } from '../../utils/itertools';
|
||||||
|
import { Menu } from '../Menu/Menu';
|
||||||
|
import { MenuItem } from '../Menu/MenuItem';
|
||||||
|
|
||||||
interface IElementsSidebarProps {
|
interface IElementsSidebarProps {
|
||||||
MainContainer: IContainerModel | null
|
MainContainer: IContainerModel | null
|
||||||
|
@ -11,9 +13,77 @@ interface IElementsSidebarProps {
|
||||||
SelectedContainer: IContainerModel | null
|
SelectedContainer: IContainerModel | null
|
||||||
onPropertyChange: (key: string, value: string) => void
|
onPropertyChange: (key: string, value: string) => void
|
||||||
selectContainer: (container: IContainerModel) => 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> {
|
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 {
|
public iterateChilds(handleContainer: (container: IContainerModel) => void): React.ReactNode {
|
||||||
if (this.props.MainContainer == null) {
|
if (this.props.MainContainer == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -56,6 +126,7 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
|
||||||
`w-full elements-sidebar-row whitespace-pre
|
`w-full elements-sidebar-row whitespace-pre
|
||||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||||
}
|
}
|
||||||
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
onClick={() => this.props.selectContainer(container)}>
|
onClick={() => this.props.selectContainer(container)}>
|
||||||
{ text }
|
{ text }
|
||||||
|
@ -68,9 +139,17 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
|
||||||
<div className='bg-slate-100 font-bold sidebar-title'>
|
<div className='bg-slate-100 font-bold sidebar-title'>
|
||||||
Elements
|
Elements
|
||||||
</div>
|
</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 }
|
{ containerRows }
|
||||||
</div>
|
</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>
|
<Properties properties={this.props.SelectedContainer?.properties} onChange={this.props.onPropertyChange}></Properties>
|
||||||
</div>
|
</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
|
historyCurrentStep: number
|
||||||
AvailableContainers: AvailableContainer[]
|
AvailableContainers: AvailableContainer[]
|
||||||
SelectContainer: (container: ContainerModel) => void
|
SelectContainer: (container: ContainerModel) => void
|
||||||
|
DeleteContainer: (containerId: string) => void
|
||||||
OnPropertyChange: (key: string, value: string) => void
|
OnPropertyChange: (key: string, value: string) => void
|
||||||
AddContainer: (type: string) => void
|
AddContainer: (type: string) => void
|
||||||
SaveEditorAsJSON: () => void
|
SaveEditorAsJSON: () => void
|
||||||
|
@ -97,6 +98,7 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
|
||||||
isHistoryOpen={this.state.isHistoryOpen}
|
isHistoryOpen={this.state.isHistoryOpen}
|
||||||
onPropertyChange={this.props.OnPropertyChange}
|
onPropertyChange={this.props.OnPropertyChange}
|
||||||
selectContainer={this.props.SelectContainer}
|
selectContainer={this.props.SelectContainer}
|
||||||
|
deleteContainer={this.props.DeleteContainer}
|
||||||
/>
|
/>
|
||||||
<History
|
<History
|
||||||
history={this.props.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
|
* Handled the property change event in the properties form
|
||||||
* @param key Property name
|
* @param key Property name
|
||||||
|
@ -286,6 +329,7 @@ class Editor extends React.Component<IEditorProps> {
|
||||||
historyCurrentStep={this.state.historyCurrentStep}
|
historyCurrentStep={this.state.historyCurrentStep}
|
||||||
AvailableContainers={this.state.configuration.AvailableContainers}
|
AvailableContainers={this.state.configuration.AvailableContainers}
|
||||||
SelectContainer={(container) => this.SelectContainer(container)}
|
SelectContainer={(container) => this.SelectContainer(container)}
|
||||||
|
DeleteContainer={(containerId: string) => this.DeleteContainer(containerId)}
|
||||||
OnPropertyChange={(key, value) => this.OnPropertyChange(key, value)}
|
OnPropertyChange={(key, value) => this.OnPropertyChange(key, value)}
|
||||||
AddContainer={(type) => this.AddContainer(type)}
|
AddContainer={(type) => this.AddContainer(type)}
|
||||||
SaveEditorAsJSON={() => this.SaveEditorAsJSON()}
|
SaveEditorAsJSON={() => this.SaveEditorAsJSON()}
|
||||||
|
|
|
@ -44,4 +44,8 @@
|
||||||
text-xs font-bold
|
text-xs font-bold
|
||||||
transition-all duration-100 scale-0 origin-left;
|
transition-all duration-100 scale-0 origin-left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contextmenu-item {
|
||||||
|
@apply px-2 py-1 hover:bg-slate-300 text-left
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,14 +11,14 @@ export function * MakeIterator(root: IContainerModel): Generator<IContainerModel
|
||||||
|
|
||||||
yield container;
|
yield container;
|
||||||
|
|
||||||
// if this reverse() gets costly, replace it by a simple for
|
for (let i = container.children.length - 1; i >= 0; i--) {
|
||||||
container.children.forEach((child) => {
|
const child = container.children[i];
|
||||||
if (visited.has(child)) {
|
if (visited.has(child)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visited.add(child);
|
visited.add(child);
|
||||||
queue.push(child);
|
queue.push(child);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue