Add app menu and context menu

This commit is contained in:
futpib 2018-11-18 23:16:30 +03:00
parent 331dd078ba
commit 9a56180509
5 changed files with 216 additions and 55 deletions

View File

@ -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
View 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 };

View File

@ -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",

View File

@ -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);

View File

@ -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"