diff --git a/packages/xod-client-electron/src/app/arduinoActions.js b/packages/xod-client-electron/src/app/arduinoActions.js
index 197c5280..cbc9019d 100644
--- a/packages/xod-client-electron/src/app/arduinoActions.js
+++ b/packages/xod-client-electron/src/app/arduinoActions.js
@@ -323,8 +323,10 @@ export const startDebugSessionHandler = (storeFn, onCloseCb) => (event, { port }
const intervalId = setInterval(
() => {
- event.sender.send(EVENTS.DEBUG_SESSION, messageCollector);
- messageCollector = [];
+ if (messageCollector.length > 0) {
+ event.sender.send(EVENTS.DEBUG_SESSION, messageCollector);
+ messageCollector = [];
+ }
},
throttleDelay
);
diff --git a/packages/xod-client-electron/src/view/containers/App.jsx b/packages/xod-client-electron/src/view/containers/App.jsx
index dd6c34f2..b94a0187 100644
--- a/packages/xod-client-electron/src/view/containers/App.jsx
+++ b/packages/xod-client-electron/src/view/containers/App.jsx
@@ -205,7 +205,11 @@ class App extends client.App {
foldEither(
error => this.props.actions.addError(error.message),
(nodeIdsMap) => {
- this.props.actions.startDebuggerSession(createSystemMessage('Debug session started'), nodeIdsMap);
+ this.props.actions.startDebuggerSession(
+ createSystemMessage('Debug session started'),
+ nodeIdsMap,
+ this.props.currentPatchPath
+ );
debuggerIPC.sendStartDebuggerSession(ipcRenderer, port);
},
R.map(getNodeIdsMap, eitherTProject)
diff --git a/packages/xod-client/src/core/styles/abstracts/variables.scss b/packages/xod-client/src/core/styles/abstracts/variables.scss
index 717cd008..e63d395a 100644
--- a/packages/xod-client/src/core/styles/abstracts/variables.scss
+++ b/packages/xod-client/src/core/styles/abstracts/variables.scss
@@ -48,7 +48,7 @@ $color-tabs-unactive-background: #2b2b2b;
$color-tabs-hover-background: #444444;
$color-tabs-active-background: $color-canvas-background;
$color-tabs-unactive-text: #cccccc;
-$color-tabs-active-text: #4ed5ed;
+$color-tabs-active-text: $color-canvas-selected;
$color-tabs-hover-text: #FFF;
$color-tabs-debugger-background: #3d4931;
diff --git a/packages/xod-client/src/core/styles/components/Breadcrumbs.scss b/packages/xod-client/src/core/styles/components/Breadcrumbs.scss
new file mode 100644
index 00000000..848ddc11
--- /dev/null
+++ b/packages/xod-client/src/core/styles/components/Breadcrumbs.scss
@@ -0,0 +1,76 @@
+.Breadcrumbs {
+ z-index: 9;
+ position: absolute;
+ top: 30px;
+ left: 0;
+ right: 0;
+
+ display: block;
+ list-style: none;
+ box-sizing: border-box;
+ padding: 1px;
+ margin: 0;
+
+ background: $color-tabs-unactive-background;
+ border-top: 1px solid #3e3e3e;
+ border-bottom: 1px solid #3e3e3e;
+
+ li {
+ display: inline-block;
+ padding: 0;
+ margin: 0 7px 0 0;
+ }
+
+ li:nth-child(1) button:before { display: none; }
+
+ button {
+ &:after, &:before {
+ display: block;
+ content: '';
+ position: absolute;
+ top: 0;
+ }
+ &:before {
+ border-top: 12px solid $sidebar-color-bg-even;
+ border-bottom: 12px solid $sidebar-color-bg-even;
+ border-left: 6px solid transparent;
+ border-right: 0;
+ left: -6px;
+ }
+ &:after {
+ border-top: 12px solid transparent;
+ border-bottom: 12px solid transparent;
+ border-left: 6px solid $sidebar-color-bg-even;
+ border-right: 0;
+ right: -6px;
+ }
+
+ position: relative;
+ display: inline-block;
+ background: $sidebar-color-bg-even;
+ border: none;
+ color: $sidebar-color-bg-tabs-selected;
+ outline: none;
+
+ height: 24px;
+ padding: 0 8px;
+
+ &:hover {
+ background: $input-option-highlighted;
+ &:before {
+ border-top-color: $input-option-highlighted;
+ border-bottom-color: $input-option-highlighted;
+ }
+ &:after {
+ border-left-color: $input-option-highlighted;
+ }
+ }
+
+ &.is-active {
+ color: $color-canvas-selected;
+ }
+ &.is-tail {
+ color: $button-border-color;
+ }
+ }
+}
diff --git a/packages/xod-client/src/core/styles/components/Debugger.scss b/packages/xod-client/src/core/styles/components/Debugger.scss
index 252bd871..18a46218 100644
--- a/packages/xod-client/src/core/styles/components/Debugger.scss
+++ b/packages/xod-client/src/core/styles/components/Debugger.scss
@@ -1,8 +1,8 @@
.debug-session-stop-button {
z-index: 10;
position: absolute;
- top: 50px;
- right: 20px;
+ top: 64px;
+ right: 6px;
padding: 8px 15px 10px 15px;
diff --git a/packages/xod-client/src/core/styles/main.scss b/packages/xod-client/src/core/styles/main.scss
index ee91eebd..3b0768d5 100644
--- a/packages/xod-client/src/core/styles/main.scss
+++ b/packages/xod-client/src/core/styles/main.scss
@@ -15,9 +15,10 @@
@import
'components/BackgroundLayer',
'components/Button',
+ 'components/Breadcrumbs',
'components/Comment',
- 'components/Inspector',
'components/Debugger',
+ 'components/Inspector',
'components/Helpbar',
'components/Link',
'components/Modals',
diff --git a/packages/xod-client/src/debugger/actionTypes.js b/packages/xod-client/src/debugger/actionTypes.js
index 639fdb0b..4a7c4c73 100644
--- a/packages/xod-client/src/debugger/actionTypes.js
+++ b/packages/xod-client/src/debugger/actionTypes.js
@@ -7,3 +7,5 @@ export const DEBUGGER_LOG_CLEAR = 'DEBUGGER_LOG_CLEAR';
export const DEBUG_SESSION_STARTED = 'DEBUG_SESSION_STARTED';
export const DEBUG_SESSION_STOPPED = 'DEBUG_SESSION_STOPPED';
+
+export const DEBUG_DRILL_DOWN = 'DEBUG_DRILL_DOWN';
diff --git a/packages/xod-client/src/debugger/actions.js b/packages/xod-client/src/debugger/actions.js
index 463d627e..71de6fdf 100644
--- a/packages/xod-client/src/debugger/actions.js
+++ b/packages/xod-client/src/debugger/actions.js
@@ -21,11 +21,12 @@ export const clearDebuggerLog = () => ({
type: AT.DEBUGGER_LOG_CLEAR,
});
-export const startDebuggerSession = (message, nodeIdsMap) => ({
+export const startDebuggerSession = (message, nodeIdsMap, currentPatchPath) => ({
type: AT.DEBUG_SESSION_STARTED,
payload: {
message,
nodeIdsMap,
+ patchPath: currentPatchPath,
},
});
@@ -35,3 +36,11 @@ export const stopDebuggerSession = message => ({
message,
},
});
+
+export const drillDown = (patchPath, nodeId) => ({
+ type: AT.DEBUG_DRILL_DOWN,
+ payload: {
+ patchPath,
+ nodeId,
+ },
+});
diff --git a/packages/xod-client/src/debugger/containers/Breadcrumbs.jsx b/packages/xod-client/src/debugger/containers/Breadcrumbs.jsx
new file mode 100644
index 00000000..f742d985
--- /dev/null
+++ b/packages/xod-client/src/debugger/containers/Breadcrumbs.jsx
@@ -0,0 +1,49 @@
+import R from 'ramda';
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { drillDown } from '../actions';
+import { getRenerableBreadcrumbChunks, getBreadcrumbActiveIndex } from '../../editor/selectors';
+
+const Breadcrumbs = ({ chunks, activeIndex, actions }) => (
+
+ {chunks.map((chunk, i) => {
+ const cls = classNames('Breadcrumbs-chunk-button', {
+ 'is-active': (i === activeIndex),
+ 'is-tail': (i > activeIndex),
+ });
+ return (
+ -
+
+
+ );
+ })}
+
+);
+
+Breadcrumbs.propTypes = {
+ chunks: PropTypes.arrayOf(PropTypes.object),
+ activeIndex: PropTypes.number,
+ actions: PropTypes.objectOf(PropTypes.func),
+};
+
+const mapStateToProps = R.applySpec({
+ chunks: getRenerableBreadcrumbChunks,
+ activeIndex: getBreadcrumbActiveIndex,
+});
+
+const mapDispatchToProps = dispatch => ({
+ actions: bindActionCreators({
+ drillDown,
+ }, dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Breadcrumbs);
diff --git a/packages/xod-client/src/debugger/reducer.js b/packages/xod-client/src/debugger/reducer.js
index cbe43f33..a04cb3c6 100644
--- a/packages/xod-client/src/debugger/reducer.js
+++ b/packages/xod-client/src/debugger/reducer.js
@@ -17,6 +17,12 @@ import initialState from './state';
const MAX_LOG_MESSAGES = 1000;
+// =============================================================================
+//
+// Utils
+//
+// =============================================================================
+
const addToLog = R.over(R.lensProp('log'));
const addMessageToLog = R.curry(
@@ -62,6 +68,12 @@ const updateWatchNodeValues = R.curry(
const showDebuggerPane = R.assoc('isVisible', true);
const hideDebuggerPane = R.assoc('isVisible', false);
+// =============================================================================
+//
+// Reducer
+//
+// =============================================================================
+
export default (state = initialState, action) => {
switch (action.type) {
case SHOW_DEBUGGER_PANEL:
diff --git a/packages/xod-client/src/debugger/selectors.js b/packages/xod-client/src/debugger/selectors.js
index bd21f7ad..c883c5a4 100644
--- a/packages/xod-client/src/debugger/selectors.js
+++ b/packages/xod-client/src/debugger/selectors.js
@@ -1,4 +1,11 @@
import R from 'ramda';
+import { createSelector } from 'reselect';
+import {
+ getCurrentTabId,
+ getBreadcrumbChunks,
+ getBreadcrumbActiveIndex,
+} from '../editor/selectors';
+import { DEBUGGER_TAB_ID } from '../editor/constants';
export const getDebuggerState = R.prop('debugger');
@@ -26,3 +33,40 @@ export const getWatchNodeValues = R.compose(
R.prop('watchNodeValues'),
getDebuggerState
);
+
+export const getWatchNodeValuesForCurrentPatch = createSelector(
+ [getCurrentTabId, getWatchNodeValues, getBreadcrumbChunks, getBreadcrumbActiveIndex],
+ (tabId, nodeValues, chunks, activeIndex) => {
+ if (tabId !== DEBUGGER_TAB_ID) return {};
+
+ const nodeIdPath = R.compose(
+ R.join('~'),
+ R.append(''),
+ R.map(R.prop('nodeId')),
+ R.tail, // remove first cause it's entry patch without any nodeId
+ R.take(activeIndex + 1)
+ )(chunks);
+
+ return R.compose(
+ R.fromPairs,
+ R.when(
+ () => (activeIndex !== 0),
+ R.map(
+ R.over(
+ R.lensIndex(0),
+ R.replace(nodeIdPath, '')
+ )
+ )
+ ),
+ R.filter(R.compose(
+ R.ifElse(
+ () => (activeIndex === 0),
+ R.complement(R.contains)('~'),
+ R.startsWith(nodeIdPath),
+ ),
+ R.nth(0)
+ )),
+ R.toPairs
+ )(nodeValues);
+ }
+);
diff --git a/packages/xod-client/src/editor/constants.js b/packages/xod-client/src/editor/constants.js
index 22a34966..9b7add99 100644
--- a/packages/xod-client/src/editor/constants.js
+++ b/packages/xod-client/src/editor/constants.js
@@ -67,3 +67,5 @@ export const TAB_TYPES = {
PATCH: 'PATCH',
DEBUGGER: 'DEBUGGER',
};
+
+export const DEBUGGER_TAB_ID = 'debugger';
diff --git a/packages/xod-client/src/editor/containers/Editor.jsx b/packages/xod-client/src/editor/containers/Editor.jsx
index 5df26746..2e3f2f9d 100644
--- a/packages/xod-client/src/editor/containers/Editor.jsx
+++ b/packages/xod-client/src/editor/containers/Editor.jsx
@@ -19,13 +19,14 @@ import * as EditorSelectors from '../selectors';
import { isInput } from '../../utils/browser';
import { COMMAND } from '../../utils/constants';
-import { FOCUS_AREAS } from '../constants';
+import { FOCUS_AREAS, DEBUGGER_TAB_ID } from '../constants';
import Patch from './Patch';
import NoPatch from '../components/NoPatch';
import Suggester from '../components/Suggester';
import Inspector from '../components/Inspector';
import Debugger from '../../debugger/containers/Debugger';
+import Breadcrumbs from '../../debugger/containers/Breadcrumbs';
import Sidebar from '../../utils/components/Sidebar';
import Workarea from '../../utils/components/Workarea';
@@ -119,6 +120,10 @@ class Editor extends React.Component {
) : null;
const DebuggerContainer = (this.props.isDebuggerVisible) ? : null;
+ const BreadcrumbsContainer = (
+ this.props.isDebuggerVisible &&
+ this.props.currentTabId === DEBUGGER_TAB_ID
+ ) ? : null;
const DebugSessionStopButton = (
this.props.isDebugSessionRunning &&
@@ -154,6 +159,7 @@ class Editor extends React.Component {
{openedPatch}
{suggester}
{DebuggerContainer}
+ {BreadcrumbsContainer}
@@ -168,6 +174,7 @@ Editor.propTypes = {
selection: sanctuaryPropType($.Array(RenderableSelection)),
currentPatchPath: PropTypes.string,
currentPatch: sanctuaryPropType($Maybe(PatchType)),
+ currentTabId: PropTypes.string,
patchesIndex: PropTypes.object,
isHelpbarVisible: PropTypes.bool,
isDebuggerVisible: PropTypes.bool,
@@ -194,6 +201,7 @@ const mapStateToProps = R.applySpec({
selection: ProjectSelectors.getRenderableSelection,
currentPatch: ProjectSelectors.getCurrentPatch,
currentPatchPath: EditorSelectors.getCurrentPatchPath,
+ currentTabId: EditorSelectors.getCurrentTabId,
patchesIndex: ProjectSelectors.getPatchSearchIndex,
suggesterIsVisible: EditorSelectors.isSuggesterVisible,
suggesterPlacePosition: EditorSelectors.getSuggesterPlacePosition,
diff --git a/packages/xod-client/src/editor/containers/Patch/index.jsx b/packages/xod-client/src/editor/containers/Patch/index.jsx
index 36b7362e..a3f06ad0 100644
--- a/packages/xod-client/src/editor/containers/Patch/index.jsx
+++ b/packages/xod-client/src/editor/containers/Patch/index.jsx
@@ -7,6 +7,7 @@ import { bindActionCreators } from 'redux';
import * as EditorActions from '../../actions';
import * as ProjectActions from '../../../project/actions';
+import * as DebuggerActions from '../../../debugger/actions';
import * as EditorSelectors from '../../selectors';
import * as ProjectSelectors from '../../../project/selectors';
@@ -153,7 +154,7 @@ const mapStateToProps = R.applySpec({
offset: EditorSelectors.getCurrentPatchOffset,
draggedPreviewSize: EditorSelectors.getDraggedPreviewSize,
isDebugSession: DebugSelectors.isDebugSession,
- nodeValues: DebugSelectors.getWatchNodeValues,
+ nodeValues: DebugSelectors.getWatchNodeValuesForCurrentPatch,
});
const mapDispatchToProps = dispatch => ({
@@ -174,6 +175,7 @@ const mapDispatchToProps = dispatch => ({
linkPin: EditorActions.linkPin,
setOffset: EditorActions.setCurrentPatchOffset,
switchPatch: EditorActions.switchPatch,
+ drillDown: DebuggerActions.drillDown,
}, dispatch),
});
diff --git a/packages/xod-client/src/editor/containers/Patch/modes/debugging.jsx b/packages/xod-client/src/editor/containers/Patch/modes/debugging.jsx
index 65985ab0..a3563883 100644
--- a/packages/xod-client/src/editor/containers/Patch/modes/debugging.jsx
+++ b/packages/xod-client/src/editor/containers/Patch/modes/debugging.jsx
@@ -71,6 +71,9 @@ const debuggingMode = {
api.props.actions.deselectAll();
},
+ onNodeDoubleClick(api, nodeId, patchPath) {
+ api.props.actions.drillDown(patchPath, nodeId);
+ },
getHotkeyHandlers(api) {
return {
[COMMAND.DESELECT]: api.props.actions.deselectAll,
@@ -112,6 +115,7 @@ const debuggingMode = {
linkingPin={api.props.linkingPin}
onMouseDown={R.partial(this.onEntityMouseDown, [api, SELECTION_ENTITY_TYPE.NODE])}
onMouseUp={R.partial(this.onEntityMouseUp, [api, SELECTION_ENTITY_TYPE.NODE])}
+ onDoubleClick={bindApi(api, this.onNodeDoubleClick)}
/>
R.compose(
@@ -60,6 +62,27 @@ const getTabById = R.curry(
)(state)
);
+const getCurrentTab = R.converge(
+ getTabById,
+ [
+ getCurrentTabId,
+ R.identity,
+ ]
+);
+
+const getBreadcrumbs = R.compose(
+ R.prop('breadcrumbs'),
+ getCurrentTab
+);
+const getBreadcrumbActiveIndex = R.compose(
+ R.propOr(-1, 'activeIndex'),
+ getBreadcrumbs
+);
+const getBreadcrumbChunks = R.compose(
+ R.propOr([], 'chunks'),
+ getBreadcrumbs
+);
+
const isTabOpened = R.curry(
(tabId, state) => R.compose(
R.complement(R.isNil),
@@ -76,11 +99,20 @@ const isPatchOpened = R.curry(
);
const setTabOffset = R.curry(
- (offset, tabId, state) => R.assocPath(
- ['tabs', tabId, 'offset'],
- offset,
- state
- )
+ (offset, tabId, state) => R.compose(
+ R.when(
+ () => (tabId === DEBUGGER_TAB_ID),
+ newState => R.assocPath(
+ ['tabs', tabId, 'breadcrumbs', 'chunks', getBreadcrumbActiveIndex(newState), 'offset'],
+ offset,
+ newState
+ )
+ ),
+ R.assocPath(
+ ['tabs', tabId, 'offset'],
+ offset
+ )
+ )(state)
);
const getTabIdbyPatchPath = R.curry(
@@ -117,6 +149,14 @@ const syncTabOffset = R.curry(
}
);
+const setPropsToTab = R.curry(
+ (id, props, state) => R.compose(
+ R.assocPath(['tabs', id], R.__, state),
+ R.merge(R.__, props),
+ getTabById(id)
+ )(state)
+);
+
const addTabWithProps = R.curry(
(id, type, patchPath, state) => {
const tabs = R.prop('tabs')(state);
@@ -130,7 +170,7 @@ const addTabWithProps = R.curry(
);
const newIndex = R.inc(lastIndex);
- return R.assocPath(['tabs', id], {
+ return setPropsToTab(id, {
id,
patchPath,
index: newIndex,
@@ -225,6 +265,10 @@ const closeTabById = R.curry(
);
return R.compose(
+ R.when(
+ isCurrentDebuggerTabClosing,
+ clearSelection
+ ),
R.cond([
[isCurrentDebuggerTabClosing, openOriginalPatch(tabToClose.patchPath)],
[isCurrentTabClosing, openLatestOpenedTab],
@@ -250,6 +294,77 @@ const renamePatchInTabs = (newPatchPath, oldPatchPath, state) => {
const createSelectionEntity = R.curry((entityType, id) => ({ entity: entityType, id }));
+const findChunkIndex = R.curry(
+ (patchPath, nodeId, chunks) =>
+ R.findIndex(R.both(
+ R.propEq('patchPath', patchPath),
+ R.propEq('nodeId', nodeId),
+ ),
+ chunks
+ )
+);
+
+const createChunk = (patchPath, nodeId) => ({
+ patchPath,
+ nodeId,
+ offset: DEFAULT_PANNING_OFFSET,
+});
+
+const setActiveIndex = R.curry(
+ (index, state) => {
+ const curTabId = getCurrentTabId(state);
+ return R.assocPath(['tabs', curTabId, 'breadcrumbs', 'activeIndex'], index, state);
+ }
+);
+
+const drillDown = R.curry(
+ (patchPath, nodeId, state) => {
+ const currentTabId = getCurrentTabId(state);
+ if (currentTabId !== DEBUGGER_TAB_ID) return state;
+
+ const activeIndex = getBreadcrumbActiveIndex(state);
+ const chunks = getBreadcrumbChunks(state);
+ const index = findChunkIndex(patchPath, nodeId, chunks);
+
+ if (index > -1) {
+ const chunkOffset = getBreadcrumbChunks(state)[index].offset;
+ return R.compose(
+ setTabOffset(chunkOffset, currentTabId),
+ setActiveIndex(index)
+ )(state);
+ }
+
+ const shouldResetTail = (activeIndex < (chunks.length - 1));
+ const newChunk = createChunk(patchPath, nodeId);
+ const newChunkOffset = newChunk.offset;
+
+ return R.compose(
+ setTabOffset(newChunkOffset, currentTabId),
+ R.converge(
+ setActiveIndex,
+ [
+ R.compose(
+ R.dec,
+ R.length,
+ getBreadcrumbChunks
+ ),
+ R.identity,
+ ]
+ ),
+ R.over(
+ R.lensPath(['tabs', currentTabId, 'breadcrumbs', 'chunks']),
+ R.compose(
+ R.append(newChunk),
+ R.when(
+ () => shouldResetTail,
+ R.take((activeIndex + 1))
+ )
+ )
+ ),
+ )(state);
+ }
+);
+
// =============================================================================
//
// Reducer
@@ -363,11 +478,13 @@ const editorReducer = (state = {}, action) => {
case EDITOR_SWITCH_PATCH:
return openPatchByPath(action.payload.patchPath, state);
case EDITOR_SWITCH_TAB:
- return R.assoc(
- 'currentTabId',
- action.payload.tabId,
- state
- );
+ return R.compose(
+ R.assoc(
+ 'currentTabId',
+ action.payload.tabId
+ ),
+ clearSelection
+ )(state);
case PATCH_RENAME:
return renamePatchInTabs(
action.payload.newPatchPath,
@@ -419,16 +536,24 @@ const editorReducer = (state = {}, action) => {
const currentPatchPath = currentTab.patchPath;
const currentOffset = currentTab.offset;
return R.compose(
- R.assoc('currentTabId', 'debugger'),
+ drillDown(action.payload.patchPath, null),
+ R.assoc('currentTabId', DEBUGGER_TAB_ID),
R.assoc('mode', EDITOR_MODE.DEBUGGING),
- setTabOffset(currentOffset, 'debugger'),
- addTabWithProps('debugger', TAB_TYPES.DEBUGGER, currentPatchPath)
+ setTabOffset(currentOffset, DEBUGGER_TAB_ID),
+ clearSelection,
+ addTabWithProps(DEBUGGER_TAB_ID, TAB_TYPES.DEBUGGER, currentPatchPath)
)(state);
}
case DEBUG_SESSION_STOPPED:
return R.when(
- isTabOpened('debugger'),
- closeTabById('debugger')
+ isTabOpened(DEBUGGER_TAB_ID),
+ closeTabById(DEBUGGER_TAB_ID)
+ )(state);
+ case DEBUG_DRILL_DOWN:
+ return R.compose(
+ drillDown(action.payload.patchPath, action.payload.nodeId),
+ setPropsToTab(DEBUGGER_TAB_ID, { patchPath: action.payload.patchPath }),
+ clearSelection
)(state);
default:
return state;
diff --git a/packages/xod-client/src/editor/selectors.js b/packages/xod-client/src/editor/selectors.js
index 722e9b9f..996fdec3 100644
--- a/packages/xod-client/src/editor/selectors.js
+++ b/packages/xod-client/src/editor/selectors.js
@@ -1,7 +1,12 @@
import R from 'ramda';
+import { Maybe } from 'ramda-fantasy';
import { createSelector } from 'reselect';
+import { mapIndexed } from 'xod-func-tools';
+import * as XP from 'xod-project';
import { addPoints, subtractPoints, DEFAULT_PANNING_OFFSET } from '../project/nodeLayout';
+const getProject = R.prop('project'); // Problem of cycle imports...
+
export const getEditor = R.prop('editor');
@@ -141,3 +146,56 @@ export const getSuggesterHighlightedPatchPath = R.pipe(
getSuggester,
R.prop('highlightedPatchPath')
);
+
+
+export const getBreadcrumbs = R.compose(
+ R.prop('breadcrumbs'),
+ getCurrentTab
+);
+
+export const getBreadcrumbChunks = R.compose(
+ R.propOr([], 'chunks'),
+ getBreadcrumbs
+);
+
+export const getBreadcrumbActiveIndex = R.compose(
+ R.propOr(-1, 'activeIndex'),
+ getBreadcrumbs
+);
+
+export const getActiveBreadcrumb = createSelector(
+ [getBreadcrumbActiveIndex, getBreadcrumbChunks],
+ R.ifElse(
+ R.equals(-1),
+ R.always(null),
+ R.nth
+ )
+);
+
+export const getRenerableBreadcrumbChunks = createSelector(
+ [getProject, getBreadcrumbChunks],
+ (project, chunks) => mapIndexed(
+ (chunk, i) => {
+ const patchName = XP.getBaseName(chunk.patchPath);
+ if (chunk.nodeId === null) {
+ return R.assoc('label', patchName, chunk);
+ }
+
+ const parentPatchPath = chunks[i - 1].patchPath;
+ return R.compose(
+ R.assoc('label', R.__, chunk),
+ R.when(
+ R.isEmpty,
+ R.always(patchName)
+ ),
+ Maybe.maybe(
+ patchName,
+ XP.getNodeLabel
+ ),
+ XP.getNodeById(chunk.nodeId),
+ XP.getPatchByPathUnsafe(parentPatchPath)
+ )(project);
+ },
+ chunks
+ )
+);