Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
feat: implement page-element renaming
Browse files Browse the repository at this point in the history
  • Loading branch information
marionebl committed May 4, 2018
1 parent 7d59867 commit ac1412a
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 27 deletions.
78 changes: 59 additions & 19 deletions src/component/container/element-list.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { colors } from '../../lsg/patterns/colors';
import { elementMenu } from '../../electron/context-menus';
import { ElementAnchors } from '../../lsg/patterns/element';
import { ElementAnchors, ElementProps } from '../../lsg/patterns/element';
import { ElementLocationCommand } from '../../store/command/element-location-command';
import { ElementNameCommand } from '../../store/command/element-name-command';
import { ElementWrapper } from './element-wrapper';
import { ListItemProps } from '../../lsg/patterns/list';
import { createMenu } from '../../electron/menu';
import { observer } from 'mobx-react';
import { Page } from '../../store/page/page';
Expand Down Expand Up @@ -33,21 +33,30 @@ const DRAG_IMG_STYLE = `
@observer
export class ElementList extends React.Component<{}, ElementListState> {
private dragImg?: HTMLElement;
private globalKeyUpListener?: (e: KeyboardEvent) => void;

public state = {
dragging: true
};

public componentDidMount(): void {
createMenu();

this.globalKeyUpListener = e => this.handleKeyUp(e);
window.addEventListener('keyup', this.globalKeyUpListener);
}

public componentWillUnmount(): void {
if (this.globalKeyUpListener) {
window.removeEventListener('keyup', this.globalKeyUpListener);
}
}

public componentWillUpdate(): void {
createMenu();
}

public createItemFromElement(
key: string,
element: PageElement,
selectedElement?: PageElement
): ElementNodeProps {
Expand All @@ -56,7 +65,6 @@ export class ElementList extends React.Component<{}, ElementListState> {

if (!pattern) {
return {
label: key,
title: '(invalid)',
id: uuid.v4(),
children: [],
Expand All @@ -78,9 +86,9 @@ export class ElementList extends React.Component<{}, ElementListState> {
});

return {
label: key,
title: element.getName(),
dragging: this.state.dragging,
editable: element.getNameEditable(),
id: element.getId(),
children: [...slots, ...defaultSlotItems],
active: element === selectedElement && !store.getSelectedSlotId()
Expand All @@ -99,22 +107,16 @@ export class ElementList extends React.Component<{}, ElementListState> {
const selectedSlot = store.getSelectedSlotId();

slotContents.forEach((value: PageElement, index: number) => {
childItems.push(
this.createItemFromElement(
slotContents.length > 1 ? `Child ${index + 1}` : 'Child',
value,
selectedElement
)
);
childItems.push(this.createItemFromElement(value, selectedElement));
});

const slotListItem: ElementNodeProps = {
id: slot.getId(),
title: `\uD83D\uDD18 ${slot.getName()}`,
editable: false,
draggable: false,
dragging: this.state.dragging,
children: childItems,
label: slotId,
// TODO: Unify this with the event-delegation based drag/drop handling
onDragDrop: (e: React.DragEvent<HTMLElement>) => {
const patternId = e.dataTransfer.getData('patternId');
Expand Down Expand Up @@ -149,11 +151,28 @@ export class ElementList extends React.Component<{}, ElementListState> {
return slotListItem;
}

private handleBlur(e: React.FormEvent<HTMLElement>): void {
const store = Store.getInstance();
const editableElement = store.getNameEditableElement();

if (editableElement) {
store.execute(new ElementNameCommand(editableElement, editableElement.getName()));
store.setNameEditableElement();
}
}

private handleClick(e: React.MouseEvent<HTMLElement>): void {
const element = elementFromTarget(e.target);
const store = Store.getInstance();
const label = above(e.target, `[${ElementAnchors.label}]`);
e.stopPropagation();
Store.getInstance().setSelectedElement(element);
Store.getInstance().setElementFocussed(true);

if (element && store.getSelectedElement() === element && label) {
store.setNameEditableElement(element);
}

store.setSelectedElement(element);
store.setElementFocussed(true);
}

private handleContextMenu(e: React.MouseEvent<HTMLElement>): void {
Expand Down Expand Up @@ -232,6 +251,25 @@ export class ElementList extends React.Component<{}, ElementListState> {
store.setSelectedElement(draggedElement);
}

private handleKeyUp(e: KeyboardEvent): void {
const store = Store.getInstance();

if (e.keyCode === 13) {
// ENTER
e.stopPropagation();

const editableElement = store.getNameEditableElement();
const selectedElement = store.getSelectedElement();

if (editableElement) {
store.execute(new ElementNameCommand(editableElement, editableElement.getName()));
store.setNameEditableElement();
} else {
store.setNameEditableElement(selectedElement);
}
}
}

private handleMouseLeave(e: React.MouseEvent<HTMLElement>): void {
this.setState({ dragging: true });
}
Expand All @@ -255,26 +293,28 @@ export class ElementList extends React.Component<{}, ElementListState> {
}

const selectedElement = store.getSelectedElement();
const item = this.createItemFromElement('Root', rootElement, selectedElement);
const item = this.createItemFromElement(rootElement, selectedElement);

return (
<div
data-drag-root
onBlur={e => this.handleBlur(e)}
onClick={e => this.handleClick(e)}
onContextMenu={e => this.handleContextMenu(e)}
onDragStart={e => this.handleDragStart(e)}
onDragEnd={e => this.handleDragEnd(e)}
onDragStart={e => this.handleDragStart(e)}
onDrop={e => this.handleDrop(e)}
onMouseOver={e => this.handleMouseOver(e)}
onKeyUp={e => this.handleKeyUp(e.nativeEvent)}
onMouseLeave={e => this.handleMouseLeave(e)}
onMouseOver={e => this.handleMouseOver(e)}
>
<ElementTree {...item} dragging={this.state.dragging} />
</div>
);
}
}

export interface ElementNodeProps extends ListItemProps {
export interface ElementNodeProps extends ElementProps {
children?: ElementNodeProps[];
dragging: boolean;
id: string;
Expand Down
17 changes: 16 additions & 1 deletion src/component/container/element-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Element, { ElementAnchors } from '../../lsg/patterns/element';
import * as React from 'react';
import { Store } from '../../store/store';

export interface ElementWrapperState {
highlight?: boolean;
Expand All @@ -10,6 +11,7 @@ export interface ElementWrapperState {
export interface ElementWrapperProps {
active?: boolean;
dragging: boolean;
editable?: boolean;
id: string;
onClick?: React.MouseEventHandler<HTMLElement>;
onContextMenu?: React.MouseEventHandler<HTMLElement>;
Expand All @@ -24,9 +26,20 @@ export class ElementWrapper extends React.Component<ElementWrapperProps, Element
public state = {
open: this.props.open,
highlightPlaceholder: false,
highlight: false
highlight: false,
editTitle: this.props.title
};

private handleChange(e: React.FormEvent<HTMLInputElement>): void {
const target = e.target as HTMLInputElement;
const store = Store.getInstance();
const element = store.getNameEditableElement();

if (element) {
element.setName(target.value);
}
}

private handleClick(e: React.MouseEvent<HTMLElement>): void {
const target = e.target as HTMLElement;
const icon = above(target, `svg[${ElementAnchors.icon}]`);
Expand Down Expand Up @@ -97,6 +110,8 @@ export class ElementWrapper extends React.Component<ElementWrapperProps, Element
active={active}
dragging={this.props.dragging}
draggable
editable={this.props.editable}
onChange={e => this.handleChange(e)}
onClick={e => this.handleClick(e)}
onDragDrop={e => this.handleDragDrop(e)}
onDragDropForChild={e => this.handleDragDropForChild(e)}
Expand Down
31 changes: 27 additions & 4 deletions src/lsg/patterns/element/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export interface ElementProps {
active?: boolean;
draggable?: boolean;
dragging: boolean;
editable?: boolean;
highlight?: boolean;
highlightPlaceholder?: boolean;
id?: string;
onChange?: React.FormEventHandler<HTMLInputElement>;
onClick?: React.MouseEventHandler<HTMLElement>;
onContextMenu?: React.MouseEventHandler<HTMLElement>;
onDragDrop?: React.DragEventHandler<HTMLElement>;
Expand Down Expand Up @@ -64,9 +66,6 @@ const div = tag('div').omit(['active', 'highlight']);
const StyledElementLabel = styled(div)`
position: relative;
display: flex;
padding: ${getSpace(Size.XS)}px ${getSpace(Size.L)}px ${getSpace(Size.XS)}px ${getSpace(
Size.XXL
)}px;
align-items: center;
color: ${colors.grey20.toString()};
position: relative;
Expand Down Expand Up @@ -124,6 +123,26 @@ const StyledElementLabel = styled(div)`
: ''};
`;

