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

Commit

Permalink
feat: allow users to add new pages (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
marionebl authored and tilmx committed May 5, 2018
1 parent 0aa1953 commit ff975d6
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 94 deletions.
34 changes: 34 additions & 0 deletions src/component/page-list/page-add-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FloatingButton } from '../../lsg/patterns/floating-button';
import { Icon, IconName } from '../../lsg/patterns/icons';
import { EditState } from '../../store/page/page-ref';
import * as React from 'react';
import Space, { SpaceSize } from '../../lsg/patterns/space';
import { Store } from '../../store/store';
import styled from 'styled-components';

export class PageAddButton extends React.Component {
private handleClick(e: React.MouseEvent<HTMLElement>): void {
e.preventDefault();
const store = Store.getInstance();
const page = store.addNewPageRef();
store.openPage(page.getId());
page.setNameState(EditState.Editing);
}

public render(): JSX.Element {
return (
<FixedContainer>
<FloatingButton
icon={<Icon name={IconName.Plus} />}
onClick={e => this.handleClick(e)}
/>
</FixedContainer>
);
}
}

const FixedContainer = styled(Space)`
position: fixed;
bottom: ${SpaceSize.XXL}px;
right: ${SpaceSize.XXXL}px;
`;
2 changes: 2 additions & 0 deletions src/component/page-list/page-list-container.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Layout, { LayoutWrap } from '../../lsg/patterns/layout';
import { observer } from 'mobx-react';
import { PageAddButton } from './page-add-button';
import { PageRef } from '../../store/page/page-ref';
import { PageTileContainer } from './page-tile-container';
import * as React from 'react';
Expand Down Expand Up @@ -28,6 +29,7 @@ export const PageListContainer: React.StatelessComponent = observer((): JSX.Elem
page={pageRef}
/>
))}
<PageAddButton />
</Layout>
</Space>
);
Expand Down
30 changes: 20 additions & 10 deletions src/component/page-list/page-list-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Copy from '../../lsg/patterns/copy';
import { Headline } from '../../lsg/patterns/headline';
import Space, { SpaceSize } from '../../lsg/patterns/space';
import { Store } from '../../store/store';
import styled from 'styled-components';

export const PageListPreview: React.StatelessComponent = props => {
const project = Store.getInstance().getCurrentProject();
Expand All @@ -13,16 +14,25 @@ export const PageListPreview: React.StatelessComponent = props => {
}
const dateString = new Intl.DateTimeFormat().format(project.getLastChangedDate());
return (
<Space size={[SpaceSize.XXL * 3]}>
<Space size={[SpaceSize.S, SpaceSize.S, SpaceSize.XXXL]}>
<Headline order={1} tagName="h1" textColor={colors.grey20}>
{project.getName()}
</Headline>
<Copy textColor={colors.grey60}>
Last change: {dateString} by {project.getLastChangedAuthor()}
</Copy>
<StyledPageListPreview>
<Space size={[SpaceSize.XXL * 3]}>
<Space size={[SpaceSize.S, SpaceSize.S, SpaceSize.XXXL]}>
<Headline order={1} tagName="h1" textColor={colors.grey20}>
{project.getName()}
</Headline>
<Copy textColor={colors.grey60}>
Last change: {dateString} by {project.getLastChangedAuthor()}
</Copy>
</Space>
{props.children}
</Space>
{props.children}
</Space>
</StyledPageListPreview>
);
};

const StyledPageListPreview = styled.div`
box-sizing: border-box;
overflow: auto;
width: 100%;
min-height: 100vh;
`;
107 changes: 55 additions & 52 deletions src/component/page-list/page-tile-container.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { PreviewTile } from '../../lsg/patterns/preview-tile/index';
import Space, { SpaceSize } from '../../lsg/patterns/space/index';
import * as MobX from 'mobx';
import { observer } from 'mobx-react';
import { PageRef } from '../../store/page/page-ref';
import { EditState, PageRef } from '../../store/page/page-ref';
import * as React from 'react';
import { AlvaView, Store } from '../../store/store';

