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..e7f5a4a127 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) { @@ -438,20 +441,11 @@ export class App extends Component { const { onTabChanged, - onTabShown, - onToolStateChanged + onTabShown } = this.props; if (prevState.activeTab !== activeTab) { - if (typeof onToolStateChanged === 'function') { - onToolStateChanged(activeTab, { - closable: activeTab !== EMPTY_TAB, - save: activeTab !== EMPTY_TAB, - dirty: this.isDirty(activeTab) - }); - } - if (typeof onTabChanged === 'function') { onTabChanged(activeTab, prevState.activeTab); } @@ -542,6 +536,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 +626,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..0d49f84c9e 100644 --- a/client/src/app/AppParent.js +++ b/client/src/app/AppParent.js @@ -47,10 +47,6 @@ export default class AppParent extends Component { this.getBackend().showContextMenu(type, options); } - handleToolStateChanged = (tab, state) => { - this.getBackend().updateMenu(state); - } - handleReady = () => { this.getBackend().sendReady(); @@ -114,7 +110,6 @@ export default class AppParent extends Component { ref={ this.appRef } tabsProvider={ tabsProvider } globals={ globals } - onToolStateChanged={ this.handleToolStateChanged } onContextMenu={ this.handleContextMenu } onReady={ this.handleReady } /> diff --git a/client/src/app/__tests__/AppSpec.js b/client/src/app/__tests__/AppSpec.js index bee5a14ca0..f192715d89 100644 --- a/client/src/app/__tests__/AppSpec.js +++ b/client/src/app/__tests__/AppSpec.js @@ -27,8 +27,6 @@ describe('', function() { it('tabsProvider'); - it('onToolStateChanged'); - it('onReady'); it('onContextMenu'); @@ -588,7 +586,6 @@ function createApp(options = {}, mountFn=shallow) { }; const onTabShown = options.onTabShown; - const onToolStateChanged = options.onToolStateChanged; const onReady = options.onReady; const tree = mountFn( @@ -599,7 +596,6 @@ function createApp(options = {}, mountFn=shallow) { onReady={ onReady } onTabChanged={ onTabChanged } onTabShown={ onTabShown } - onToolStateChanged={ onToolStateChanged } /> ); diff --git a/client/src/app/tabs/bpmn/BpmnEditor.js b/client/src/app/tabs/bpmn/BpmnEditor.js index 962bdbf9d4..7a20baadf2 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 './getBpmnEditMenu'; 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/app/tabs/xml/getXMLEditMenu.js b/client/src/app/tabs/xml/getXMLEditMenu.js new file mode 100644 index 0000000000..45d28cf30e --- /dev/null +++ b/client/src/app/tabs/xml/getXMLEditMenu.js @@ -0,0 +1,30 @@ +import { + getUndoRedoEntries +} from '../getEditMenu'; + +function getXMLFindEntries() { + return [{ + label: 'Find', + accelerator: 'CommandOrControl+F', + action: 'find' + }, { + label: 'Find Next', + accelerator: 'Shift+CommandOrControl+N', + action: 'findNext' + }, { + label: 'Find Previous', + accelerator: 'Shift+CommandOrControl+P', + action: 'findPrev' + }, { + label: 'Replace', + accelerator: 'Shift+CommandOrControl+F', + action: 'replace' + }]; +} + +export function getXMLEditMenu(state) { + return [ + getUndoRedoEntries(state), + getXMLFindEntries() + ]; +} \ No newline at end of file diff --git a/client/src/remote/Backend.js b/client/src/remote/Backend.js index f482380fd0..dbc6ae2344 100644 --- a/client/src/remote/Backend.js +++ b/client/src/remote/Backend.js @@ -74,7 +74,10 @@ export default class Backend { this.send('context-menu:open', type, options); } - updateMenu = (state) => { + updateMenu = (state = {}) => { + console.log('%cupdateMenu, editor id: ' + state.id + ', state:', '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