diff --git a/app/lib/menu/menu-builder.js b/app/lib/menu/menu-builder.js index b0297fe370..ddd64739e8 100644 --- a/app/lib/menu/menu-builder.js +++ b/app/lib/menu/menu-builder.js @@ -243,594 +243,64 @@ MenuBuilder.prototype.appendQuit = function(submenu) { return this; }; -MenuBuilder.prototype.appendRedo = function() { - this.menu.append(new MenuItem({ - label: 'Redo', - enabled: this.opts.state.redo, - accelerator: 'CommandOrControl+Y', - click: function() { - app.emit('menu:action', 'redo'); - } - })); -}; - -MenuBuilder.prototype.appendCopyPaste = function() { - - var copyEntry = { - label: 'Copy', - enabled: !this.opts.state.inactiveInput || (this.opts.state.elementsSelected && this.opts.state.copy), - accelerator: 'CommandOrControl+C', - click: function() { - app.emit('menu:action', 'copy'); - } - }; - - var pasteEntry = { - label: 'Paste', - enabled: !this.opts.state.inactiveInput || this.opts.state.paste, - accelerator: 'CommandOrControl+V', - click: function() { - app.emit('menu:action', 'paste'); - } - }; - - if (!this.opts.state.inactiveInput) { - assign(copyEntry, { enabled: true, click: function() {}, role: 'copy' }); - - assign(pasteEntry, { enabled: true, click: function() {}, role: 'paste' }); - - this.menu.append(new MenuItem({ - label: 'Cut', - accelerator: 'CommandOrControl+X', - role: 'cut' - })); - } - - this.menu.append(new MenuItem(copyEntry)); +MenuBuilder.prototype.appendMenuItem = function(builder, menuItem) { + var submenu; - this.menu.append(new MenuItem(pasteEntry)); - - return this; -}; - -MenuBuilder.prototype.appendBaseEditActions = function() { - this.menu.append(new MenuItem({ - label: 'Undo', - enabled: this.opts.state.undo, - accelerator: 'CommandOrControl+Z', - click: function() { - app.emit('menu:action', 'undo'); - } - })); - - this.appendRedo(); - - this.appendSeparator(); - - if (this.opts.state.copy) { - this.appendCopyPaste(); - } - - return this; -}; - -MenuBuilder.prototype.appendBpmnActions = function() { - - this.menu.append(new MenuItem({ - label: 'Hand Tool', - accelerator: 'H', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'handTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Lasso Tool', - accelerator: 'L', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'lassoTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Space Tool', - accelerator: 'S', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'spaceTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Global Connect Tool', - accelerator: 'C', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'globalConnectTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Edit Label', - accelerator: 'E', - enabled: this.opts.state.elementsSelected && this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'directEditing'); - } - })); - - this.appendSeparator(); - - this.menu.append(new MenuItem({ - label: 'Align Elements', - enabled: this.opts.state.elementsSelected && this.opts.inactiveInput, - submenu: Menu.buildFromTemplate([ - { - label: 'Align Left', + if (menuItem.submenu) { + submenu = Menu.buildFromTemplate(menuItem.submenu.map(submenuEntry => { + return assign(submenuEntry, { click: function() { - app.emit('menu:action', 'alignElements', { - type: 'left' - }); + app.emit('menu:action', submenuEntry.action, submenuEntry.options); } - }, { - label: 'Align Right', - click: function() { - app.emit('menu:action', 'alignElements', { - type: 'right' - }); - } - }, { - label: 'Align Center', - click: function() { - app.emit('menu:action', 'alignElements', { - type: 'center' - }); - } - }, { - label: 'Align Top', - click: function() { - app.emit('menu:action', 'alignElements', { - type: 'top' - }); - } - }, { - label: 'Align Bottom', - click: function() { - app.emit('menu:action', 'alignElements', { - type: 'bottom' - }); - } - }, { - label: 'Align Middle', - click: function() { - app.emit('menu:action', 'alignElements', { - type: 'middle' - }); - } - } - ]) - })); - - this.menu.append(new MenuItem({ - label: 'Distribute Elements', - enabled: this.opts.state.elementsSelected && this.opts.inactiveInput, - submenu: Menu.buildFromTemplate([ - { - label: 'Distribute Horizontally', - enabled: this.opts.state.elementsSelected, - click: function() { - app.emit('menu:action', 'distributeHorizontally'); - } - }, - { - label: 'Distribute Vertically', - enabled: this.opts.state.elementsSelected, - click: function() { - app.emit('menu:action', 'distributeVertically'); - } - } - ]) - })); - - this.appendSeparator(); - - this.menu.append(new MenuItem({ - label: 'Find', - accelerator: 'CommandOrControl + F', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'find'); - } - })); - - this.appendSeparator(); + }); + })); + } - this.menu.append(new MenuItem({ - label: 'Move Elements to Origin', - accelerator: 'CommandOrControl+Shift+0', - enabled: this.opts.state.inactiveInput, + builder.menu.append(new MenuItem({ + label: menuItem.label, + accelerator: menuItem.accelerator, + enabled: menuItem.enabled !== undefined ? menuItem.enabled : true, click: function() { - app.emit('menu:action', 'moveToOrigin'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Move Canvas', - enabled: this.opts.state.inactiveInput, - submenu: Menu.buildFromTemplate([{ - label: 'Move Up', - accelerator: 'Up', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'up' - }); - } - }, { - label: 'Move Left', - accelerator: 'Left', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'left' - }); - } - }, { - label: 'Move Down', - accelerator: 'Down', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'down' - }); - } - }, { - label: 'Move Right', - accelerator: 'Right', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'right' - }); - } - }]) + app.emit('menu:action', menuItem.action, menuItem.options); + }, + submenu: submenu })); - - this.appendSelectAll(); - - this.appendRemoveSelection(); - - return this; }; +MenuBuilder.prototype.getEditMenu = function(menuItems) { + var builder = new this.constructor(this.opts); -MenuBuilder.prototype.appendCmmnActions = function() { - this.menu.append(new MenuItem({ - label: 'Hand Tool', - accelerator: 'H', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'handTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Lasso Tool', - accelerator: 'L', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'lassoTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Space Tool', - accelerator: 'S', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'spaceTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Global Connect Tool', - accelerator: 'C', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'globalConnectTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Edit Label', - accelerator: 'E', - enabled: this.opts.state.elementsSelected && this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'directEditing'); - } - })); - - this.appendSeparator(); + menuItems.forEach((menuItem, index) => { - this.menu.append(new MenuItem({ - label: 'Find', - accelerator: 'CommandOrControl + F', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'find'); - } - })); - - this.appendSeparator(); - - this.menu.append(new MenuItem({ - label: 'Move Canvas', - enabled: this.opts.state.inactiveInput, - submenu: Menu.buildFromTemplate([{ - label: 'Move Up', - accelerator: 'Up', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'up' - }); - } - }, { - label: 'Move Left', - accelerator: 'Left', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'left' - }); - } - }, { - label: 'Move Down', - accelerator: 'Down', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'down' - }); + if (Array.isArray(menuItem)) { + if (index !== 0) { + builder.appendSeparator(); } - }, { - label: 'Move Right', - accelerator: 'Right', - click: function() { - app.emit('menu:action', 'moveCanvas', { - direction: 'right' - }); - } - }]) - })); - - this.appendSelectAll(); - - this.appendRemoveSelection(); - return this; -}; - -MenuBuilder.prototype.appendRemoveSelection = function() { - this.menu.append(new MenuItem({ - label: 'Remove Selected', - accelerator: 'Delete', - enabled: this.opts.state.elementsSelected, - click: function() { - app.emit('menu:action', 'removeSelection'); - } - })); -}; - - -MenuBuilder.prototype.appendSelectAll = function() { - var selectAll = { - label: 'Select All', - accelerator: 'CommandOrControl+A', - click: function() { - app.emit('menu:action', 'selectElements'); + menuItem.forEach((menuItem) => { + this.appendMenuItem(builder, menuItem); + }); + } else { + this.appendMenuItem(builder, menuItem); } - }; - if (!this.opts.state.inactiveInput) { - assign(selectAll, { enabled: true, click: function() {}, role: 'selectall' }); - } - - this.menu.append(new MenuItem(selectAll)); + }); - return this; + return builder.get(); }; +MenuBuilder.prototype.appendEditMenu = function() { + var subMenu; -MenuBuilder.prototype.appendDmnActions = function() { - var activeEditor = this.opts.state.activeEditor; - - if (activeEditor === 'drd') { - - // DRD editor - this.menu.append(new MenuItem({ - label: 'Lasso Tool', - accelerator: 'L', - enabled: this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'lassoTool'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Edit Label', - accelerator: 'E', - enabled: this.opts.state.elementsSelected && this.opts.state.inactiveInput, - click: function() { - app.emit('menu:action', 'directEditing'); - } - })); - - this.appendSeparator(); - - this.appendSelectAll(); - - this.appendRemoveSelection(); - - } else if (activeEditor === 'decisionTable') { - - // decision table editor - this.menu.append(new MenuItem({ - label: 'Add Rule..', - submenu: Menu.buildFromTemplate([{ - label: 'At End', - accelerator: 'CommandOrControl+D', - click: function() { - app.emit('menu:action', 'addRule'); - } - }, { - label: 'Above Selected', - enabled: this.opts.state.dmnRuleEditing, - click: function() { - app.emit('menu:action', 'addRuleAbove'); - } - }, { - label: 'Below Selected', - enabled: this.opts.state.dmnRuleEditing, - click: function() { - app.emit('menu:action', 'addRuleBelow'); - } - }]) - })); - - this.menu.append(new MenuItem({ - label: 'Remove Rule', - enabled: this.opts.state.dmnRuleEditing, - click: function() { - app.emit('menu:action', 'removeRule'); - } - })); - - this.appendSeparator(); - - this.menu.append(new MenuItem({ - label: 'Add Clause..', - submenu: Menu.buildFromTemplate([{ - label: 'Input', - click: function() { - app.emit('menu:action', 'addInput'); - } - }, { - label: 'Output', - click: function() { - app.emit('menu:action', 'addOutput'); - } - }, { - type: 'separator' - }, { - label: 'Left of selected', - enabled: this.opts.state.dmnClauseEditing, - click: function() { - app.emit('menu:action', 'addClauseLeft'); - } - }, { - label: 'Right of selected', - enabled: this.opts.state.dmnClauseEditing, - click: function() { - app.emit('menu:action', 'addClauseRight'); - } - }]) - })); - - this.menu.append(new MenuItem({ - label: 'Remove Clause', - enabled: this.opts.state.dmnClauseEditing, - click: function() { - app.emit('menu:action', 'removeClause'); - } - })); - - this.appendSeparator(); - - this.menu.append(new MenuItem({ - label: 'Select Cell Above', - enabled: this.opts.state.dmnClauseEditing, - click: function() { - app.emit('menu:action', 'selectCellAbove'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Select Cell Below', - enabled: this.opts.state.dmnClauseEditing, - click: function() { - app.emit('menu:action', 'selectCellBelow'); - } - })); + if (this.opts.state.editMenu) { + subMenu = this.getEditMenu(this.opts.state.editMenu, this.opts); } - return this; -}; - - -MenuBuilder.prototype.appendSearchActions = function() { this.menu.append(new MenuItem({ - label: 'Find', - accelerator: 'CommandOrControl + F', - click: function() { - app.emit('menu:action', 'find'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Find Next', - accelerator: 'Shift + CommandOrControl + N', - click: function() { - app.emit('menu:action', 'findNext'); - } + label: 'Edit', + submenu: subMenu })); - this.menu.append(new MenuItem({ - label: 'Find Previous', - accelerator: 'Shift + CommandOrControl + P', - click: function() { - app.emit('menu:action', 'findPrev'); - } - })); - - this.menu.append(new MenuItem({ - label: 'Replace', - accelerator: 'Shift + CommandOrControl + F', - click: function() { - app.emit('menu:action', 'replace'); - } - })); -}; - -MenuBuilder.prototype.appendEditMenu = function() { - if (this.opts.state.editable) { - var builder = new this.constructor(this.opts).appendBaseEditActions(); - - if (this.opts.state.bpmn) { - builder.appendSeparator(); - - builder.appendBpmnActions(); - } - - if (this.opts.state.dmn) { - builder.appendDmnActions(); - } - - if (this.opts.state.cmmn) { - builder.appendSeparator(); - - builder.appendCmmnActions(); - } - - if (this.opts.state.searchable) { - builder.appendSeparator(); - - builder.appendSearchActions(); - } - - this.menu.append(new MenuItem({ - label: 'Edit', - submenu: builder.get() - })); - } - - - return this; }; diff --git a/client/src/app/App.js b/client/src/app/App.js index 85e81cb274..8c914cc8ee 100644 --- a/client/src/app/App.js +++ b/client/src/app/App.js @@ -362,13 +362,16 @@ export class App extends Component { }); } + tabState = { + ...tabState, + ...properties + }; this.setState({ - tabState: { - ...tabState, - ...properties - } + tabState }); + + this.updateMenu(tabState); } tabSaved(tab, newFile) { @@ -542,6 +545,11 @@ export class App extends Component { console.error('NOT IMPLEMENTED'); } + updateMenu = (state) => { + console.log('App#updateMenu'); + this.props.globals.backend.updateMenu(state); + } + triggerAction = (action, options) => { const { @@ -627,6 +635,10 @@ export class App extends Component { return this.showShortcuts(); } + if (action === 'update-menu') { + return this.updateMenu(); + } + const tab = this.tabRef.current; return tab.triggerAction(action, options); diff --git a/client/src/app/AppParent.js b/client/src/app/AppParent.js index c74bd6abdb..df5f0ffd8b 100644 --- a/client/src/app/AppParent.js +++ b/client/src/app/AppParent.js @@ -48,6 +48,8 @@ export default class AppParent extends Component { } handleToolStateChanged = (tab, state) => { + + // TODO(philipp): is this necessary? this.getBackend().updateMenu(state); } diff --git a/client/src/app/tabs/bpmn/BpmnEditor.js b/client/src/app/tabs/bpmn/BpmnEditor.js index 962bdbf9d4..9e6dda087a 100644 --- a/client/src/app/tabs/bpmn/BpmnEditor.js +++ b/client/src/app/tabs/bpmn/BpmnEditor.js @@ -17,6 +17,9 @@ import { import CamundaBpmnModeler from './modeler'; +import { active as isInputActive } from '../../../util/dom/is-input'; + +import { getBpmnEditMenu } from '../get-edit-menu'; import css from './BpmnEditor.less'; @@ -177,22 +180,46 @@ export class BpmnEditor extends CachedComponent { onChanged } = this.props; - // TODO(nikku): complete state updating const commandStack = modeler.get('commandStack'); const selection = modeler.get('selection'); + const canPaste = !modeler.get('clipboard').isEmpty(); const selectionLength = selection.get().length; + const inputActive = isInputActive(); + + const editMenu = getBpmnEditMenu({ + align: selectionLength > 1, + canCopy: !!selectionLength, + canPaste, + canRedo: commandStack.canRedo(), + canUndo: commandStack.canUndo(), + distribute: selectionLength > 2, + editLabel: !inputActive && !!selectionLength, + find: !inputActive, + globalConnectTool: !inputActive, + handTool: !inputActive, + lassoTool: !inputActive, + moveToOrigin: !inputActive, + spaceTool: !inputActive, + moveCanvas: !inputActive, + removeSelected: !!selectionLength + }); + const newState = { - undo: commandStack.canUndo(), - redo: commandStack.canRedo(), align: selectionLength > 1, + canExport: [ 'svg', 'png' ], + distribute: selectionLength > 2, + redo: commandStack.canRedo(), setColor: selectionLength, - canExport: [ 'svg', 'png' ] + undo: commandStack.canUndo() }; if (typeof onChanged === 'function') { - onChanged(newState); + onChanged({ + ...newState, + editMenu + }); } this.setState(newState); @@ -382,14 +409,14 @@ export class BpmnEditor extends CachedComponent { - -
); diff --git a/client/src/remote/Backend.js b/client/src/remote/Backend.js index f482380fd0..627167967d 100644 --- a/client/src/remote/Backend.js +++ b/client/src/remote/Backend.js @@ -74,7 +74,12 @@ export default class Backend { this.send('context-menu:open', type, options); } - updateMenu = (state) => { + updateMenu = (state = {}) => { + console.log('updating menu with state', state); + + console.log('%cupdateMenu ' + state.id, 'background-color: #52b415; color: white'); + console.log(state); + this.send('menu:update', state); } diff --git a/client/src/util/dom/is-input.js b/client/src/util/dom/is-input.js new file mode 100644 index 0000000000..7dd15ccc5d --- /dev/null +++ b/client/src/util/dom/is-input.js @@ -0,0 +1,17 @@ +export function isInput(element) { + return ( + element.tagName === 'TEXTAREA' || + element.tagName === 'INPUT' || + element.contentEditable === 'true' + ); +} + +export function active(element) { + element = element || document.activeElement; + + if (!element) { + element = document.getSelection().focusNode; + } + + return isInput(element); +} \ No newline at end of file