Expand All @@ -13,108 +12,112 @@ export interface PageTileContainerProps {

@observer
export class PageTileContainer extends React.Component<PageTileContainerProps> {
@MobX.observable public editable: boolean = false;
@MobX.observable public editing: boolean = false;
@MobX.observable public inputValue: string = '';
private onKeyDown: (e: KeyboardEvent) => void;

@MobX.computed
public get namedPage(): boolean {
return Boolean(this.props.page.getName());
public componentDidMount(): void {
this.onKeyDown = e => this.handleKeyDown(e);
document.addEventListener('keydown', this.onKeyDown);
}

@MobX.action
protected handleBlur(): void {
this.editing = false;
public componentWillUnmount(): void {
if (this.onKeyDown) {
document.removeEventListener('keydown', this.onKeyDown);
}
}

if (!this.inputValue) {
this.inputValue = this.props.page.getName();
this.editable = false;
protected handleBlur(): void {
if (!this.props.page.getName()) {
this.props.page.setName(this.props.page.getName({ unedited: true }));
this.props.page.setNameState(EditState.Editable);
return;
}

this.props.page.setName(this.inputValue);
this.props.page.setNameState(EditState.Editable);
this.props.page.setName(this.props.page.getEditedName());
Store.getInstance().save();
this.editable = false;
}

@MobX.action
protected handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
this.inputValue = e.target.value;
this.props.page.setName(e.target.value);
}

@MobX.action
protected handleClick(e: React.MouseEvent<HTMLElement>): void {
const store = Store.getInstance();
store.openPage(this.props.page.getId());

const target = e.target as HTMLElement;

if (!this.props.focused) {
store.openPage(this.props.page.getId());
}

if (this.props.focused && target.matches('[data-title]')) {
this.inputValue = this.props.page.getName();
this.editable = true;
this.props.page.setNameState(EditState.Editing);
}
}

@MobX.action
protected handleDoubleClick(e: React.MouseEvent<HTMLElement>): void {
if (this.editing) {
if (this.props.page.getNameState() === EditState.Editing) {
return;
}

const store = Store.getInstance();
const next = store.getActiveView() === AlvaView.Pages ? AlvaView.PageDetail : AlvaView.Pages;

store.setActiveView(next);
store.openPage(this.props.page.getId());
store.setActiveView(next);
}

@MobX.action
protected handleFocus(): void {
setTimeout(() => {
this.editing = true;
}, 300);
this.props.page.setNameState(EditState.Editing);
}

@MobX.action
protected handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {
protected handleKeyDown(e: KeyboardEvent): void {
switch (e.key) {
case 'Escape':
this.inputValue = this.props.page.getName();
this.editable = false;
break;

this.props.page.setNameState(EditState.Editable);
this.props.page.setName(this.props.page.getName({ unedited: true }));
return;
case 'Enter':
if (!this.inputValue) {
this.inputValue = this.props.page.getName();
this.editable = false;
if (this.props.page.getNameState() === EditState.Editing) {
if (!this.props.page.getName()) {
this.props.page.setName(this.props.page.getName({ unedited: true }));
this.props.page.setNameState(EditState.Editable);
return;
}

this.props.page.setNameState(EditState.Editable);
this.props.page.setName(this.props.page.getEditedName());
return;
}
if (
e.target === document.body &&
this.props.focused &&
this.props.page.getNameState() === EditState.Editable
) {
this.props.page.setNameState(EditState.Editing);
return;
}

this.props.page.setName(this.inputValue);
Store.getInstance().save();
this.editable = false;
break;

default:
return;
}
}

public render(): JSX.Element {
const { props } = this;
return (
<Space size={SpaceSize.S}>
<PreviewTile
editable={this.editable}
focused={this.props.focused}
id={this.props.page.getId()}
focused={props.focused}
id={props.page.getId()}
onBlur={e => this.handleBlur()}
onChange={e => this.handleChange(e)}
onClick={e => this.handleClick(e)}
onDoubleClick={e => this.handleDoubleClick(e)}
onFocus={e => this.handleFocus()}
onKeyDown={e => this.handleKeyDown(e)}
named={this.namedPage}
value={this.editable ? this.inputValue : this.props.page.getName()}
onKeyDown={e => {
e.stopPropagation();
this.handleKeyDown(e.nativeEvent);
}}
nameState={props.page.getNameState()}
name={props.page.getName()}
/>
</Space>
);
Expand Down
3 changes: 3 additions & 0 deletions src/lsg/patterns/floating-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const StyledFloatingButton = styled.button`
background: ${colors.blue.toString()};
border-radius: 50%;
}
&:focus {
outline: none;
}
&:hover {
&::before {
content: '';
Expand Down
27 changes: 11 additions & 16 deletions src/lsg/patterns/preview-tile/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import * as React from 'react';

import Copy from '../copy';
import { Headline } from '../headline';
import { PreviewTile } from './index';
import Layout from '../layout';
import Space, { SpaceSize } from '../space';
import { EditState, PreviewTile } from '.';

const handleChange = (e: React.ChangeEvent<HTMLInputElement>): string => e.target.value;

Expand All @@ -21,47 +21,42 @@ export const DemoPreviewTile = (): JSX.Element => (
<Layout>
<Space size={SpaceSize.S}>
<PreviewTile
editable={false}
focused={false}
onChange={handleChange}
named={true}
value="Editable"
name="Editable"
nameState={EditState.Editable}
/>
</Space>
<Space size={SpaceSize.S}>
<PreviewTile
editable={false}
focused={true}
onChange={handleChange}
named={true}
value="Page Name"
name="Page Name"
nameState={EditState.Editable}
/>
</Space>
<Space size={SpaceSize.S}>
<PreviewTile
editable={true}
focused={true}
onChange={handleChange}
named={true}
value="Editable Page Name"
name="Editable Page Name"
nameState={EditState.Editable}
/>
</Space>
<Space size={SpaceSize.S}>
<PreviewTile
editable={false}
focused={false}
onChange={handleChange}
named={true}
value="Editable Page Name"
name="Editable Page Name"
nameState={EditState.Editable}
/>
</Space>
<Space size={SpaceSize.S}>
<PreviewTile
editable={false}
focused={false}
onChange={handleChange}
named={true}
value="Editable Page Name"
name="Editable Page Name"
nameState={EditState.Editable}
/>
</Space>
</Layout>
Expand Down
Loading

0 comments on commit ff975d6

Please sign in to comment.