diff --git a/src/components/add-page-button/demo.tsx b/src/components/add-page-button/demo.tsx
new file mode 100644
index 000000000..0fbdfeeca
--- /dev/null
+++ b/src/components/add-page-button/demo.tsx
@@ -0,0 +1,11 @@
+import DemoContainer from '../demo-container';
+import * as React from 'react';
+import { AddPageButton } from '.';
+
+const AddPageButtonDemo: React.StatelessComponent = (): JSX.Element => (
+
+
+
+);
+
+export default AddPageButtonDemo;
diff --git a/src/components/add-page-button/index.tsx b/src/components/add-page-button/index.tsx
new file mode 100644
index 000000000..ff361dfb1
--- /dev/null
+++ b/src/components/add-page-button/index.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+import { Color } from '../colors';
+import { Copy } from '../copy';
+import { getSpace, SpaceSize } from '../space';
+import { Icon, IconName, IconSize } from '../icons';
+import styled from 'styled-components';
+
+export interface AddPageButtonProps {
+ onClick?: React.MouseEventHandler;
+}
+
+const StyledAddPageButton = styled.button`
+ position: relative;
+ box-sizing: border-box;
+ height: 60px;
+ width: 100%;
+ border: 1px solid ${Color.Grey80};
+ border-radius: 6px;
+ background-color: transparent;
+ margin: ${getSpace(SpaceSize.S)}px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: border 0.2s;
+ user-select: none;
+
+ &:focus {
+ outline: none;
+ }
+
+ &:hover {
+ border: 1px solid ${Color.Grey60};
+ }
+`;
+
+const StyledIcon = styled(Icon)`
+ margin-right: ${getSpace(SpaceSize.XS)}px;
+`;
+
+export const AddPageButton: React.SFC = props => (
+
+
+ Add Page
+
+);
diff --git a/src/components/add-page-button/pattern.json b/src/components/add-page-button/pattern.json
new file mode 100644
index 000000000..704647c0a
--- /dev/null
+++ b/src/components/add-page-button/pattern.json
@@ -0,0 +1,8 @@
+{
+ "name": "add-page-button",
+ "description": "Button to add pages",
+ "displayName": "Add Page Button",
+ "version": "1.0.0",
+ "tags": ["atom", "button"],
+ "flag": "alpha"
+}
diff --git a/src/components/colors/index.tsx b/src/components/colors/index.tsx
index e8582b16f..f31d33270 100644
--- a/src/components/colors/index.tsx
+++ b/src/components/colors/index.tsx
@@ -5,6 +5,7 @@ export enum Color {
BlueAlpha40 = 'rgba(0, 112, 214, .4)',
Blue20 = 'rgb(51, 141, 222)',
Blue40 = 'rgb(102, 169, 230)',
+ Blue60 = 'rgb(153, 198, 239)',
Blue80 = 'rgb(212, 226, 242)',
Green = 'rgb(91, 226, 122)',
Grey20 = 'rgb(52, 61, 69)',
diff --git a/src/components/element/demo.tsx b/src/components/element/demo.tsx
index 5ef189f21..0189f0a88 100644
--- a/src/components/element/demo.tsx
+++ b/src/components/element/demo.tsx
@@ -1,5 +1,5 @@
import DemoContainer from '../demo-container';
-import { Element, ElementState } from './index';
+import { Element, ElementCapability, ElementState } from './index';
import * as React from 'react';
// tslint:disable-next-line:no-empty
@@ -9,8 +9,7 @@ const CHILD = (
= (): JSX.Element => (
= (): JSX.Element => (
Active
= (): JSX.Element => (
Highlighted
= (): JSX.Element => (
Placeholder Highlighted
= (): JSX.Element => (
Editable
= (): JSX.Element => (
May open, closed
= (): JSX.Element => (
May open, opened
= (): JSX.Element => (
With child, active and open
;
open: boolean;
placeholderHighlighted?: boolean;
@@ -45,6 +51,7 @@ interface StyledIconProps {
interface LabelContentProps {
active: boolean;
+ editable?: boolean;
}
export interface StyledElementChildProps {
@@ -178,7 +185,8 @@ const LabelContent = styled.div`
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
- cursor: ${(props: LabelContentProps) => (props.active ? 'text' : 'default')};
+ cursor: ${(props: LabelContentProps) => (props.active && props.editable ? 'text' : 'default')};
+ user-select: none;
`;
const StyledSeamlessInput = styled.input`
@@ -227,7 +235,10 @@ class SeamlessInput extends React.Component {
}
export const Element: React.StatelessComponent = props => (
-
+
{props.dragging && (
= props => (
/>
)}
- {props.mayOpen && (
+ {props.capabilities.includes(ElementCapability.Openable) && (
= props => (
active={props.state === ElementState.Active}
/>
)}
- {props.state === ElementState.Editable ? (
+ {props.state === ElementState.Editable &&
+ props.capabilities.includes(ElementCapability.Editable) ? (
= props => (
) : (
{props.title}
diff --git a/src/components/floating-button/demo.tsx b/src/components/floating-button/demo.tsx
deleted file mode 100644
index 95e4604de..000000000
--- a/src/components/floating-button/demo.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import DemoContainer from '../demo-container';
-import { Icon, IconName } from '../icons';
-import * as React from 'react';
-import { FloatingButton } from '.';
-
-const FloatingButtonDemo: React.StatelessComponent = (): JSX.Element => (
-
- } />
-
-);
-
-export default FloatingButtonDemo;
diff --git a/src/components/floating-button/index.md b/src/components/floating-button/index.md
deleted file mode 100644
index b75f790d1..000000000
--- a/src/components/floating-button/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
-[Floating Action Button](https://material.io/guidelines/components/buttons-floating-action-button.html) commonly found in Material Design.
-
-The Alva implemation limits the content to instances of [Icon](./pattern/icons).
-
-* Minimal width is 56px
-* Height directly proportional to width `1:1`
-* Centers provided [Icon](./pattern/icons) horizontally and vertically
-* Positioning is static
-
-## Usage in Alva
-
-* Primary action on page list view for adding new pages
diff --git a/src/components/floating-button/index.tsx b/src/components/floating-button/index.tsx
deleted file mode 100644
index 5cc6549fe..000000000
--- a/src/components/floating-button/index.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Color } from '../colors';
-import * as React from 'react';
-import styled from 'styled-components';
-
-export interface FloatingButtonProps {
- icon: React.ReactNode;
- onClick?: React.MouseEventHandler;
-}
-
-const StyledFloatingButton = styled.button`
- background: transparent;
- border: none;
- box-sizing: border-box;
- color: ${Color.White};
- cursor: pointer;
- margin: 0;
- min-width: 56px;
- overflow: hidden;
- padding: 0;
- position: relative;
- &::before {
- content: '';
- display: block;
- padding-top: 100%;
- background: ${Color.Blue};
- border-radius: 50%;
- }
- &:focus {
- outline: none;
- }
- &:hover {
- &::before {
- content: '';
- display: block;
- padding-top: 100%;
- background: ${Color.Blue20};
- border-radius: 50%;
- }
- }
-`;
-
-const StyledFloatingButtonContent = styled.div`
- box-sizing: border-box;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
-`;
-
-export const FloatingButton: React.SFC = props => (
-
- {props.icon}
-
-);
diff --git a/src/components/floating-button/pattern.json b/src/components/floating-button/pattern.json
deleted file mode 100644
index 65369b528..000000000
--- a/src/components/floating-button/pattern.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "floating-button",
- "description": "Always available interaction target",
- "displayName": "Floating Button",
- "version": "1.0.0",
- "tags": ["atom", "button"],
- "flag": "alpha"
-}
diff --git a/src/components/index.ts b/src/components/index.ts
index 1b43542ea..1366d58a3 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,3 +1,4 @@
+export * from './add-page-button';
export * from './bug-report';
export * from './button';
export * from './chrome';
@@ -5,7 +6,6 @@ export * from './colors';
export * from './copy';
export * from './create-select';
export * from './element';
-export * from './floating-button';
export * from './fonts';
export * from './global-styles';
export * from './headline';
@@ -15,9 +15,9 @@ export * from './layout';
export * from './link';
export * from './list';
export * from './overlay';
+export * from './page-tile';
export * from './panes';
export * from './pattern-list';
-export * from './preview-tile';
export * from './property-input';
export * from './property-item';
export * from './property-item-asset';
diff --git a/src/components/preview-tile/demo.tsx b/src/components/page-tile/demo.tsx
similarity index 84%
rename from src/components/preview-tile/demo.tsx
rename to src/components/page-tile/demo.tsx
index ecf26f074..2fdb91856 100644
--- a/src/components/preview-tile/demo.tsx
+++ b/src/components/page-tile/demo.tsx
@@ -5,14 +5,14 @@ import DemoContainer from '../demo-container';
import { Headline } from '../headline';
import { Layout } from '../layout';
import { Space, SpaceSize } from '../space';
-import { EditState, PreviewTile } from '.';
+import { EditState, PageTile } from '.';
const handleChange = (e: React.ChangeEvent): string => e.target.value;
const currentDate = new Date();
export default (): JSX.Element => (
-
+
Project Name
@@ -22,40 +22,45 @@ export default (): JSX.Element => (
-
-
-
-
- ;
onChange?: React.ChangeEventHandler;
onClick?: React.MouseEventHandler;
- onDoubleClick?: React.MouseEventHandler;
onFocus?: React.FocusEventHandler;
onKeyDown?: React.KeyboardEventHandler;
}
@@ -25,7 +25,7 @@ export interface PreviewTileProps {
interface EditableTitleProps {
autoFocus: boolean;
autoSelect: boolean;
- focused: boolean;
+ highlighted: boolean;
onBlur?: React.FocusEventHandler;
onChange?: React.ChangeEventHandler;
onClick?: React.MouseEventHandler;
@@ -35,40 +35,51 @@ interface EditableTitleProps {
value: string;
}
-interface StyledPreviewTileProps {
+interface StyledPageTileProps {
+ highlighted: boolean;
focused: boolean;
}
-interface StyledPreviewTitleProps {
+interface StyledPageTitleProps {
children: React.ReactNode;
editable: boolean;
}
-const StyledPreview = styled.section`
- width: 245px;
- text-align: center;
-`;
+const BORDER_COLOR = (props: StyledPageTileProps) => (props.focused ? Color.Blue20 : Color.Blue60);
-const StyledPreviewTile = styled.div`
+const StyledPageTile = styled.div`
position: relative;
box-sizing: border-box;
- width: inherit;
- height: 340px;
- border: 4px solid;
- border-color: ${(props: StyledPreviewTileProps) =>
- props.focused ? Color.Blue40 : 'transparent'};
+ height: 60px;
+ width: 100%;
+ border: 3px solid;
+ border-color: ${(props: StyledPageTileProps) =>
+ props.highlighted ? BORDER_COLOR : 'transparent'};
border-radius: 6px;
- box-shadow: 0 3px 12px ${Color.BlackAlpha13};
+ box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.15);
background-color: ${Color.White};
overflow: hidden;
+ margin: ${getSpace(SpaceSize.S)}px;
+ margin-bottom: 0;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: box-shadow 0.2s, color 0.2s;
+ color: ${Color.Grey20};
+
+ &:hover {
+ box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.3);
+ color: ${Color.Black};
+ }
`;
-const StyledTitle = (props: StyledPreviewTitleProps): JSX.Element => {
+const StyledTitle = (props: StyledPageTitleProps): JSX.Element => {
const Strong = styled.strong`
display: inline-block;
width: 100%;
margin: 0;
- font-size: 12px;
+ font-size: inherit;
font-weight: normal;
text-align: center;
cursor: ${props.editable ? 'text' : 'default'};
@@ -76,6 +87,8 @@ const StyledTitle = (props: StyledPreviewTitleProps): JSX.Element => {
white-space: nowrap;
text-overflow: ellipsis;
padding: 0;
+ color: inherit;
+ user-select: none;
`;
return {props.children};
};
@@ -99,15 +112,6 @@ const StyledEditableTitle = styled.input`
}
`;
-const StyledContainer = styled.div`
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- padding: ${getSpace(SpaceSize.S)}px 0;
- background: ${Color.White};
-`;
-
class EditableTitle extends React.Component {
public componentDidMount(): void {
const node = ReactDOM.findDOMNode(this);
@@ -141,26 +145,27 @@ class EditableTitle extends React.Component {
}
}
-export const PreviewTile: React.StatelessComponent = (props): JSX.Element => (
-
-
-
- {props.nameState === EditState.Editing ? (
-
- ) : (
- {props.name}
- )}
-
-
-
+export const PageTile: React.StatelessComponent = (props): JSX.Element => (
+
+ {props.nameState === EditState.Editing ? (
+
+ ) : (
+ {props.name}
+ )}
+
);
diff --git a/src/components/preview-tile/pattern.json b/src/components/page-tile/pattern.json
similarity index 63%
rename from src/components/preview-tile/pattern.json
rename to src/components/page-tile/pattern.json
index 0358f3b30..f8f5d37ae 100644
--- a/src/components/preview-tile/pattern.json
+++ b/src/components/page-tile/pattern.json
@@ -1,6 +1,6 @@
{
- "name": "preview-tile",
- "displayName": "Preview Tile",
+ "name": "page-tile",
+ "displayName": "Page Tile",
"description": "Represent a page",
"flag": "alpha",
"version": "1.0.0",
diff --git a/src/components/view-switch/index.tsx b/src/components/view-switch/index.tsx
index 42d42f919..5988eea37 100644
--- a/src/components/view-switch/index.tsx
+++ b/src/components/view-switch/index.tsx
@@ -23,6 +23,7 @@ export interface StyledViewButtonProps {
export interface ViewButtonProps {
onClick?: React.MouseEventHandler;
+ rotateIcon?: boolean;
title: string;
}
@@ -33,6 +34,7 @@ export interface ViewTitleProps {
}
interface StyledIconProps extends IconProps {
+ rotate?: boolean;
visible: boolean;
}
@@ -64,6 +66,9 @@ const StyledViewButton: any = styled.div`
&:hover {
background: ${Color.Grey90};
}
+ &:active {
+ background: ${Color.Grey80};
+ }
`;
const StyledTitle = styled.strong`
@@ -87,10 +92,15 @@ const StyledIcons = styled(Icon)`
visibility: ${(props: StyledIconProps) => (props.visible ? 'visible' : 'hidden')};
cursor: pointer;
pointer-events: auto;
+ transform: rotate(${(props: StyledIconProps) => (props.rotate ? '180deg' : '0')});
+ transition: transform 0.2s;
&:hover {
background: ${Color.Grey90};
}
+ &:active {
+ background: ${Color.Grey80};
+ }
`;
export const ViewTitle: React.SFC = (props): JSX.Element => (
@@ -105,6 +115,7 @@ export const ViewButton: React.SFC = (props): JSX.Element => (
color={Color.Grey60}
size={IconSize.XS}
name={IconName.ArrowLeft}
+ rotate={props.rotateIcon}
visible={true}
/>
{props.title}
diff --git a/src/container/app.tsx b/src/container/app.tsx
index cbd545e0b..8614268b8 100644
--- a/src/container/app.tsx
+++ b/src/container/app.tsx
@@ -19,7 +19,6 @@ import { ElementList } from './element-list';
import { ServerMessageType } from '../message';
import * as MobxReact from 'mobx-react';
import { PageListContainer } from './page-list/page-list-container';
-import { PageListPreview } from './page-list/page-list-preview';
import { PatternListContainer } from './pattern-list';
import { PreviewPaneWrapper } from './preview-pane-wrapper';
import { PropertyListContainer } from './property-list';
@@ -71,13 +70,24 @@ export class App extends React.Component {
}}
/>
)}
- {props.store.getActiveAppView() === Types.AlvaView.Pages && (
-
-
-
- )}
{props.store.getActiveAppView() === Types.AlvaView.PageDetail && (
+ {props.store.getShowPages() && (
+
+
+
+
+
+ )}
{props.store.getShowLeftSidebar() && (
)}
- {store.getActiveAppView() === Types.AlvaView.Pages && (
-
- )}
{
diff --git a/src/container/chrome/overview-switch-container.tsx b/src/container/chrome/overview-switch-container.tsx
index f1822db0c..1d844a93b 100644
--- a/src/container/chrome/overview-switch-container.tsx
+++ b/src/container/chrome/overview-switch-container.tsx
@@ -2,7 +2,6 @@ import { ViewButton } from '../../components';
import * as MobxReact from 'mobx-react';
import * as React from 'react';
import { ViewStore } from '../../store';
-import * as Types from '../../types';
@MobxReact.inject('store')
@MobxReact.observer
@@ -16,14 +15,14 @@ export class OverviewSwitchContainer extends React.Component {
return null;
}
- const title =
- store.getActiveAppView() === Types.AlvaView.Pages ? `Show "${page.getName()}"` : 'Pages';
+ const title = store.getShowPages() ? 'Hide Pages' : 'Show Pages';
- const next =
- store.getActiveAppView() === Types.AlvaView.Pages
- ? Types.AlvaView.PageDetail
- : Types.AlvaView.Pages;
-
- return store.setActiveAppView(next)} title={title} />;
+ return (
+ store.setShowPages(!store.getShowPages())}
+ rotateIcon={!store.getShowPages()}
+ />
+ );
}
}
diff --git a/src/container/element-list/element-container.tsx b/src/container/element-list/element-container.tsx
index 86649fee6..ff3c9a311 100644
--- a/src/container/element-list/element-container.tsx
+++ b/src/container/element-list/element-container.tsx
@@ -1,5 +1,6 @@
import * as AlvaUtil from '../../alva-util';
import * as Components from '../../components';
+import { ElementCapability } from '../../components';
import { ElementContentContainer } from './element-content-container';
import * as MobxReact from 'mobx-react';
import * as Model from '../../model';
@@ -28,17 +29,20 @@ export class ElementContainer extends React.Component {
return (
Boolean(item))}
dragging={store.getDragging()}
id={props.element.getId()}
- mayOpen={
- props.element.acceptsChildren() && props.element.getRole() !== ElementRole.Root
- }
open={open}
onChange={AlvaUtil.noop}
placeholderHighlighted={props.element.getPlaceholderHighlighted()}
state={getState(props.element, store)}
- title={props.element.getName()}
+ title={props.element.getRole() === ElementRole.Root ? 'Page' : props.element.getName()}
>
{open
? props.element
diff --git a/src/container/page-list/page-add-button.tsx b/src/container/page-list/page-add-button.tsx
deleted file mode 100644
index d22e941cb..000000000
--- a/src/container/page-list/page-add-button.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import * as Sender from '../../message/client';
-import { FloatingButton, Icon, IconName, Space, SpaceSize } from '../../components';
-import { ServerMessageType } from '../../message';
-import * as React from 'react';
-import styled from 'styled-components';
-import * as uuid from 'uuid';
-
-export class PageAddButton extends React.Component {
- private handleClick(e: React.MouseEvent): void {
- e.preventDefault();
- Sender.send({
- id: uuid.v4(),
- payload: undefined,
- type: ServerMessageType.CreateNewPage
- });
- }
-
- public render(): JSX.Element {
- return (
-
- }
- onClick={e => this.handleClick(e)}
- />
-
- );
- }
-}
-
-const FixedContainer = styled(Space)`
- position: fixed;
- bottom: ${SpaceSize.XXL}px;
- right: ${SpaceSize.XXXL}px;
-`;
diff --git a/src/container/page-list/page-list-container.tsx b/src/container/page-list/page-list-container.tsx
index dc41d1e19..c48319c18 100644
--- a/src/container/page-list/page-list-container.tsx
+++ b/src/container/page-list/page-list-container.tsx
@@ -1,9 +1,11 @@
-import { Layout, LayoutWrap, Space, SpaceSize } from '../../components';
+import * as Sender from '../../message/client';
+import { AddPageButton, Layout, LayoutWrap } from '../../components';
+import { ServerMessageType } from '../../message';
import * as MobxReact from 'mobx-react';
-import { PageAddButton } from './page-add-button';
import { PageTileContainer } from './page-tile-container';
import * as React from 'react';
import { ViewStore } from '../../store';
+import * as uuid from 'uuid';
export const PageListContainer: React.StatelessComponent = MobxReact.inject('store')(
MobxReact.observer((props): JSX.Element | null => {
@@ -17,20 +19,27 @@ export const PageListContainer: React.StatelessComponent = MobxReact.inject('sto
}
return (
-
-
- {project
- .getPages()
- .map((page, i) => (
-
- ))}
-
-
-
+
+ {project
+ .getPages()
+ .map(page => (
+
+ ))}
+
+ Sender.send({
+ id: uuid.v4(),
+ payload: undefined,
+ type: ServerMessageType.CreateNewPage
+ })
+ }
+ />
+
);
})
);
diff --git a/src/container/page-list/page-list-preview.tsx b/src/container/page-list/page-list-preview.tsx
deleted file mode 100644
index 2361d43c8..000000000
--- a/src/container/page-list/page-list-preview.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Color, Headline, Space, SpaceSize } from '../../components';
-import * as MobxReact from 'mobx-react';
-import * as React from 'react';
-import { ViewStore } from '../../store';
-import styled from 'styled-components';
-
-export const PageListPreview: React.StatelessComponent = MobxReact.inject('store')(
- MobxReact.observer(props => {
- const { store } = props as { store: ViewStore };
- const project = store.getProject();
- if (!project) {
- return <>props.children>;
- }
- return (
-
-
-
-
- {project.getName()}
-
-
- {props.children}
-
-
- );
- })
-);
-
-const StyledPageListPreview = styled.div`
- box-sizing: border-box;
- overflow: auto;
- width: 100%;
- min-height: 100vh;
-`;
diff --git a/src/container/page-list/page-tile-container.tsx b/src/container/page-list/page-tile-container.tsx
index 0cd244569..5776708e1 100644
--- a/src/container/page-list/page-tile-container.tsx
+++ b/src/container/page-list/page-tile-container.tsx
@@ -1,4 +1,4 @@
-import { PreviewTile, Space, SpaceSize } from '../../components';
+import { PageTile } from '../../components';
import * as MobxReact from 'mobx-react';
import { Page } from '../../model';
import * as React from 'react';
@@ -7,6 +7,7 @@ import * as Types from '../../types';
export interface PageTileContainerProps {
focused: boolean;
+ highlighted: boolean;
page: Page;
}
@@ -55,31 +56,13 @@ export class PageTileContainer extends React.Component {
const target = e.target as HTMLElement;
- if (!this.props.focused) {
- store.setActivePage(this.props.page);
- }
+ store.setActivePage(this.props.page);
- if (this.props.focused && target.matches('[data-title]')) {
+ if (this.props.highlighted && target.matches('[data-title]')) {
this.props.page.setNameState(Types.EditState.Editing);
}
}
- protected handleDoubleClick(e: React.MouseEvent): void {
- if (this.props.page.getNameState() === Types.EditState.Editing) {
- return;
- }
-
- const { store } = this.props as PageTileContainerProps & { store: ViewStore };
-
- const next =
- store.getActiveAppView() === Types.AlvaView.Pages
- ? Types.AlvaView.PageDetail
- : Types.AlvaView.Pages;
-
- store.setActivePage(this.props.page);
- store.setActiveAppView(next);
- }
-
protected handleFocus(): void {
this.props.page.setNameState(Types.EditState.Editing);
}
@@ -107,7 +90,7 @@ export class PageTileContainer extends React.Component {
}
if (
e.target === document.body &&
- this.props.focused &&
+ this.props.highlighted &&
this.props.page.getNameState() === Types.EditState.Editable
) {
this.props.page.setNameState(Types.EditState.Editing);
@@ -119,23 +102,21 @@ export class PageTileContainer extends React.Component {
public render(): JSX.Element {
const { props } = this;
return (
-
- this.handleBlur()}
- onChange={e => this.handleChange(e)}
- onClick={e => this.handleClick(e)}
- onDoubleClick={e => this.handleDoubleClick(e)}
- onFocus={e => this.handleFocus()}
- onKeyDown={e => {
- e.stopPropagation();
- this.handleKeyDown(e.nativeEvent);
- }}
- nameState={props.page.getNameState()}
- name={props.page.getName()}
- />
-
+ this.handleBlur()}
+ onChange={e => this.handleChange(e)}
+ onClick={e => this.handleClick(e)}
+ onFocus={e => this.handleFocus()}
+ onKeyDown={e => {
+ e.stopPropagation();
+ this.handleKeyDown(e.nativeEvent);
+ }}
+ nameState={props.page.getNameState()}
+ name={props.page.getName()}
+ />
);
}
}
diff --git a/src/electron/create-menu.ts b/src/electron/create-menu.ts
index daf3b2cd2..034cddfd1 100644
--- a/src/electron/create-menu.ts
+++ b/src/electron/create-menu.ts
@@ -385,7 +385,7 @@ export function createMenu(ctx: MenuContext): void {
type: 'separator'
},
{
- label: '&Show Left Sidebar',
+ label: '&Show Elements & Components',
type: 'checkbox',
checked: true,
enabled: ctx.store.getActiveAppView() === Types.AlvaView.PageDetail,
@@ -395,7 +395,7 @@ export function createMenu(ctx: MenuContext): void {
}
},
{
- label: '&Show Right Sidebar',
+ label: '&Show Properties',
type: 'checkbox',
checked: true,
enabled: ctx.store.getActiveAppView() === Types.AlvaView.PageDetail,
diff --git a/src/electron/renderer.tsx b/src/electron/renderer.tsx
index 409eeb404..827f9becb 100644
--- a/src/electron/renderer.tsx
+++ b/src/electron/renderer.tsx
@@ -9,7 +9,7 @@ import { AlvaApp, EditHistory, PatternLibrary, Project } from '../model';
import * as Path from 'path';
import * as React from 'react';
import * as ReactDom from 'react-dom';
-import { ViewStore } from '../store';
+import { FocusedItemType, ViewStore } from '../store';
import * as Types from '../types';
import * as uuid from 'uuid';
@@ -59,14 +59,10 @@ Sender.receive(message => {
newProject.setPath(message.payload.path);
store.setProject(newProject);
- const view =
- newProject.getPages().length === 0
- ? Types.AlvaView.Pages
- : Types.AlvaView.PageDetail;
+ app.setActiveView(Types.AlvaView.PageDetail);
+ store.setFocusedItem(FocusedItemType.Page, store.getCurrentPage());
+ store.commit();
- app.setActiveView(view);
- store.commit();
-
const patternLibrary = newProject.getPatternLibrary();
if (patternLibrary.getState() !== Types.PatternLibraryState.Pristine) {
@@ -126,10 +122,8 @@ Sender.receive(message => {
}
store.setActivePage(page);
+ page.setNameState(Types.EditState.Editing);
- if (app.getActiveView() === Types.AlvaView.Pages) {
- page.setNameState(Types.EditState.Editing);
- }
break;
}
case ServerMessageType.ConnectPatternLibraryResponse: {
@@ -209,10 +203,10 @@ Sender.receive(message => {
break;
}
case ServerMessageType.Cut: {
- if (app.getActiveView() === Types.AlvaView.Pages) {
+ /*if (app.getActiveView() === Types.AlvaView.Pages) {
// TODO: implement this
// store.cutSelectedPage();
- }
+ }*/
if (app.getActiveView() === Types.AlvaView.PageDetail) {
store.executeElementCutSelected();
}
@@ -223,10 +217,17 @@ Sender.receive(message => {
break;
}
case ServerMessageType.Delete: {
- if (app.getActiveView() === Types.AlvaView.Pages) {
+ if (
+ app.getActiveView() === Types.AlvaView.PageDetail &&
+ store.getFocusedItemType() === FocusedItemType.Page
+ ) {
store.executePageRemoveSelected();
}
- if (app.getActiveView() === Types.AlvaView.PageDetail) {
+
+ if (
+ app.getActiveView() === Types.AlvaView.PageDetail &&
+ store.getFocusedItemType() === FocusedItemType.Element
+ ) {
store.executeElementRemoveSelected();
}
break;
@@ -236,10 +237,10 @@ Sender.receive(message => {
break;
}
case ServerMessageType.Copy: {
- if (app.getActiveView() === Types.AlvaView.Pages) {
+ /*if (app.getActiveView() === Types.AlvaView.Pages) {
// TODO: implement this
// store.copySelectedPage();
- }
+ }*/
if (app.getActiveView() === Types.AlvaView.PageDetail) {
store.copySelectedElement();
}
@@ -250,10 +251,10 @@ Sender.receive(message => {
break;
}
case ServerMessageType.Paste: {
- if (app.getActiveView() === Types.AlvaView.Pages) {
+ /*if (app.getActiveView() === Types.AlvaView.Pages) {
// TODO: implement this
// store.pasteAfterSelectedPage();
- }
+ }*/
if (app.getActiveView() === Types.AlvaView.PageDetail) {
store.executeElementPasteAfterSelected();
}
diff --git a/src/model/alva-app.ts b/src/model/alva-app.ts
index 0edac0b41..4652ad9bb 100644
--- a/src/model/alva-app.ts
+++ b/src/model/alva-app.ts
@@ -78,8 +78,6 @@ function deserializeView(state: Types.SerializedAlvaView): Types.AlvaView {
switch (state) {
case 'SplashScreen':
return Types.AlvaView.SplashScreen;
- case 'Pages':
- return Types.AlvaView.Pages;
case 'PageDetail':
return Types.AlvaView.PageDetail;
}
@@ -100,8 +98,6 @@ function serializeView(view: Types.AlvaView): Types.SerializedAlvaView {
switch (view) {
case Types.AlvaView.SplashScreen:
return 'SplashScreen';
- case Types.AlvaView.Pages:
- return 'Pages';
case Types.AlvaView.PageDetail:
return 'PageDetail';
}
diff --git a/src/model/element/element.ts b/src/model/element/element.ts
index 099aec569..89c11cf22 100644
--- a/src/model/element/element.ts
+++ b/src/model/element/element.ts
@@ -12,6 +12,7 @@ export interface ElementInit {
containerId?: string;
contentIds: string[];
dragged: boolean;
+ focused: boolean;
forcedOpen: boolean;
highlighted: boolean;
id?: string;
@@ -39,6 +40,8 @@ export class Element {
@Mobx.observable private editedName: string;
+ @Mobx.observable private focused: boolean;
+
@Mobx.observable private forcedOpen: boolean;
@Mobx.observable private highlighted: boolean;
@@ -71,6 +74,7 @@ export class Element {
public constructor(init: ElementInit, context: ElementContext) {
this.dragged = init.dragged;
+ this.focused = init.focused;
this.highlighted = init.highlighted;
this.id = init.id ? init.id : uuid.v4();
this.patternId = init.patternId;
@@ -129,6 +133,7 @@ export class Element {
return new Element(
{
dragged: serialized.dragged,
+ focused: serialized.focused,
highlighted: serialized.highlighted,
id: serialized.id,
name: serialized.name,
@@ -173,6 +178,7 @@ export class Element {
const clone = new Element(
{
dragged: false,
+ focused: false,
highlighted: false,
id: uuid.v4(),
containerId: undefined,
@@ -313,6 +319,10 @@ export class Element {
return this.highlighted;
}
+ public getFocused(): boolean {
+ return this.focused;
+ }
+
public getId(): string {
return this.id;
}
@@ -425,6 +435,11 @@ export class Element {
this.highlighted = highlighted;
}
+ @Mobx.action
+ public setFocused(focused: boolean): void {
+ this.focused = focused;
+ }
+
@Mobx.action
public setIndex(index: number): void {
const container = this.getContainer();
@@ -553,6 +568,7 @@ export class Element {
containerId: this.containerId,
contentIds: Array.from(this.contentIds),
dragged: this.dragged,
+ focused: this.focused,
highlighted: this.highlighted,
id: this.id,
name: this.name,
diff --git a/src/model/page/page.ts b/src/model/page/page.ts
index af1cfb823..9af4e41b1 100644
--- a/src/model/page/page.ts
+++ b/src/model/page/page.ts
@@ -27,6 +27,8 @@ export interface PageContext {
export class Page {
@Mobx.observable private active: boolean;
+ @Mobx.observable private focused: boolean;
+
/**
* Intermediary edited name
*/
@@ -98,6 +100,7 @@ export class Page {
dragged: false,
open: true,
forcedOpen: false,
+ focused: false,
patternId: rootPattern.getId(),
placeholderHighlighted: false,
properties: [],
@@ -166,6 +169,10 @@ export class Page {
return rootElement.getContentById(id);
}
+ public getFocused(): boolean {
+ return this.focused;
+ }
+
public getEditedName(): string {
return this.editedName;
}
@@ -222,6 +229,11 @@ export class Page {
this.active = active;
}
+ @Mobx.action
+ public setFocused(focused: boolean): void {
+ this.focused = focused;
+ }
+
@Mobx.action
public setName(name: string): void {
if (this.nameState === Types.EditState.Editing) {
diff --git a/src/store/view-store.ts b/src/store/view-store.ts
index dedaee2b0..88b48ae7c 100644
--- a/src/store/view-store.ts
+++ b/src/store/view-store.ts
@@ -29,6 +29,11 @@ export interface ClipboardElement {
type: ClipBoardType.Element;
}
+export enum FocusedItemType {
+ Page,
+ Element
+}
+
/**
* The central entry-point for all view-related application state, managed by MobX.
* Use this object and its properties in your React components,
@@ -46,6 +51,8 @@ export class ViewStore {
private editHistory: Model.EditHistory;
+ @Mobx.observable private focusedItemType: FocusedItemType;
+
@Mobx.observable private metaDown: boolean = false;
@Mobx.observable private project: Model.Project;
@@ -54,6 +61,8 @@ export class ViewStore {
@Mobx.observable private showLeftSidebar: boolean = true;
+ @Mobx.observable private showPages: boolean = true;
+
@Mobx.observable private showRightSidebar: boolean = true;
@Mobx.observable private serverPort: number;
@@ -161,6 +170,7 @@ export class ViewStore {
{
contentIds: elementContents.map(e => e.getId()),
dragged: init.dragged || false,
+ focused: false,
highlighted: false,
forcedOpen: false,
open: false,
@@ -591,6 +601,24 @@ export class ViewStore {
return this.project.getElements().find(element => element.getHighlighted());
}
+ public getFocusedItem(): Model.Element | Model.Page | undefined {
+ if (!this.project) {
+ return;
+ }
+
+ if (this.focusedItemType === FocusedItemType.Element) {
+ return this.project.getElements().find(element => element.getFocused());
+ } else if (this.focusedItemType === FocusedItemType.Page) {
+ return this.project.getPages().find(page => page.getFocused());
+ } else {
+ return undefined;
+ }
+ }
+
+ public getFocusedItemType(): FocusedItemType {
+ return this.focusedItemType;
+ }
+
public getMetaDown(): boolean {
return this.metaDown;
}
@@ -680,9 +708,9 @@ export class ViewStore {
return this.hasClipboardItem(ClipBoardType.Element);
}
- if (view === Types.AlvaView.Pages) {
+ /*if (view === Types.AlvaView.Pages) {
return this.hasClipboardItem(ClipBoardType.Element);
- }
+ }*/
return false;
}
@@ -821,14 +849,15 @@ export class ViewStore {
public removePage(page: Model.Page): void {
const index = this.project.getPageIndex(page);
- if (index === 0) {
- this.unsetActivePage();
- this.setActiveAppView(Types.AlvaView.Pages);
- } else {
- this.setActivePageByIndex(index - 1);
- }
+ if (this.project.getPages().length > 1) {
+ if (index !== 0) {
+ this.setActivePageByIndex(index - 1);
+ } else {
+ this.setActivePageByIndex(index + 1);
+ }
- this.project.removePage(page);
+ this.project.removePage(page);
+ }
}
@Mobx.action
@@ -873,6 +902,14 @@ export class ViewStore {
this.showRightSidebar = show;
}
+ public getShowPages(): boolean {
+ return this.showPages;
+ }
+
+ public setShowPages(show: boolean): void {
+ this.showPages = show;
+ }
+
@Mobx.action
public setActiveAppView(appView: Types.AlvaView): void {
this.app.setActiveView(appView);
@@ -887,6 +924,11 @@ export class ViewStore {
this.unsetActivePage();
page.setActive(true);
+ this.setFocusedItem(FocusedItemType.Page, page);
+
+ this.unsetSelectedElement();
+
+ console.log('setActivePage');
}
@Mobx.action
@@ -963,6 +1005,22 @@ export class ViewStore {
});
}
+ @Mobx.action
+ public setFocusedItem(
+ type: FocusedItemType,
+ payload: Model.Element | Model.Page | undefined
+ ): void {
+ const previousFocusItem = this.getFocusedItem();
+
+ if (previousFocusItem) {
+ previousFocusItem.setFocused(false);
+ }
+ this.focusedItemType = type;
+ if (payload) {
+ payload.setFocused(true);
+ }
+ }
+
@Mobx.action
public setMetaDown(metaDown: boolean): void {
this.metaDown = metaDown;
@@ -1018,6 +1076,7 @@ export class ViewStore {
}
selectedElement.setSelected(true);
+ this.setFocusedItem(FocusedItemType.Element, selectedElement);
selectedElement.getAncestors().forEach(ancestor => {
ancestor.setForcedOpen(true);
diff --git a/src/types/types.ts b/src/types/types.ts
index 93d13a4eb..8ac6211f8 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -36,6 +36,7 @@ export interface SerializedElement {
containerId?: string;
contentIds: string[];
dragged: boolean;
+ focused: boolean;
forcedOpen: boolean;
highlighted: boolean;
id: string;
@@ -124,12 +125,11 @@ export interface SerializedPatternSlot {
}
export enum AlvaView {
- Pages = 'Pages',
PageDetail = 'PageDetail',
SplashScreen = 'SplashScreen'
}
-export type SerializedAlvaView = 'Pages' | 'PageDetail' | 'SplashScreen';
+export type SerializedAlvaView = 'PageDetail' | 'SplashScreen';
export enum EditState {
Editable = 'Editable',