dev.delete #19

Merged
Siklos merged 2 commits from dev.delete into dev 2022-08-08 09:41:54 -04:00
7 changed files with 172 additions and 4 deletions

View file

@ -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>
); );

View 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>
);
};

View 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>
);
};

View file

@ -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}

View file

@ -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()}

View file

@ -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
}
} }

View file

@ -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);
}); }
} }
} }