-
+
+ {(dead) ? deadIcon : null}
+ {label}
+
+
{hoverButtons}
- {/* placeholder for context menu opener */}
- {/* */}
);
@@ -113,6 +121,7 @@ PatchGroupItem.propTypes = {
connectDragSource: PropTypes.func.isRequired,
connectDragPreview: PropTypes.func.isRequired,
onBeginDrag: PropTypes.func.isRequired,
+ collectPropsFn: PropTypes.func.isRequired,
};
export default DragSource( // eslint-disable-line new-cap
diff --git a/packages/xod-client/src/projectBrowser/components/PatchGroupItemContextMenu.jsx b/packages/xod-client/src/projectBrowser/components/PatchGroupItemContextMenu.jsx
new file mode 100644
index 00000000..0bcf1df3
--- /dev/null
+++ b/packages/xod-client/src/projectBrowser/components/PatchGroupItemContextMenu.jsx
@@ -0,0 +1,96 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cn from 'classnames';
+import { ContextMenu, MenuItem, connectMenu } from 'react-contextmenu';
+
+import { PATCH_GROUP_CONTEXT_MENU_ID } from '../constants';
+
+const callCallbackWithPatchPath = onClick => (event, data) => onClick(data.patchPath);
+
+const PatchGroupItemContextMenu = (props) => {
+ const trigger = (props.trigger) ? props.trigger : {};
+
+ const renamePatch = (trigger.isLocalPatch)
+ ? (
+
-
+
+ Project Browser
+
+
+ />
+
);
@@ -19,6 +25,7 @@ ProjectBrowserToolbar.displayName = 'ProjectBrowserToolbar';
ProjectBrowserToolbar.propTypes = {
onClickAddPatch: PropTypes.func.isRequired,
+ onClickAddLibrary: PropTypes.func.isRequired,
};
export default ProjectBrowserToolbar;
diff --git a/packages/xod-client/src/projectBrowser/constants.js b/packages/xod-client/src/projectBrowser/constants.js
new file mode 100644
index 00000000..59738dd0
--- /dev/null
+++ b/packages/xod-client/src/projectBrowser/constants.js
@@ -0,0 +1,3 @@
+// Id of context menu to show it
+// eslint-disable-next-line import/prefer-default-export
+export const PATCH_GROUP_CONTEXT_MENU_ID = 'PATCH_GROUP_CONTEXT_MENU_ID';
diff --git a/packages/xod-client/src/projectBrowser/containers/ProjectBrowser.jsx b/packages/xod-client/src/projectBrowser/containers/ProjectBrowser.jsx
index 285e865e..17deb98a 100644
--- a/packages/xod-client/src/projectBrowser/containers/ProjectBrowser.jsx
+++ b/packages/xod-client/src/projectBrowser/containers/ProjectBrowser.jsx
@@ -1,12 +1,12 @@
import R from 'ramda';
import React from 'react';
import PropTypes from 'prop-types';
-import cn from 'classnames';
import CustomScroll from 'react-custom-scroll';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Icon } from 'react-fa';
import { HotKeys } from 'react-hotkeys';
+import { ContextMenuTrigger } from 'react-contextmenu';
import $ from 'sanctuary-def';
import {
@@ -28,23 +28,15 @@ import * as PopupSelectors from '../../popups/selectors';
import * as EditorSelectors from '../../editor/selectors';
import { COMMAND } from '../../utils/constants';
-import { noop } from '../../utils/ramda';
import sanctuaryPropType from '../../utils/sanctuaryPropType';
import PatchGroup from '../components/PatchGroup';
import PatchGroupItem from '../components/PatchGroupItem';
-import PatchTypeSelector from '../components/PatchTypeSelector';
import ProjectBrowserPopups from '../components/ProjectBrowserPopups';
import ProjectBrowserToolbar from '../components/ProjectBrowserToolbar';
+import PatchGroupItemContextMenu from '../components/PatchGroupItemContextMenu';
-import { getUtmSiteUrl } from '../../utils/urls';
-import { IconGuide } from '../../utils/components/IconGuide';
-
-const PATCH_TYPE = {
- ALL: 'all',
- MY: 'my',
- LIBRARY: 'library',
-};
+import { PATCH_GROUP_CONTEXT_MENU_ID } from '../constants';
const pickPatchPartsForComparsion = R.map(R.pick(['dead', 'path']));
@@ -69,17 +61,21 @@ const pickPropsForComparsion = R.compose(
class ProjectBrowser extends React.Component {
constructor(props) {
super(props);
- this.patchRenderers = {
- [PATCH_TYPE.MY]: this.renderLocalPatches.bind(this),
- [PATCH_TYPE.LIBRARY]: this.renderLibraryPatches.bind(this),
- };
+ this.state = {};
this.renderPatches = this.renderPatches.bind(this);
this.deselectIfInLibrary = this.deselectIfInLibrary.bind(this);
this.deselectIfInLocalPatches = this.deselectIfInLocalPatches.bind(this);
+ this.onAddNode = this.onAddNode.bind(this);
this.onRenameHotkey = this.onRenameHotkey.bind(this);
this.onDeleteHotkey = this.onDeleteHotkey.bind(this);
+ this.onClickAddLibrary = this.onClickAddLibrary.bind(this);
+ this.onPatchHelpClicked = this.onPatchHelpClicked.bind(this);
+
+ this.renderItem = this.renderItem.bind(this);
+ this.renderLocalPatches = this.renderLocalPatches.bind(this);
+ this.renderLibraryPatches = this.renderLibraryPatches.bind(this);
}
shouldComponentUpdate(nextProps) {
@@ -112,6 +108,15 @@ class ProjectBrowser extends React.Component {
this.props.actions.requestDelete(selectedPatchPath);
}
+ onClickAddLibrary(event) {
+ event.stopPropagation();
+ this.props.actions.showLibSuggester();
+ }
+
+ onPatchHelpClicked() {
+ this.props.actions.showHelpbar();
+ }
+
getHotkeyHandlers() {
return {
[COMMAND.ADD_PATCH]: this.props.actions.requestCreatePatch,
@@ -121,36 +126,14 @@ class ProjectBrowser extends React.Component {
};
}
- localPatchesHoveredButtons(patchPath) {
- const {
- requestRename,
- requestDelete,
- } = this.props.actions;
-
- return [
-
requestDelete(patchPath)}
- />,
- requestRename(patchPath)}
- />,
- this.renderAddNodeButton(patchPath),
- ];
- }
-
- libraryPatchesHoveredButtons(path) {
- return [
- this.renderDocsButton(path),
- this.renderAddNodeButton(path),
- ];
+ getCollectPropsFn(patchPath) {
+ const { currentPatchPath } = this.props;
+ const canAdd = currentPatchPath !== patchPath;
+ return () => ({
+ patchPath,
+ canAdd,
+ isLocalPatch: isPathLocal(patchPath),
+ });
}
deselectIfInLocalPatches() {
@@ -167,50 +150,27 @@ class ProjectBrowser extends React.Component {
};
}
- renderAddNodeButton(patchPath) {
- const { currentPatchPath } = this.props;
-
- const isCurrentPatch = currentPatchPath === patchPath;
- const canAdd = !isCurrentPatch;
-
- const classNames = cn('hover-button add-node', { disabled: !canAdd });
- const action = canAdd ? () => this.onAddNode(patchPath) : noop;
-
- return (
-
- );
- }
-
- renderDocsButton(patchPath) { // eslint-disable-line class-methods-use-this
- return (
-
- );
+ {/* Component needs at least one child :-( */}
+
+ ,
+ ];
}
- renderLocalPatches() {
+ renderItem({ path, dead }) {
const {
- projectName,
- localPatches,
currentPatchPath,
selectedPatchPath,
} = this.props;
@@ -221,7 +181,9 @@ class ProjectBrowser extends React.Component {
startDraggingPatch,
} = this.props.actions;
- const renderItem = ({ path, dead }) => (
+ const collectPropsFn = this.getCollectPropsFn(path);
+
+ return (
setSelection(path)}
- hoverButtons={this.localPatchesHoveredButtons(path)}
+ collectPropsFn={collectPropsFn}
+ hoverButtons={this.renderHoveredButtons(collectPropsFn)}
/>
);
+ }
+
+ renderLocalPatches() {
+ const {
+ projectName,
+ localPatches,
+ } = this.props;
return (
- {R.map(renderItem, localPatches)}
+ {localPatches.map(this.renderItem)}
);
}
renderLibraryPatches() {
- const { libs, installingLibs, selectedPatchPath } = this.props;
- const { setSelection, switchPatch, startDraggingPatch } = this.props.actions;
+ const { libs, installingLibs } = this.props;
const installingLibsComponents = R.map(
({ owner, name, version }) => ({
name: `${owner}/${name}`,
component: (
{owner}/{name}
@@ -279,19 +249,7 @@ class ProjectBrowser extends React.Component {
name={libName}
onClose={this.deselectIfInLibrary(libName)}
>
- {libPatches.map(({ path, dead }) =>
-
setSelection(path)}
- onDoubleClick={() => switchPatch(path)}
- onBeginDrag={startDraggingPatch}
- hoverButtons={this.libraryPatchesHoveredButtons(path)}
- />
- )}
+ {libPatches.map(this.renderItem)}
),
})),
R.toPairs
@@ -304,16 +262,13 @@ class ProjectBrowser extends React.Component {
)(libComponents, installingLibsComponents);
}
- renderPatches(patchType) {
- const rendererKeys = patchType === PATCH_TYPE.ALL
- ? [PATCH_TYPE.MY, PATCH_TYPE.LIBRARY]
- : R.of(patchType);
-
+ renderPatches() {
return (
// "calc(100% - 30px)" cause patch filtering buttons are 30px height
- {rendererKeys.map(k => this.patchRenderers[k]())}
+ {this.renderLocalPatches()}
+ {this.renderLibraryPatches()}
);
@@ -338,17 +293,17 @@ class ProjectBrowser extends React.Component {
/>
+ {this.renderPatches()}
+ { this.patchContextMenuRef = c; }}
+ onPatchAdd={this.onAddNode}
+ onPatchOpen={this.props.actions.switchPatch}
+ onPatchDelete={this.props.actions.requestDelete}
+ onPatchRename={this.props.actions.requestRename}
+ onPatchHelp={this.onPatchHelpClicked}
/>
-
- {this.renderPatches}
-
);
}
@@ -380,6 +335,8 @@ ProjectBrowser.propTypes = {
startDraggingPatch: PropTypes.func.isRequired,
renameProject: PropTypes.func.isRequired,
closeAllPopups: PropTypes.func.isRequired,
+ showLibSuggester: PropTypes.func.isRequired,
+ showHelpbar: PropTypes.func.isRequired,
}),
};
@@ -416,6 +373,8 @@ const mapDispatchToProps = dispatch => ({
closeAllPopups: PopupActions.hideAllPopups,
addNotification: MessagesActions.addNotification,
+ showLibSuggester: EditorActions.showLibSuggester,
+ showHelpbar: EditorActions.showHelpbar,
}, dispatch),
});
diff --git a/packages/xod-client/src/utils/components/Workarea.jsx b/packages/xod-client/src/utils/components/Workarea.jsx
deleted file mode 100644
index 61d9900d..00000000
--- a/packages/xod-client/src/utils/components/Workarea.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const Workarea = ({ children }) => (
-
- {children}
-
-);
-
-Workarea.propTypes = {
- children: PropTypes.arrayOf(PropTypes.element),
-};
-
-export default Workarea;
diff --git a/packages/xod-client/stories/PatchTypeSelector.jsx b/packages/xod-client/stories/PatchTypeSelector.jsx
deleted file mode 100644
index 08164543..00000000
--- a/packages/xod-client/stories/PatchTypeSelector.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import { storiesOf } from '@storybook/react';
-
-import '../src/core/styles/main.scss';
-import PatchTypeSelector from '../src/projectBrowser/components/PatchTypeSelector';
-
-const selectedOptionsRenderer = selectedKey => `selected ${selectedKey}`;
-
-storiesOf('PatchTypeSelector', module)
- .addDecorator(story => (
-
-
inside a 300px wide div:
- {story()}
-
- ))
- .add('default', () => (
-
- {selectedOptionsRenderer}
-
- ))
- .add('with initial selection', () => (
-
- {selectedOptionsRenderer}
-
- ))
- .add('with no options', () => (
-
- {selectedOptionsRenderer}
-
- ));
-
diff --git a/yarn.lock b/yarn.lock
index c1f8d8d4..ee30ef2f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8842,6 +8842,13 @@ react-collapsible@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/react-collapsible/-/react-collapsible-2.0.3.tgz#b7228b002428a6e0a9f41646ea61ba28aacfcb00"
+react-contextmenu@^2.9.1:
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/react-contextmenu/-/react-contextmenu-2.9.1.tgz#391c7001f73e49772e37e98d154736d50dac11de"
+ dependencies:
+ classnames "^2.2.5"
+ object-assign "^4.1.0"
+
react-custom-scroll@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-custom-scroll/-/react-custom-scroll-2.0.1.tgz#4775761a48d66c4b1e576c0705fc6a59fc0d89b6"