const SeamlessInput = styled.input`
box-sizing: border-box;
width: 100%;
color: ${colors.grey20.toString()};
font-size: inherit;
line-height: inherit;
padding: ${getSpace(Size.XS - 1)}px ${getSpace(Size.L - 1)}px ${getSpace(Size.XS - 1)}px 3px;
margin: 1px 1px 1px ${getSpace(Size.XXL - 3)}px;
border: 0;
&:focus {
outline: none;
}
`;

const LabelContent = styled.div`
box-sizing: border-box;
padding: ${getSpace(Size.XS)}px ${getSpace(Size.L)}px ${getSpace(Size.XS)}px
${getSpace(Size.XXL)}px;
`;

const placeholderDiv = tag('div').omit(['highlightPlaceholder']);
const StyledPlaceholder = styled(placeholderDiv)`
position: relative;
Expand Down Expand Up @@ -237,7 +256,11 @@ const Element: React.StatelessComponent<ElementProps> = props => (
active={props.active}
/>
)}
<div>{props.title}</div>
{props.editable ? (
<SeamlessInput value={props.title} onChange={props.onChange} autoFocus />
) : (
<LabelContent>{props.title}</LabelContent>
)}
</StyledElementLabel>
{props.children && (
<StyledElementChild open={props.open}>{props.children}</StyledElementChild>
Expand Down
2 changes: 1 addition & 1 deletion src/store/command/element-name-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class ElementNameCommand extends ElementCommand {
super(element);

this.name = name;
this.previousName = element.getName();
this.previousName = element.getName({ unedited: true });

if (!this.pageId) {
throw new Error(
Expand Down
45 changes: 43 additions & 2 deletions src/store/page/page-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export class PageElement {
*/
@MobX.observable private contents: Map<string, PageElement[]> = new Map();

/**
* The currently edited name of the page element, to be commited
*/
@MobX.observable private editedName: string;

/**
* The technical (internal) ID of the page element.
*/
Expand All @@ -39,6 +44,11 @@ export class PageElement {
*/
@MobX.observable private name: string;

/**
* Wether the element name is editable
*/
@MobX.observable private nameEditable: boolean;

/**
* The page this element belongs to.
*/
Expand Down Expand Up @@ -88,6 +98,7 @@ export class PageElement {
this.pattern = properties.pattern;
this.patternId = this.pattern ? this.pattern.getId() : undefined;
this.parentSlotId = properties.parentSlotId;
this.nameEditable = false;

if (this.name === undefined && this.pattern) {
this.name = this.pattern.getName();
Expand Down Expand Up @@ -253,10 +264,22 @@ export class PageElement {
* Returns the assigned name of the page element, initially the pattern's human-friendly name.
* @return The assigned name of the page element.
*/
public getName(): string {
public getName(opts?: { unedited: boolean }): string {
if ((!opts || !opts.unedited) && this.nameEditable) {
return this.editedName;
}

return this.name;
}

/**
* Returns the editable state of the element's name
* @return The editable state
*/
public getNameEditable(): boolean {
return this.nameEditable;
}

/**
* Returns the page this element belongs to.
* @return The page this element belongs to.
Expand Down Expand Up @@ -402,7 +425,25 @@ export class PageElement {
* @param name The assigned name of the page element.
*/
public setName(name: string): void {
this.name = name;
if (this.nameEditable) {
this.editedName = name;
} else {
this.name = name;
}
}

/**
* Sets the editable state of the element's name
* @param nameEditable Wether the name is editable
*/
public setNameEditable(nameEditable: boolean): void {
if (nameEditable) {
this.editedName = this.name;
} else {
this.name = this.editedName || this.name;
}

this.nameEditable = nameEditable;
}

/**
Expand Down
Loading

0 comments on commit ac1412a

Please sign in to comment.