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

Commit

Permalink
feat: undo and redo on page operations (fixes #109)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheReincarnator committed Mar 27, 2018
1 parent 9fdad0b commit 93382b8
Show file tree
Hide file tree
Showing 18 changed files with 758 additions and 126 deletions.
59 changes: 46 additions & 13 deletions src/component/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,13 @@ interface AppProps {

@observer
class App extends React.Component<AppProps> {
private static PatternListID = 'patternlist';
private static PropertiesListID = 'propertieslist';
private static PATTERN_LIST_ID = 'patternlist';
private static PROPERTIES_LIST_ID = 'propertieslist';

@MobX.observable protected activeTab: string = App.PatternListID;
@MobX.observable protected activeTab: string = App.PATTERN_LIST_ID;
private ctrlDown: boolean = false;
@MobX.observable protected projectListVisible: boolean = false;
@MobX.computed
protected get isPatternListVisible(): boolean {
return Boolean(this.activeTab === App.PatternListID);
}
@MobX.computed
protected get isPropertiesListVisible(): boolean {
return Boolean(this.activeTab === App.PropertiesListID);
}
private shiftDown: boolean = false;

public constructor(props: AppProps) {
super(props);
Expand All @@ -69,6 +63,7 @@ class App extends React.Component<AppProps> {

public componentDidMount(): void {
createMenu(this.props.store);
this.redirectUndoRedo();
}

private getDevTools(): React.StatelessComponent | null {
Expand All @@ -92,7 +87,6 @@ class App extends React.Component<AppProps> {
}

const designkitPath = PathUtils.join(appPath, 'build', 'designkit');
console.log(`Design kit path is: ${designkitPath}`);
dialog.showOpenDialog({ properties: ['openDirectory', 'createDirectory'] }, filePaths => {
if (filePaths.length <= 0) {
return;
Expand Down Expand Up @@ -121,6 +115,45 @@ class App extends React.Component<AppProps> {
this.activeTab = id;
}

@MobX.computed
protected get isPatternListVisible(): boolean {
return Boolean(this.activeTab === App.PATTERN_LIST_ID);
}

@MobX.computed
protected get isPropertiesListVisible(): boolean {
return Boolean(this.activeTab === App.PROPERTIES_LIST_ID);
}

private redirectUndoRedo(): void {
document.body.onkeydown = event => {
if (event.keyCode === 16) {
this.shiftDown = true;
} else if (event.keyCode === 17 || event.keyCode === 91) {
this.ctrlDown = true;
} else if (this.ctrlDown && event.keyCode === 90) {
event.preventDefault();
if (this.shiftDown) {
this.props.store.redo();
} else {
this.props.store.undo();
}

return false;
}

return true;
};

document.body.onkeyup = event => {
if (event.keyCode === 16) {
this.shiftDown = false;
} else if (event.keyCode === 17 || event.keyCode === 91) {
this.ctrlDown = false;
}
};
}

public render(): JSX.Element {
// Todo: project and page don't update on page change
const project = this.props.store.getCurrentProject();
Expand Down Expand Up @@ -194,7 +227,7 @@ class App extends React.Component<AppProps> {
}
}

const store = new Store();
const store: Store = Store.getInstance();
store.openFromPreferences();

ipcRenderer.on('preview-ready', (readyEvent: {}, readyMessage: JsonObject) => {
Expand Down
40 changes: 26 additions & 14 deletions src/component/container/element-list.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { elementMenu } from '../../electron/context-menus';
import { ElementCommand } from '../../store/page/command/element-command';
import { ElementWrapper } from './element-wrapper';
import { ListItemProps } from '../../lsg/patterns/list';
import { createMenu } from '../../electron/menu';
Expand Down Expand Up @@ -68,63 +69,74 @@ export class ElementList extends React.Component<ElementListProps> {
elementMenu(this.props.store, element);
},
handleDragStart: (e: React.DragEvent<HTMLElement>) => {
this.props.store.setRearrangeElement(element);
this.props.store.setDraggedElement(element);
},
handleDragDropForChild: (e: React.DragEvent<HTMLElement>) => {
const patternId = e.dataTransfer.getData('patternId');

const parentElement = element.getParent();
let pageElement: PageElement | undefined;
const newParent = element.getParent();
let draggedElement: PageElement | undefined;

if (!patternId) {
pageElement = this.props.store.getRearrangeElement();
draggedElement = this.props.store.getDraggedElement();
} else {
const styleguide = this.props.store.getStyleguide();
if (!styleguide) {
return;
}

pageElement = new PageElement({
draggedElement = new PageElement({
pattern: styleguide.getPattern(patternId),
page: this.props.store.getCurrentPage() as Page,
setDefaults: true
});
}

if (!parentElement || !pageElement || pageElement.isAncestorOf(parentElement)) {
if (!newParent || !draggedElement || draggedElement.isAncestorOf(newParent)) {
return;
}

parentElement.addChild(pageElement, element.getIndex());
this.props.store.setSelectedElement(pageElement);
let newIndex = element.getIndex();
if (draggedElement.getParent() === newParent) {
const currentIndex = draggedElement.getIndex();
if (newIndex > currentIndex) {
newIndex--;
}
if (newIndex === currentIndex) {
return;
}
}

this.props.store.execute(ElementCommand.addChild(newParent, draggedElement, newIndex));
this.props.store.setSelectedElement(draggedElement);
},
handleDragDrop: (e: React.DragEvent<HTMLElement>) => {
const patternId = e.dataTransfer.getData('patternId');

let pageElement: PageElement | undefined;
let draggedElement: PageElement | undefined;

if (!patternId) {
pageElement = this.props.store.getRearrangeElement();
draggedElement = this.props.store.getDraggedElement();
} else {
const styleguide = this.props.store.getStyleguide();

if (!styleguide) {
return;
}

pageElement = new PageElement({
draggedElement = new PageElement({
pattern: styleguide.getPattern(patternId),
page: this.props.store.getCurrentPage() as Page,
setDefaults: true
});
}

if (!pageElement || pageElement.isAncestorOf(element)) {
if (!draggedElement || draggedElement.isAncestorOf(element)) {
return;
}

element.addChild(pageElement);
this.props.store.setSelectedElement(pageElement);
this.props.store.execute(ElementCommand.addChild(element, draggedElement));
this.props.store.setSelectedElement(draggedElement);
},
children: items,
active: element === selectedElement
Expand Down
4 changes: 2 additions & 2 deletions src/component/container/page-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Dropdown from '../../lsg/patterns/dropdown';
import { DropdownItemEditableLink } from '../../lsg/patterns/dropdown-item';
import * as MobX from 'mobx';
import { observer } from 'mobx-react';
import { PageRef } from '../../store/project/page-ref';
import { Project } from '../../store/project/project';
import { PageRef } from '../../store/page/page-ref';
import { Project } from '../../store/project';
import * as React from 'react';
import { Store } from '../../store/store';

Expand Down
3 changes: 2 additions & 1 deletion src/component/container/pattern-list.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Input from '../../lsg/patterns/input/';
import { ElementCommand } from '../../store/page/command/element-command';
import { PatternFolder } from '../../store/styleguide/folder';
import { action } from 'mobx';
import { observer } from 'mobx-react';
Expand Down Expand Up @@ -118,7 +119,7 @@ export class PatternListContainer extends React.Component<PatternListContainerPr
page: this.props.store.getCurrentPage() as Page,
setDefaults: true
});
selectedElement.addSibling(newPageElement);
this.props.store.execute(ElementCommand.addSibling(selectedElement, newPageElement));
this.props.store.setSelectedElement(newPageElement);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/component/container/project-list.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Dropdown from '../../lsg/patterns/dropdown';
import DropdownItem from '../../lsg/patterns/dropdown-item';
import { observer } from 'mobx-react';
import { Project } from '../../store/project/project';
import { Project } from '../../store/project';
import * as React from 'react';
import { Store } from '../../store/store';

Expand Down
38 changes: 19 additions & 19 deletions src/component/container/property-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { observer } from 'mobx-react';
import { ObjectProperty } from '../../store/styleguide/property/object-property';
import { PageElement } from '../../store/page/page-element';
import { PropertyValue } from '../../store/page/property-value';
import { PropertyValueCommand } from '../../store/command/property-value-command';
import * as React from 'react';
import { Store } from '../../store/store';
import { StringItem } from '../../lsg/patterns/property-items/string-item';
Expand All @@ -19,6 +20,7 @@ interface ObjectContext {
interface PropertyTreeProps {
context?: ObjectContext;
element: PageElement;
store: Store;
}

@observer
Expand All @@ -40,27 +42,18 @@ class PropertyTree extends React.Component<PropertyTreeProps> {
}

protected getValue(id: string, path?: string): PropertyValue {
if (path) {
const parts = `${path}.${id}`.split('.');
const [rootId, ...propertyPath] = parts;

return this.props.element.getPropertyValue(rootId, propertyPath.join('.'));
}

return this.props.element.getPropertyValue(id);
const fullPath = path ? `${path}.${id}` : id;
const [rootId, ...propertyPath] = fullPath.split('.');
return this.props.element.getPropertyValue(rootId, propertyPath.join('.'));
}

// tslint:disable-next-line:no-any
protected handleChange(id: string, value: any, context?: ObjectContext): void {
if (context) {
const parts = `${context.path}.${id}`.split('.');
const [rootId, ...path] = parts;

this.props.element.setPropertyValue(rootId, value, path.join('.'));
return;
}

this.props.element.setPropertyValue(id, value);
const fullPath: string = context ? `${context.path}.${id}` : id;
const [rootId, ...propertyPath] = fullPath.split('.');
this.props.store.execute(
new PropertyValueCommand(this.props.element, rootId, value, propertyPath.join('.'))
);
}

@action
Expand Down Expand Up @@ -154,7 +147,14 @@ class PropertyTree extends React.Component<PropertyTreeProps> {
property: objectProperty
};

return <PropertyTree key={id} context={newContext} element={element} />;
return (
<PropertyTree
key={id}
context={newContext}
element={element}
store={this.props.store}
/>
);

default:
return <div key={id}>Unknown type: {type}</div>;
Expand Down Expand Up @@ -182,6 +182,6 @@ export class PropertyList extends React.Component<PropertyListProps> {
return <div>No Element selected</div>;
}

return <PropertyTree element={selectedElement} />;
return <PropertyTree element={selectedElement} store={this.props.store} />;
}
}
12 changes: 6 additions & 6 deletions src/electron/context-menus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MenuItemConstructorOptions, remote } from 'electron';
import { ElementCommand } from '../store/page/command/element-command';
import { PageElement } from '../store/page/page-element';
import { Store } from '../store/store';
const { Menu } = remote;

export function elementMenu(store: Store, element: PageElement): void {
const clipboardElement: PageElement | undefined = store.getClipboardElement();
Expand All @@ -11,7 +11,7 @@ export function elementMenu(store: Store, element: PageElement): void {
label: 'Cut Element',
click: () => {
store.setClipboardElement(element);
element.remove();
store.execute(ElementCommand.remove(element));
}
},
{
Expand All @@ -23,7 +23,7 @@ export function elementMenu(store: Store, element: PageElement): void {
{
label: 'Delete element',
click: () => {
element.remove();
store.execute(ElementCommand.remove(element));
}
},
{
Expand All @@ -36,7 +36,7 @@ export function elementMenu(store: Store, element: PageElement): void {
const newPageElement = clipboardElement && clipboardElement.clone();

if (newPageElement) {
element.addSibling(newPageElement);
store.execute(ElementCommand.addSibling(element, newPageElement));
}
}
},
Expand All @@ -47,12 +47,12 @@ export function elementMenu(store: Store, element: PageElement): void {
const newPageElement = clipboardElement && clipboardElement.clone();

if (newPageElement) {
element.addChild(newPageElement);
store.execute(ElementCommand.addChild(element, newPageElement));
}
}
}
];

const menu = Menu.buildFromTemplate(template);
const menu = remote.Menu.buildFromTemplate(template);
menu.popup();
}
Loading

0 comments on commit 93382b8

Please sign in to comment.