Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breadcrumb navigation #112

Merged
merged 2 commits into from
Jan 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/src/core/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const actions = {
restoreJsonNotepadAndLoadNote: actionCreator<RestoreJsonNotepadAndLoadNoteAction>('PARSE_JSON_NOTEPAD_AND_LOAD_NOTE'),
newNotepad: actionCreator<FlatNotepad>('NEW_NOTEPAD'),
flipFullScreenState: actionCreator<void>('FLIP_FULL_SCREEN'),
exitFullScreen: actionCreator<void>('EXIT_FULL_SCREEN'),
openBreadcrumb: actionCreator<string>('OPEN_BREADCRUMB'),
deleteNotepad: actionCreator<string>('DELETE_NOTEPAD'),
exportNotepad: actionCreator<void>('EXPORT_NOTEPAD'),
expandSection: actionCreator<string>('OPEN_SECTION'),
Expand Down Expand Up @@ -91,5 +93,6 @@ export const actions = {
selectTheme: actionCreator<ThemeName>('SELECT_THEME'),
moveNotepadObject: actionCreator<MoveNotepadObjectAction>('MOVE_NOTEPAD_OBJECT'),
quickMarkdownInsert: actionCreator<void>('QUICK_MARKDOWN_INSERT'),
quickNotepad: actionCreator<void>('QUICK_NOTEPAD')
quickNotepad: actionCreator<void>('QUICK_NOTEPAD'),
flashExplorer: actionCreator<void>('FLASH_EXPLORER')
};
50 changes: 47 additions & 3 deletions app/src/core/epics/ExplorerEpics.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { combineEpics } from 'redux-observable';
import { filter, map } from 'rxjs/operators';
import { concatMap, filter, map, startWith, tap } from 'rxjs/operators';
import { Action, isType } from 'redux-typescript-actions';
import { actions } from '../actions';
import { INotepadStoreState } from '../types/NotepadTypes';
import { isAction } from '../../react-web/util';
import { NewNotepadObjectAction } from '../types/ActionTypes';
import { IStoreState } from '../types';
import { FlatNotepad } from 'upad-parse/dist';
import { FlatNotepad, Note } from 'upad-parse/dist';
import { FlatSection } from 'upad-parse/dist/FlatNotepad';
import { Observable } from 'rxjs';
import { Store } from 'redux';
import { ThemeValues } from '../../react-web/ThemeValues';

export namespace ExplorerEpics {
export const expandAll$ = (action$, store) =>
Expand Down Expand Up @@ -36,8 +39,49 @@ export namespace ExplorerEpics {
map((newSection: FlatSection) => actions.expandSection(newSection.internalRef))
);

export const openBreadcrumb$ = (action$: Observable<Action<string>>, store: Store<IStoreState>) =>
action$.pipe(
isAction(actions.openBreadcrumb),
map(action => action.payload),
filter(() => !!store.getState().notepads.notepad && !!store.getState().notepads.notepad!.item),
map((ref: string) =>
store.getState().notepads.notepad!.item!.notes[ref]
|| store.getState().notepads.notepad!.item!.sections[ref]
),
map((notepadObj: FlatSection | Note) => {
const notepad = store.getState().notepads.notepad!.item!;
return [...notepad.pathFrom(notepadObj).slice(1), notepadObj];
}),
concatMap((path: Array<FlatSection | Note>) =>
[
actions.exitFullScreen(),
actions.collapseAllExplorer(),
...path
.filter(obj => !(obj as Note).parent)
.map(section => actions.expandSection(section.internalRef)),
actions.flashExplorer()
]
),
tap(a => console.log(a))
);

export const flashExplorer$ = (action$: Observable<Action<void>>, store: Store<IStoreState>) =>
action$.pipe(
isAction(actions.flashExplorer),
tap(() => {
const theme = ThemeValues[store.getState().app.theme];
const explorer = document.getElementById('notepad-explorer')!;

explorer.style.backgroundColor = theme.accent;
setTimeout(() => explorer.style.backgroundColor = theme.chrome, 150);
}),
filter(() => false)
);

export const explorerEpics$ = combineEpics(
expandAll$,
autoLoadNewSection$
autoLoadNewSection$,
openBreadcrumb$,
flashExplorer$
);
}
5 changes: 5 additions & 0 deletions app/src/core/reducers/AppReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export class AppReducer extends MicroPadReducer<IAppStoreState> {
...state,
isFullScreen: !state.isFullScreen
};
} else if (isType(action, actions.exitFullScreen)) {
return {
...state,
isFullScreen: false
};
} else if (isType(action, actions.updateDefaultFontSize)) {
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as React from 'react';
import { CSSProperties } from 'react';
import './NotepadBreadcrumbsComponent.css';
import { Breadcrumb, MenuItem } from 'react-materialize';
import { Breadcrumb as MaterialBreadcrumb, MenuItem, Dropdown, NavItem } from 'react-materialize';
import { generateGuid } from '../../../util';
import { ThemeName } from '../../../../core/types/Themes';

export type Breadcrumb = {
text: string;
ref?: string;
};

export interface INotepadBreadcrumbsProps {
themeName: ThemeName;
breadcrumbs: string[];
breadcrumbs: Breadcrumb[];
noteTime?: string;
focusItem?: (ref?: string) => void;
}

export default class NotepadBreadcrumbsComponent extends React.Component<INotepadBreadcrumbsProps> {
Expand All @@ -19,7 +25,7 @@ export default class NotepadBreadcrumbsComponent extends React.Component<INotepa
};

render() {
const { themeName, breadcrumbs, noteTime } = this.props;
const { themeName, breadcrumbs, noteTime, focusItem } = this.props;

const breadcrumbStyle: CSSProperties = {
position: 'fixed',
Expand All @@ -28,18 +34,18 @@ export default class NotepadBreadcrumbsComponent extends React.Component<INotepa
};

const crumbs: JSX.Element[] = [];
(breadcrumbs || []).forEach((title: string, i: number) =>
(breadcrumbs || []).forEach((crumb: Breadcrumb, i: number) =>
crumbs.push(
<MenuItem key={generateGuid()}>
{title} {i === breadcrumbs.length - 1 && !!noteTime && <span style={this.timeStyle}>{noteTime}</span>}
<MenuItem key={generateGuid()} href={!!crumb.ref ? '#!' : undefined} onClick={() => !!focusItem && focusItem(crumb.ref)}>
{crumb.text} {i === breadcrumbs.length - 1 && !!noteTime && <span style={this.timeStyle}>{noteTime}</span>}
</MenuItem>
));

return (
<div id="breadcrumb-holder" style={breadcrumbStyle}>
<Breadcrumb className={themeName}>
<MaterialBreadcrumb className={themeName}>
{crumbs}
</Breadcrumb>
</MaterialBreadcrumb>
</div>
);
}
Expand Down
39 changes: 31 additions & 8 deletions app/src/react-web/containers/header/NotepadBreadcrumbsContainer.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
import { connect } from 'react-redux';
import { IStoreState } from '../../../core/types';
import NotepadBreadcrumbsComponent, { INotepadBreadcrumbsProps } from '../../components/header/NotepadBreadcrumbsComponent/NotepadBreadcrumbsComponent';
import NotepadBreadcrumbsComponent, {
Breadcrumb,
INotepadBreadcrumbsProps
} from '../../components/header/NotepadBreadcrumbsComponent/NotepadBreadcrumbsComponent';
import { INotepadStoreState } from '../../../core/types/NotepadTypes';
import { format } from 'date-fns';
import { FlatNotepad } from 'upad-parse/dist';
import { FlatSection } from 'upad-parse/dist/FlatNotepad';
import { Action, Dispatch } from 'redux';
import { actions } from '../../../core/actions';

export function mapStateToProps({ notepads, currentNote, app }: IStoreState): INotepadBreadcrumbsProps {
let breadcrumbs: string[] = [];
let breadcrumbs: Breadcrumb[] = [];
let time: string | undefined = undefined;

const makeCrumb = (title: string): Breadcrumb => ({ text: title });

if (currentNote.ref.length === 0) {
breadcrumbs.push(((notepads.notepad || <INotepadStoreState> {}).item || <FlatNotepad> {}).title
|| 'Create a quick notebook below, or open/create a notebook using the drop-down/sidebar to start');
breadcrumbs.push(
makeCrumb(
((notepads.notepad || <INotepadStoreState> {}).item || <FlatNotepad> {}).title
|| 'Create a quick notebook below, or open/create a notebook using the drop-down/sidebar to start'
)
);
} else {
const note = notepads.notepad!.item!.notes[currentNote.ref];
if (!note) return { themeName: app.theme, breadcrumbs: ['Error loading note'] };
if (!note) return { themeName: app.theme, breadcrumbs: [{ text: 'Error loading note' }] };

// Get parent list up the tree
breadcrumbs = [
...notepads.notepad!.item!.pathFrom(note).map(parent => parent.title),
note.title
...notepads.notepad!.item!.pathFrom(note).map(parent => ({
text: parent.title,
ref: (parent as FlatSection).internalRef
})),
{ text: note.title, ref: note.internalRef }
];

if (breadcrumbs.length > 1) {
Expand All @@ -34,4 +49,12 @@ export function mapStateToProps({ notepads, currentNote, app }: IStoreState): IN
};
}

export default connect<INotepadBreadcrumbsProps>(mapStateToProps)(NotepadBreadcrumbsComponent);
function mapDispatchToProps(dispatch: Dispatch<Action>): Partial<INotepadBreadcrumbsProps> {
return {
focusItem: ref => {
if (!!ref) dispatch(actions.openBreadcrumb(ref));
}
};
}

export default connect<INotepadBreadcrumbsProps>(mapStateToProps, mapDispatchToProps)(NotepadBreadcrumbsComponent);