Add app menu and context menu
This commit is contained in:
parent
331dd078ba
commit
9a56180509
|
@ -33,6 +33,8 @@ const { bindActionCreators } = require('redux');
|
|||
|
||||
const { HotKeys } = require('react-hotkeys');
|
||||
|
||||
const { PopupMenu, MenuItem } = require('@futpib/react-electron-menu');
|
||||
|
||||
const d = require('../../utils/d');
|
||||
const memoize = require('../../utils/memoize');
|
||||
|
||||
|
@ -520,6 +522,19 @@ const renderEdgeText = state => ({ data: dgo, transform, selected }) => {
|
|||
|
||||
const layoutEngine = new LayoutEngine();
|
||||
|
||||
class GraphContextMenu extends React.PureComponent {
|
||||
render() {
|
||||
return r(PopupMenu, {
|
||||
onClose: this.props.onClose,
|
||||
}, [
|
||||
r(MenuItem, {
|
||||
label: 'Delete',
|
||||
onClick: this.props.onDelete,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class Graph extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -543,6 +558,9 @@ class Graph extends React.Component {
|
|||
onSwapEdge: this.onSwapEdge.bind(this),
|
||||
onDeleteEdge: this.onDeleteEdge.bind(this),
|
||||
onEdgeMouseDown: this.onEdgeMouseDown.bind(this),
|
||||
|
||||
onContextMenuDelete: this.onContextMenuDelete.bind(this),
|
||||
onContextMenuClose: this.onContextMenuClose.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -622,7 +640,11 @@ class Graph extends React.Component {
|
|||
dgoToPai.set(edge, pai);
|
||||
});
|
||||
|
||||
let { selected, moved } = state;
|
||||
let { selected, moved, contexted } = state;
|
||||
|
||||
if (contexted && selected !== contexted) {
|
||||
contexted = null;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
selected = find(x => x.id === selected.id, nodes) ||
|
||||
|
@ -634,12 +656,18 @@ class Graph extends React.Component {
|
|||
find(x => x.id === moved.id, edges);
|
||||
}
|
||||
|
||||
if (contexted) {
|
||||
contexted = find(x => x.id === contexted.id, nodes) ||
|
||||
find(x => x.id === contexted.id, edges);
|
||||
}
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
|
||||
selected,
|
||||
moved,
|
||||
contexted,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -650,6 +678,7 @@ class Graph extends React.Component {
|
|||
(nextProps.preferences === this.props.preferences) &&
|
||||
(nextProps.icons === this.props.icons) &&
|
||||
(nextState.selected === this.state.selected) &&
|
||||
(nextState.contexted === this.state.contexted) &&
|
||||
(nextState.moved === this.state.moved)
|
||||
);
|
||||
}
|
||||
|
@ -693,19 +722,7 @@ class Graph extends React.Component {
|
|||
}
|
||||
|
||||
onDeleteNode(selected) {
|
||||
const pai = dgoToPai.get(selected);
|
||||
|
||||
if (selected.type === 'client') {
|
||||
this.props.killClientByIndex(selected.index);
|
||||
} else if (selected.type === 'module') {
|
||||
this.props.unloadModuleByIndex(selected.index);
|
||||
} else if (
|
||||
(selected.type === 'sink' || selected.type === 'source') &&
|
||||
pai &&
|
||||
typeof pai.moduleIndex === 'number'
|
||||
) {
|
||||
this.props.unloadModuleByIndex(pai.moduleIndex);
|
||||
}
|
||||
this.onDelete(selected);
|
||||
}
|
||||
|
||||
onNodeMouseDown(event, data) {
|
||||
|
@ -718,6 +735,11 @@ class Graph extends React.Component {
|
|||
) {
|
||||
this.toggleMute(pai);
|
||||
}
|
||||
} else if (pai && event.button === 2) {
|
||||
this.setState({
|
||||
selected: data,
|
||||
contexted: data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,11 +759,7 @@ class Graph extends React.Component {
|
|||
}
|
||||
|
||||
onDeleteEdge(selected) {
|
||||
if (selected.type === 'sinkInput') {
|
||||
this.props.killSinkInputByIndex(selected.index);
|
||||
} else if (selected.type === 'sourceOutput') {
|
||||
this.props.killSourceOutputByIndex(selected.index);
|
||||
}
|
||||
this.onDelete(selected);
|
||||
}
|
||||
|
||||
onEdgeMouseDown(event, data) {
|
||||
|
@ -752,6 +770,11 @@ class Graph extends React.Component {
|
|||
) {
|
||||
this.toggleMute(pai);
|
||||
}
|
||||
} else if (pai && event.button === 2) {
|
||||
this.setState({
|
||||
selected: data,
|
||||
contexted: data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -798,6 +821,36 @@ class Graph extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onDelete(selected) {
|
||||
const pai = dgoToPai.get(selected);
|
||||
|
||||
if (selected.type === 'client') {
|
||||
this.props.killClientByIndex(selected.index);
|
||||
} else if (selected.type === 'module') {
|
||||
this.props.unloadModuleByIndex(selected.index);
|
||||
} else if (selected.type === 'sinkInput') {
|
||||
this.props.killSinkInputByIndex(selected.index);
|
||||
} else if (selected.type === 'sourceOutput') {
|
||||
this.props.killSourceOutputByIndex(selected.index);
|
||||
} else if (
|
||||
(selected.type === 'sink' || selected.type === 'source') &&
|
||||
pai &&
|
||||
typeof pai.moduleIndex === 'number'
|
||||
) {
|
||||
this.props.unloadModuleByIndex(pai.moduleIndex);
|
||||
}
|
||||
}
|
||||
|
||||
onContextMenuDelete() {
|
||||
this.onDelete(this.state.selected);
|
||||
}
|
||||
|
||||
onContextMenuClose() {
|
||||
this.setState({
|
||||
contexted: null,
|
||||
});
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.graphViewElement.focus();
|
||||
}
|
||||
|
@ -1054,48 +1107,55 @@ class Graph extends React.Component {
|
|||
handlers: map(f => bind(f, this), pick(keys(keyMap), this)),
|
||||
}, r.div({
|
||||
id: 'graph',
|
||||
}, r(GraphView, {
|
||||
nodeKey: 'id',
|
||||
edgeKey: 'id',
|
||||
}, [
|
||||
r(GraphView, {
|
||||
nodeKey: 'id',
|
||||
edgeKey: 'id',
|
||||
|
||||
nodes,
|
||||
edges,
|
||||
nodes,
|
||||
edges,
|
||||
|
||||
selected: this.state.selected,
|
||||
moved: this.state.moved,
|
||||
selected: this.state.selected,
|
||||
moved: this.state.moved,
|
||||
|
||||
nodeTypes: {},
|
||||
nodeSubtypes: {},
|
||||
edgeTypes: {},
|
||||
nodeTypes: {},
|
||||
nodeSubtypes: {},
|
||||
edgeTypes: {},
|
||||
|
||||
onSelectNode: this.onSelectNode,
|
||||
onCreateNode: this.onCreateNode,
|
||||
onUpdateNode: this.onUpdateNode,
|
||||
onDeleteNode: this.onDeleteNode,
|
||||
onNodeMouseDown: this.onNodeMouseDown,
|
||||
onSelectNode: this.onSelectNode,
|
||||
onCreateNode: this.onCreateNode,
|
||||
onUpdateNode: this.onUpdateNode,
|
||||
onDeleteNode: this.onDeleteNode,
|
||||
onNodeMouseDown: this.onNodeMouseDown,
|
||||
|
||||
onSelectEdge: this.onSelectEdge,
|
||||
onCreateEdge: this.onCreateEdge,
|
||||
onSwapEdge: this.onSwapEdge,
|
||||
onDeleteEdge: this.onDeleteEdge,
|
||||
onEdgeMouseDown: this.onEdgeMouseDown,
|
||||
onSelectEdge: this.onSelectEdge,
|
||||
onCreateEdge: this.onCreateEdge,
|
||||
onSwapEdge: this.onSwapEdge,
|
||||
onDeleteEdge: this.onDeleteEdge,
|
||||
onEdgeMouseDown: this.onEdgeMouseDown,
|
||||
|
||||
showGraphControls: false,
|
||||
showGraphControls: false,
|
||||
|
||||
edgeArrowSize: 64,
|
||||
edgeArrowSize: 64,
|
||||
|
||||
layoutEngine,
|
||||
layoutEngine,
|
||||
|
||||
backgroundFillId: '#background-pattern',
|
||||
backgroundFillId: '#background-pattern',
|
||||
|
||||
renderDefs,
|
||||
renderDefs,
|
||||
|
||||
renderNode,
|
||||
renderNodeText: renderNodeText(this.props),
|
||||
renderNode,
|
||||
renderNodeText: renderNodeText(this.props),
|
||||
|
||||
renderEdge,
|
||||
renderEdgeText: renderEdgeText(this.props),
|
||||
})));
|
||||
renderEdge,
|
||||
renderEdgeText: renderEdgeText(this.props),
|
||||
}),
|
||||
|
||||
this.state.contexted && r(GraphContextMenu, {
|
||||
onClose: this.onContextMenuClose,
|
||||
onDelete: this.onContextMenuDelete,
|
||||
}),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
63
components/menu/index.js
Normal file
63
components/menu/index.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
|
||||
const electron = require('electron');
|
||||
|
||||
const React = require('react');
|
||||
|
||||
const r = require('r-dom');
|
||||
|
||||
const {
|
||||
WindowMenu: WindowMenuBase,
|
||||
MenuItem,
|
||||
Provider,
|
||||
} = require('@futpib/react-electron-menu');
|
||||
|
||||
const MenuProvider = ({ children }) => r(Provider, { electron }, r(React.Fragment, {}, [
|
||||
r(WindowMenu),
|
||||
...[].concat(children),
|
||||
]));
|
||||
|
||||
const WindowMenu = () => r(WindowMenuBase, [
|
||||
r(MenuItem, {
|
||||
label: 'App',
|
||||
}, [
|
||||
r(MenuItem, {
|
||||
label: 'Quit',
|
||||
role: 'quit',
|
||||
}),
|
||||
]),
|
||||
|
||||
r(MenuItem, {
|
||||
label: 'View',
|
||||
}, [
|
||||
r(MenuItem, {
|
||||
label: 'Reload',
|
||||
role: 'reload',
|
||||
}),
|
||||
r(MenuItem, {
|
||||
label: 'Force Reload',
|
||||
role: 'forcereload',
|
||||
}),
|
||||
r(MenuItem, {
|
||||
label: 'Toggle Developer Tools',
|
||||
role: 'toggledevtools',
|
||||
}),
|
||||
|
||||
r(MenuItem.Separator),
|
||||
|
||||
r(MenuItem, {
|
||||
label: 'Toggle Full Screen',
|
||||
role: 'togglefullscreen',
|
||||
}),
|
||||
]),
|
||||
|
||||
r(MenuItem, {
|
||||
label: 'Help',
|
||||
}, [
|
||||
r(MenuItem, {
|
||||
label: 'Documentation',
|
||||
onClick: () => electron.shell.openExternal('https://github.com/futpib/pagraphcontrol#readme'),
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
|
||||
module.exports = { MenuProvider };
|
|
@ -22,6 +22,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@futpib/paclient": "^0.0.7",
|
||||
"@futpib/react-electron-menu": "^0.3.0",
|
||||
"bluebird": "^3.5.3",
|
||||
"camelcase": "^5.0.0",
|
||||
"d3": "^5.7.0",
|
||||
|
|
|
@ -4,7 +4,7 @@ const r = require('r-dom');
|
|||
|
||||
const { render } = require('react-dom');
|
||||
|
||||
const { Provider } = require('react-redux');
|
||||
const { Provider: ReduxProvider } = require('react-redux');
|
||||
|
||||
const createStore = require('./store');
|
||||
|
||||
|
@ -12,16 +12,17 @@ const Graph = require('./components/graph');
|
|||
const Cards = require('./components/cards');
|
||||
const Preferences = require('./components/preferences');
|
||||
const { HotKeys } = require('./components/hot-keys');
|
||||
const { MenuProvider } = require('./components/menu');
|
||||
|
||||
const theme = require('./utils/theme');
|
||||
|
||||
const Root = () => r(Provider, {
|
||||
const Root = () => r(ReduxProvider, {
|
||||
store: createStore(),
|
||||
}, r(HotKeys, {}, ({ graphRef, cardsRef, preferencesRef }) => [
|
||||
}, r(MenuProvider, {}, r(HotKeys, {}, ({ graphRef, cardsRef, preferencesRef }) => [
|
||||
r(Graph, { ref: graphRef }),
|
||||
r(Cards, { ref: cardsRef }),
|
||||
r(Preferences, { ref: preferencesRef }),
|
||||
]));
|
||||
])));
|
||||
|
||||
Object.entries(theme.colors).forEach(([ key, value ]) => {
|
||||
document.body.style.setProperty('--' + key, value);
|
||||
|
|
40
yarn.lock
40
yarn.lock
|
@ -89,6 +89,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@futpib/paclient/-/paclient-0.0.7.tgz#d8957135ba81888f5e92812d8e9e4e8e1ebf935f"
|
||||
integrity sha512-fjpJaS3LHuo+51/7g3dqpZBGO2wZtnLAWYKVk5CIBsfqn3345xJaEe0HfLpBxPAdpAHRTcTz5aWXlhOWsBClHA==
|
||||
|
||||
"@futpib/react-electron-menu@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@futpib/react-electron-menu/-/react-electron-menu-0.3.0.tgz#33a9f5bb21823805a9782daf8ba2f8df02cde8f7"
|
||||
integrity sha512-RqlD74LUvDTP5gvHwUy1qGwBLvNnjPmgbDzl8+n2t9Z5WkHsCMAXnsYfEyuKxWMkivCrEYeLBwYEardtm6hRiA==
|
||||
dependencies:
|
||||
react "^15.4.2"
|
||||
react-test-renderer "^15.4.2"
|
||||
|
||||
"@ladjs/time-require@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@ladjs/time-require/-/time-require-0.1.4.tgz#5c615d75fd647ddd5de9cf6922649558856b21a1"
|
||||
|
@ -1394,6 +1402,15 @@ create-error-class@^3.0.0:
|
|||
dependencies:
|
||||
capture-stack-trace "^1.0.0"
|
||||
|
||||
create-react-class@^15.6.0:
|
||||
version "15.6.3"
|
||||
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
|
||||
integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==
|
||||
dependencies:
|
||||
fbjs "^0.8.9"
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
cross-spawn@^5.0.1:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
|
||||
|
@ -2502,7 +2519,7 @@ fast-levenshtein@~2.0.4:
|
|||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fbjs@^0.8.1:
|
||||
fbjs@^0.8.1, fbjs@^0.8.9:
|
||||
version "0.8.17"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
||||
|
@ -4915,7 +4932,7 @@ promise@^7.1.1:
|
|||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
|
||||
prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||
|
@ -5093,6 +5110,25 @@ react-redux@^5.1.0:
|
|||
react-is "^16.6.0"
|
||||
react-lifecycles-compat "^3.0.0"
|
||||
|
||||
react-test-renderer@^15.4.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.6.2.tgz#d0333434fc2c438092696ca770da5ed48037efa8"
|
||||
integrity sha1-0DM0NPwsQ4CSaWyncNpe1IA376g=
|
||||
dependencies:
|
||||
fbjs "^0.8.9"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
react@^15.4.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
|
||||
integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=
|
||||
dependencies:
|
||||
create-react-class "^15.6.0"
|
||||
fbjs "^0.8.9"
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.0"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react@^16.6.0:
|
||||
version "16.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246"
|
||||
|
|
Loading…
Reference in New Issue
Block a user