From 7c29819e07cfe85b38ec0237aae05e9e0174624e Mon Sep 17 00:00:00 2001 From: Roman Rodin Date: Tue, 11 Feb 2020 09:27:04 +0300 Subject: [PATCH] Diagram - redesign (add history toolbar and view toolbar) (#11944) * Diagram - add history toolbar + viewSettings toolbar - first commit * Diagram - commands refactoring * Diagram - add an ability to check context menu items * Diagram - context menus refactoring * Diagram - context menu icons fix * Diagram - fix select box commands * Diagram - toolbars refactoring + tests * Fix tests * Fix tests * Fix tests --- js/docEnums.js | 2 +- js/ui/diagram.d.ts | 20 +- js/ui/diagram/diagram.bar.js | 13 + ...ommands.js => diagram.commands_manager.js} | 445 ++++++------ js/ui/diagram/ui.diagram.context_menu.js | 139 +++- js/ui/diagram/ui.diagram.dialogs.js | 12 +- js/ui/diagram/ui.diagram.floating_panel.js | 36 + js/ui/diagram/ui.diagram.history_toolbar.js | 10 + js/ui/diagram/ui.diagram.js | 238 +++++-- js/ui/diagram/ui.diagram.main_toolbar.js | 26 + js/ui/diagram/ui.diagram.menu_helper.js | 53 ++ js/ui/diagram/ui.diagram.rightpanel.js | 6 +- js/ui/diagram/ui.diagram.toolbar.js | 237 ++++--- js/ui/diagram/ui.diagram.toolbox.js | 79 +-- js/ui/diagram/ui.diagram.view_toolbar.js | 10 + styles/widgets/common/diagram.less | 45 +- styles/widgets/generic/diagram.generic.less | 15 +- styles/widgets/material/diagram.material.less | 14 +- .../DevExpress.ui.widgets/diagram.tests.js | 638 +----------------- .../diagramParts/clientSideEvents.tests.js | 93 +++ .../diagramParts/commandManager.tests.js | 82 +++ .../diagramParts/contextMenu.tests.js | 62 ++ .../diagramParts/dom.tests.js | 75 ++ .../diagramParts/historyToolbar.tests.js | 41 ++ .../diagramParts/mainToolbar.tests.js | 151 +++++ .../diagramParts/options.tests.js | 240 +++++++ .../diagramParts/propertiesPanel.tests.js | 31 + .../diagramParts/toolbox.tests.js | 42 ++ .../diagramParts/viewToolbar.tests.js | 58 ++ ts/dx.all.d.ts | 6 +- 30 files changed, 1835 insertions(+), 1084 deletions(-) rename js/ui/diagram/{diagram.commands.js => diagram.commands_manager.js} (67%) create mode 100644 js/ui/diagram/ui.diagram.floating_panel.js create mode 100644 js/ui/diagram/ui.diagram.history_toolbar.js create mode 100644 js/ui/diagram/ui.diagram.main_toolbar.js create mode 100644 js/ui/diagram/ui.diagram.menu_helper.js create mode 100644 js/ui/diagram/ui.diagram.view_toolbar.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/clientSideEvents.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/commandManager.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/contextMenu.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/dom.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/historyToolbar.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/mainToolbar.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/propertiesPanel.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/toolbox.tests.js create mode 100644 testing/tests/DevExpress.ui.widgets/diagramParts/viewToolbar.tests.js diff --git a/js/docEnums.js b/js/docEnums.js index 2ba0a7ef37ba..cc59ab1f179f 100644 --- a/js/docEnums.js +++ b/js/docEnums.js @@ -1112,7 +1112,7 @@ /** * @typedef {string} Enums.DiagramToolbarCommand - * @enum {'separator'|'export'|'undo'|'redo'|'cut'|'copy'|'paste'|'selectAll'|'delete'|'fontName'|'fontSize'|'bold'|'italic'|'underline'|'fontColor'|'lineColor'|'fillColor'|'textAlignLeft'|'textAlignCenter'|'textAlignRight'|'lock'|'unlock'|'sendToBack'|'bringToFront'|'insertShapeImage'|'editShapeImage'|'deleteShapeImage'|'connectorLineType'|'connectorLineStart'|'connectorLineEnd'|'autoLayout'|'fullScreen'} + * @enum {'separator'|'export'|'undo'|'redo'|'cut'|'copy'|'paste'|'selectAll'|'delete'|'fontName'|'fontSize'|'bold'|'italic'|'underline'|'fontColor'|'lineColor'|'fillColor'|'textAlignLeft'|'textAlignCenter'|'textAlignRight'|'lock'|'unlock'|'sendToBack'|'bringToFront'|'insertShapeImage'|'editShapeImage'|'deleteShapeImage'|'connectorLineType'|'connectorLineStart'|'connectorLineEnd'|'autoLayout'|'fullScreen'|'zoomLevel'|'autoZoom'|'showGrid'|'snapToGrid'|'gridSize'|'units'} */ /** diff --git a/js/ui/diagram.d.ts b/js/ui/diagram.d.ts index b666cd4ce13b..765badb6b571 100644 --- a/js/ui/diagram.d.ts +++ b/js/ui/diagram.d.ts @@ -206,7 +206,23 @@ export interface dxDiagramOptions extends WidgetOptions { * @prevFileNamespace DevExpress.ui * @public */ - toolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen'>, visible?: boolean }; + toolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen' | 'zoomLevel' | 'autoZoom' | 'showGrid' | 'snapToGrid' | 'gridSize' | 'units'>, visible?: boolean }; + /** + * @docid dxDiagramOptions.historyToolbar + * @type Object + * @default {} + * @prevFileNamespace DevExpress.ui + * @public + */ + historyToolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen' | 'zoomLevel' | 'autoZoom' | 'showGrid' | 'snapToGrid' | 'gridSize' | 'units'>, visible?: boolean }; + /** + * @docid dxDiagramOptions.viewToolbar + * @type Object + * @default {} + * @prevFileNamespace DevExpress.ui + * @public + */ + viewToolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen' | 'zoomLevel' | 'autoZoom' | 'showGrid' | 'snapToGrid' | 'gridSize' | 'units'>, visible?: boolean }; /** * @docid dxDiagramOptions.toolbox * @type Object @@ -356,4 +372,4 @@ interface JQuery { export type Options = dxDiagramOptions; /** @deprecated use Options instead */ -export type IOptions = dxDiagramOptions; \ No newline at end of file +export type IOptions = dxDiagramOptions; diff --git a/js/ui/diagram/diagram.bar.js b/js/ui/diagram/diagram.bar.js index f1ef4646cf6c..c29a72d79b01 100644 --- a/js/ui/diagram/diagram.bar.js +++ b/js/ui/diagram/diagram.bar.js @@ -26,6 +26,19 @@ class DiagramBar { isVisible() { // IBar.isVisible(): boolean return true; } + + _getKeys(items) { + const keys = items.reduce((commands, item) => { + if(item.command !== undefined) { + commands.push(item.command); + } + if(item.items) { + commands = commands.concat(this._getKeys(item.items)); + } + return commands; + }, []); + return keys; + } } module.exports = DiagramBar; diff --git a/js/ui/diagram/diagram.commands.js b/js/ui/diagram/diagram.commands_manager.js similarity index 67% rename from js/ui/diagram/diagram.commands.js rename to js/ui/diagram/diagram.commands_manager.js index f394065cbe8e..6db203ac5b66 100644 --- a/js/ui/diagram/diagram.commands.js +++ b/js/ui/diagram/diagram.commands_manager.js @@ -12,41 +12,33 @@ const CSS_CLASSES = { BUTTON_COLOR: 'dx-diagram-color-b', }; -const DiagramCommands = { - getAllToolbarCommands: function() { +const DiagramCommandsManager = { + getAllCommands: function() { const { DiagramCommand } = getDiagram(); - return this.toolbarCommands || - (this.toolbarCommands = { + return this.allCommands || + (this.allCommands = { separator: SEPARATOR, - export: { - widget: 'dxButton', - icon: 'export', - text: messageLocalization.format('dxDiagram-commandExport'), - hint: messageLocalization.format('dxDiagram-commandExport'), - items: [ - { - command: DiagramCommand.ExportSvg, // eslint-disable-line spellcheck/spell-checker - text: messageLocalization.format('dxDiagram-commandExportToSvg'), - getParameter: (widget) => { - return (dataURI) => this._exportTo(widget, dataURI, 'SVG', 'image/svg+xml'); - } - }, - { - command: DiagramCommand.ExportPng, // eslint-disable-line spellcheck/spell-checker - text: messageLocalization.format('dxDiagram-commandExportToPng'), - getParameter: (widget) => { - return (dataURI) => this._exportTo(widget, dataURI, 'PNG', 'image/png'); - } - }, - { - command: DiagramCommand.ExportJpg, // eslint-disable-line spellcheck/spell-checker - text: messageLocalization.format('dxDiagram-commandExportToJpg'), - getParameter: (widget) => { - return (dataURI) => this._exportTo(widget, dataURI, 'JPEG', 'image/jpeg'); - } - } - ] + exportSvg: { + command: DiagramCommand.ExportSvg, // eslint-disable-line spellcheck/spell-checker + text: messageLocalization.format('dxDiagram-commandExportToSvg'), + getParameter: (widget) => { + return (dataURI) => this._exportTo(widget, dataURI, 'SVG', 'image/svg+xml'); + } + }, + exportPng: { + command: DiagramCommand.ExportPng, // eslint-disable-line spellcheck/spell-checker + text: messageLocalization.format('dxDiagram-commandExportToPng'), + getParameter: (widget) => { + return (dataURI) => this._exportTo(widget, dataURI, 'PNG', 'image/png'); + } + }, + exportJpg: { + command: DiagramCommand.ExportJpg, // eslint-disable-line spellcheck/spell-checker + text: messageLocalization.format('dxDiagram-commandExportToJpg'), + getParameter: (widget) => { + return (dataURI) => this._exportTo(widget, dataURI, 'JPEG', 'image/jpeg'); + } }, undo: { command: DiagramCommand.Undo, @@ -85,7 +77,8 @@ const DiagramCommands = { command: DiagramCommand.SelectAll, hint: messageLocalization.format('dxDiagram-commandSelectAll'), text: messageLocalization.format('dxDiagram-commandSelectAll'), - icon: 'dx-diagram-i-button-select-all dx-diagram-i' + icon: 'dx-diagram-i-button-select-all dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-select-all dx-diagram-i' }, delete: { command: DiagramCommand.Delete, @@ -170,40 +163,47 @@ const DiagramCommands = { command: DiagramCommand.Lock, hint: messageLocalization.format('dxDiagram-commandLock'), text: messageLocalization.format('dxDiagram-commandLock'), - icon: 'dx-diagram-i-button-lock dx-diagram-i' + icon: 'dx-diagram-i-button-lock dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-lock dx-diagram-i' }, unlock: { command: DiagramCommand.Unlock, hint: messageLocalization.format('dxDiagram-commandUnlock'), text: messageLocalization.format('dxDiagram-commandUnlock'), - icon: 'dx-diagram-i-button-unlock dx-diagram-i' + icon: 'dx-diagram-i-button-unlock dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-unlock dx-diagram-i' }, bringToFront: { command: DiagramCommand.BringToFront, hint: messageLocalization.format('dxDiagram-commandBringToFront'), text: messageLocalization.format('dxDiagram-commandBringToFront'), - icon: 'dx-diagram-i-button-bring-to-front dx-diagram-i' + icon: 'dx-diagram-i-button-bring-to-front dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-bring-to-front dx-diagram-i' }, sendToBack: { command: DiagramCommand.SendToBack, hint: messageLocalization.format('dxDiagram-commandSendToBack'), text: messageLocalization.format('dxDiagram-commandSendToBack'), - icon: 'dx-diagram-i-button-send-to-back dx-diagram-i' + icon: 'dx-diagram-i-button-send-to-back dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-send-to-back dx-diagram-i' }, insertShapeImage: { command: DiagramCommand.InsertShapeImage, text: messageLocalization.format('dxDiagram-commandInsertShapeImage'), - icon: 'dx-diagram-i-button-image-insert dx-diagram-i' + icon: 'dx-diagram-i-button-image-insert dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-image-insert dx-diagram-i' }, editShapeImage: { command: DiagramCommand.EditShapeImage, text: messageLocalization.format('dxDiagram-commandEditShapeImage'), - icon: 'dx-diagram-i-button-image-edit dx-diagram-i' + icon: 'dx-diagram-i-button-image-edit dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-image-edit dx-diagram-i' }, deleteShapeImage: { command: DiagramCommand.DeleteShapeImage, text: messageLocalization.format('dxDiagram-commandDeleteShapeImage'), - icon: 'dx-diagram-i-button-image-delete dx-diagram-i' + icon: 'dx-diagram-i-button-image-delete dx-diagram-i', + menuIcon: 'dx-diagram-i-menu-image-delete dx-diagram-i' }, connectorLineType: { command: DiagramCommand.ConnectorLineOption, @@ -304,56 +304,42 @@ const DiagramCommands = { text: messageLocalization.format('dxDiagram-commandFullscreen'), icon: 'dx-diagram-i dx-diagram-i-button-fullscreen', cssClass: CSS_CLASSES.BUTTON_COLOR - } - }); - }, - getToolbarCommands: function(commandNames) { - const commands = this.getAllToolbarCommands(); - if(commandNames) { - return commandNames.map(function(cn) { return commands[cn]; }).filter(function(c) { return c; }); - } - return [ - commands['export'], - commands['separator'], - commands['undo'], - commands['redo'], - commands['separator'], - commands['fontName'], - commands['fontSize'], - commands['separator'], - commands['bold'], - commands['italic'], - commands['underline'], - commands['separator'], - commands['fontColor'], - commands['lineColor'], - commands['fillColor'], - commands['separator'], - commands['textAlignLeft'], - commands['textAlignCenter'], - commands['textAlignRight'], - commands['separator'], - commands['connectorLineType'], - commands['connectorLineStart'], - commands['connectorLineEnd'], - commands['separator'], - commands['autoLayout'], - commands['separator'], - commands['fullScreen'] - ]; - }, + }, - getAllPropertyPanelCommands: function() { - const { DiagramCommand } = getDiagram(); - return this.propertyPanelCommands || - (this.propertyPanelCommands = { units: { command: DiagramCommand.ViewUnits, + hint: messageLocalization.format('dxDiagram-commandUnits'), text: messageLocalization.format('dxDiagram-commandUnits'), widget: 'dxSelectBox' }, + simpleView: { + command: DiagramCommand.ToggleSimpleView, + hint: messageLocalization.format('dxDiagram-commandSimpleView'), + text: messageLocalization.format('dxDiagram-commandSimpleView'), + widget: 'dxCheckBox' + }, + showGrid: { + command: DiagramCommand.ShowGrid, + hint: messageLocalization.format('dxDiagram-commandShowGrid'), + text: messageLocalization.format('dxDiagram-commandShowGrid'), + widget: 'dxCheckBox' + }, + snapToGrid: { + command: DiagramCommand.SnapToGrid, + hint: messageLocalization.format('dxDiagram-commandSnapToGrid'), + text: messageLocalization.format('dxDiagram-commandSnapToGrid'), + widget: 'dxCheckBox' + }, + gridSize: { + command: DiagramCommand.GridSize, + hint: messageLocalization.format('dxDiagram-commandGridSize'), + text: messageLocalization.format('dxDiagram-commandGridSize'), + widget: 'dxSelectBox' + }, + pageSize: { command: DiagramCommand.PageSize, + hint: messageLocalization.format('dxDiagram-commandPageSize'), text: messageLocalization.format('dxDiagram-commandPageSize'), widget: 'dxSelectBox', getValue: (v) => JSON.parse(v), @@ -361,6 +347,7 @@ const DiagramCommands = { }, pageOrientation: { command: DiagramCommand.PageLandscape, + hint: messageLocalization.format('dxDiagram-commandPageOrientation'), text: messageLocalization.format('dxDiagram-commandPageOrientation'), widget: 'dxSelectBox', items: [ @@ -370,162 +357,210 @@ const DiagramCommands = { }, pageColor: { command: DiagramCommand.PageColor, + hint: messageLocalization.format('dxDiagram-commandPageColor'), text: messageLocalization.format('dxDiagram-commandPageColor'), widget: 'dxColorBox', }, - showGrid: { - command: DiagramCommand.ShowGrid, - text: messageLocalization.format('dxDiagram-commandShowGrid'), - widget: 'dxCheckBox', - }, - snapToGrid: { - command: DiagramCommand.SnapToGrid, - text: messageLocalization.format('dxDiagram-commandSnapToGrid'), - widget: 'dxCheckBox' - }, - gridSize: { - command: DiagramCommand.GridSize, - text: messageLocalization.format('dxDiagram-commandGridSize'), - widget: 'dxSelectBox' - }, zoomLevel: { command: DiagramCommand.ZoomLevel, + hint: messageLocalization.format('dxDiagram-commandZoomLevel'), text: messageLocalization.format('dxDiagram-commandZoomLevel'), widget: 'dxSelectBox' }, autoZoom: { command: DiagramCommand.ToggleAutoZoom, + hint: messageLocalization.format('dxDiagram-commandAutoZoom'), text: messageLocalization.format('dxDiagram-commandAutoZoom'), widget: 'dxCheckBox' - }, - simpleView: { - command: DiagramCommand.ToggleSimpleView, - text: messageLocalization.format('dxDiagram-commandSimpleView'), - widget: 'dxCheckBox' - }, + } }); }, + getMainToolbarCommands: function(commands) { + const allCommands = this.getAllCommands(); + const mainToolbarCommands = commands ? this._getCustomCommands(allCommands, commands) : + this._getDefaultMainToolbarCommands(allCommands); + return this._prepareToolbarCommands(mainToolbarCommands); + }, + _getDefaultMainToolbarCommands: function(allCommands) { + return [ + allCommands['undo'], + allCommands['redo'], + allCommands['separator'], + allCommands['fontName'], + allCommands['fontSize'], + allCommands['separator'], + allCommands['bold'], + allCommands['italic'], + allCommands['underline'], + allCommands['separator'], + allCommands['fontColor'], + allCommands['lineColor'], + allCommands['fillColor'], + allCommands['separator'], + allCommands['textAlignLeft'], + allCommands['textAlignCenter'], + allCommands['textAlignRight'], + allCommands['separator'], + allCommands['connectorLineType'], + allCommands['connectorLineStart'], + allCommands['connectorLineEnd'], + allCommands['separator'], + allCommands['autoLayout'], + ]; + }, + getHistoryToolbarCommands: function(commands) { + const allCommands = this.getAllCommands(); + const historyToolbarCommands = commands ? this._getCustomCommands(allCommands, commands) : + this._getDefaultHistoryToolbarCommands(allCommands); + return this._prepareToolbarCommands(historyToolbarCommands); + }, + _getDefaultHistoryToolbarCommands: function(allCommands) { + return [ + allCommands['undo'], + allCommands['separator'], + allCommands['redo'] + ]; + }, + getViewToolbarCommands: function(commands) { + const allCommands = this.getAllCommands(); + const viewToolbarCommands = commands ? this._getCustomCommands(allCommands, commands) : + this._getDefaultViewToolbarCommands(allCommands); + return this._prepareToolbarCommands(viewToolbarCommands); + }, + _getDefaultViewToolbarCommands: function(allCommands) { + return [ + allCommands['zoomLevel'], + allCommands['separator'], + allCommands['fullScreen'], + allCommands['separator'], + { + widget: 'dxButton', + icon: 'export', + text: messageLocalization.format('dxDiagram-commandExport'), + hint: messageLocalization.format('dxDiagram-commandExport'), + items: [ + allCommands['exportSvg'], + allCommands['exportPng'], + allCommands['exportJpg'] + ] + }, + { + icon: 'preferences', + hint: messageLocalization.format('dxDiagram-commandProperties'), + text: messageLocalization.format('dxDiagram-commandProperties'), + items: [ + allCommands['units'], + allCommands['separator'], + allCommands['showGrid'], + allCommands['snapToGrid'], + allCommands['gridSize'], + allCommands['separator'], + allCommands['simpleView'] + ] + } + ]; + }, + getDefaultPropertyPanelCommandGroups: function() { return [ - { commands: ['units'] }, - { commands: ['pageSize', 'pageOrientation', 'pageColor'] }, - { commands: ['showGrid', 'snapToGrid', 'gridSize'] }, - { commands: ['zoomLevel', 'autoZoom', 'simpleView'] }, + { commands: ['pageSize', 'pageOrientation', 'pageColor'] } ]; }, - getPropertyPanelCommandsByGroups: function(groups) { - const commands = DiagramCommands.getAllPropertyPanelCommands(); + _getPropertyPanelCommandsByGroups: function(groups) { + const allCommands = this.getAllCommands(); const result = []; groups.forEach(function(g, gi) { g.commands.forEach(function(cn, ci) { - result.push(extend(commands[cn], { + result.push(extend({ beginGroup: gi > 0 && ci === 0 - })); + }, allCommands[cn])); }); }); return result; }, getPropertyPanelCommands: function(commandGroups) { - commandGroups = commandGroups || DiagramCommands.getDefaultPropertyPanelCommandGroups(); - return DiagramCommands.getPropertyPanelCommandsByGroups(commandGroups); + commandGroups = commandGroups || this.getDefaultPropertyPanelCommandGroups(); + return this._getPropertyPanelCommandsByGroups(commandGroups); }, - getAllContextMenuCommands: function() { - const { DiagramCommand } = getDiagram(); - return this.contextMenuCommands || - (this.contextMenuCommands = { - separator: SEPARATOR, - - cut: { - command: DiagramCommand.Cut, - text: messageLocalization.format('dxDiagram-commandCut'), - icon: 'cut' - }, - copy: { - command: DiagramCommand.Copy, - text: messageLocalization.format('dxDiagram-commandCopy'), - icon: 'copy' - }, - paste: { - command: DiagramCommand.PasteInPosition, - text: messageLocalization.format('dxDiagram-commandPaste'), - getParameter: (diagramContextMenu) => { - return diagramContextMenu.clickPosition; - }, - icon: 'paste' - }, - selectAll: { - command: DiagramCommand.SelectAll, - text: messageLocalization.format('dxDiagram-commandSelectAll'), - icon: 'dx-diagram-i-menu-select-all dx-diagram-i' - }, - delete: { - command: DiagramCommand.Delete, - text: messageLocalization.format('dxDiagram-commandDelete'), - icon: 'remove' - }, - bringToFront: { - command: DiagramCommand.BringToFront, - text: messageLocalization.format('dxDiagram-commandBringToFront'), - icon: 'dx-diagram-i-menu-bring-to-front dx-diagram-i' - }, - sendToBack: { - command: DiagramCommand.SendToBack, - text: messageLocalization.format('dxDiagram-commandSendToBack'), - icon: 'dx-diagram-i-menu-send-to-back dx-diagram-i' - }, - lock: { - command: DiagramCommand.Lock, - text: messageLocalization.format('dxDiagram-commandLock'), - icon: 'dx-diagram-i-menu-lock dx-diagram-i' - }, - unlock: { - command: DiagramCommand.Unlock, - text: messageLocalization.format('dxDiagram-commandUnlock'), - icon: 'dx-diagram-i-menu-unlock dx-diagram-i' - }, - insertShapeImage: { - command: DiagramCommand.InsertShapeImage, - text: messageLocalization.format('dxDiagram-commandInsertShapeImage'), - icon: 'dx-diagram-i-menu-image-insert dx-diagram-i' - }, - editShapeImage: { - command: DiagramCommand.EditShapeImage, - text: messageLocalization.format('dxDiagram-commandEditShapeImage'), - icon: 'dx-diagram-i-menu-image-edit dx-diagram-i' - }, - deleteShapeImage: { - command: DiagramCommand.DeleteShapeImage, - text: messageLocalization.format('dxDiagram-commandDeleteShapeImage'), - icon: 'dx-diagram-i-menu-image-delete dx-diagram-i' - } - }); + getContextMenuCommands: function(commands) { + const allCommands = this.getAllCommands(); + const contextMenuCommands = commands ? this._getCustomCommands(allCommands, commands) : + this._getDefaultContextMenuCommands(allCommands); + return this._prepareContextMenuCommands(contextMenuCommands); }, - getContextMenuCommands: function(commandNames) { - const commands = this.getAllContextMenuCommands(); - if(commandNames) { - return commandNames.map(function(cn) { return commands[cn]; }).filter(function(c) { return c; }); - } + _getDefaultContextMenuCommands: function(allCommands) { return [ - commands['cut'], - commands['copy'], - commands['paste'], - commands['delete'], - commands['separator'], - commands['selectAll'], - commands['separator'], - commands['bringToFront'], - commands['sendToBack'], - commands['separator'], - commands['lock'], - commands['unlock'], - commands['separator'], - commands['insertShapeImage'], - commands['editShapeImage'], - commands['deleteShapeImage'] + allCommands['cut'], + allCommands['copy'], + allCommands['paste'], + allCommands['delete'], + allCommands['separator'], + allCommands['selectAll'], + allCommands['separator'], + allCommands['bringToFront'], + allCommands['sendToBack'], + allCommands['separator'], + allCommands['lock'], + allCommands['unlock'], + allCommands['separator'], + allCommands['insertShapeImage'], + allCommands['editShapeImage'], + allCommands['deleteShapeImage'] ]; }, + _getCustomCommands(allCommands, customCommands) { + return customCommands.map(c => { + if(allCommands[c]) { + return allCommands[c]; + } else if(c.text || c.icon) { + const command = { + text: c.text, + icon: c.icon, + onExecuted: c.onClick + }; + if(Array.isArray(c.items)) { + command.items = this._getCustomCommands(allCommands, c.items); + } + return command; + } + }).filter(c => c); + }, + + _prepareContextMenuCommands(commands) { + const result = []; + let beginGroup = false; + commands.forEach(command => { + if(command === SEPARATOR) { + beginGroup = true; + } else { + if(typeof command === 'object') { + if(Array.isArray(command.items)) { + command.items = this._prepareContextMenuCommands(command.items); + } + result.push(extend(command, { + beginGroup: beginGroup + })); + } else { + result.push(command); + } + beginGroup = false; + } + }); + return result; + }, + _prepareToolbarCommands(commands) { + const result = []; + commands.forEach(command => { + if(Array.isArray(command.items)) { + command.items = this._prepareContextMenuCommands(command.items); + } + result.push(command); + }); + return result; + }, + _exportTo(widget, dataURI, format, mimeString) { const window = getWindow(); if(window && window.atob && isFunction(window.Blob)) { @@ -544,4 +579,4 @@ const DiagramCommands = { } }; -module.exports = DiagramCommands; +module.exports = DiagramCommandsManager; diff --git a/js/ui/diagram/ui.diagram.context_menu.js b/js/ui/diagram/ui.diagram.context_menu.js index 405563d3e0de..ff35cd59e4b1 100644 --- a/js/ui/diagram/ui.diagram.context_menu.js +++ b/js/ui/diagram/ui.diagram.context_menu.js @@ -2,7 +2,9 @@ import $ from '../../core/renderer'; import Widget from '../widget/ui.widget'; import ContextMenu from '../context_menu'; -import DiagramCommands from './diagram.commands'; +import DiagramCommandsManager from './diagram.commands_manager'; +import DiagramMenuHelper from './ui.diagram.menu_helper'; + import DiagramBar from './diagram.bar'; import { getDiagram } from './diagram.importer'; @@ -16,18 +18,18 @@ class DiagramContextMenu extends Widget { super._init(); this._createOnVisibleChangedAction(); this._createOnItemClickAction(); - this.bar = new ContextMenuBar(this); this._tempState = undefined; this._commands = []; this._commandToIndexMap = {}; + this.bar = new ContextMenuBar(this); } _initMarkup() { super._initMarkup(); - this._commands = DiagramCommands.getContextMenuCommands(this.option('commands')); + this._commands = this._getCommands(); this._commandToIndexMap = {}; - this._commands.forEach((item, index) => this._commandToIndexMap[item.command] = index); + this._fillCommandToIndexMap(this._commands, []); this._$contextMenuTargetElement = $('
') .addClass(DIAGRAM_TOUCHBAR_TARGET_CLASS) @@ -40,7 +42,7 @@ class DiagramContextMenu extends Widget { this._contextMenuInstance = this._createComponent($contextMenu, ContextMenu, { closeOnOutsideClick: false, showEvent: '', - cssClass: Browser.TouchUI ? DIAGRAM_TOUCHBAR_CLASS : '', + cssClass: Browser.TouchUI ? DIAGRAM_TOUCHBAR_CLASS : DiagramMenuHelper.getContextMenuCssClass(), items: this._getItems(this._commands), focusStateEnabled: false, position: (Browser.TouchUI ? { @@ -48,36 +50,20 @@ class DiagramContextMenu extends Widget { at: { x: 'center', y: 'top' }, of: this._$contextMenuTargetElement } : {}), + itemTemplate: function(itemData, itemIndex, itemElement) { + DiagramMenuHelper.getContextMenuItemTemplate(itemData, itemIndex, itemElement, this._menuHasCheckedItems); + }, onItemClick: ({ itemData }) => this._onItemClick(itemData), onShowing: (e) => { if(this._inOnShowing === true) return; this._inOnShowing = true; this._onVisibleChangedAction({ visible: true, component: this }); - this._contextMenuInstance.option('items', this._getItems(this._commands, true)); + e.component.option('items', this._getItems(this._commands, true)); delete this._inOnShowing; } }); } - _getItems(commands, onlyVisible) { - const items = []; - let beginGroup = false; - commands.forEach(function(command) { - if(command.widget === 'separator') { - beginGroup = true; - } else if(command.visible || !onlyVisible) { - items.push({ - command: command.command, - text: command.text, - icon: command.icon, - getParameter: command.getParameter, - beginGroup: beginGroup - }); - beginGroup = false; - } - }); - return items; - } _show(x, y, selection) { this.clickPosition = { x, y }; const { Browser } = getDiagram(); @@ -112,23 +98,92 @@ class DiagramContextMenu extends Widget { } if(!processed) { - const parameter = this._getExecCommandParameter(itemData); - this.bar.raiseBarCommandExecuted(itemData.command, parameter); + DiagramMenuHelper.onContextMenuItemClick(itemData, this._execDiagramCommand.bind(this)); this._contextMenuInstance.hide(); } } - _getExecCommandParameter(itemData) { - if(itemData.getParameter) { - return itemData.getParameter(this); + _execDiagramCommand(command, value, onExecuted) { + if(command !== undefined) { + this.bar.raiseBarCommandExecuted(command, value); } + + if(typeof onExecuted === 'function') { + onExecuted.call(this); + } + } + + _getCommands() { + return DiagramCommandsManager.getContextMenuCommands(this.option('commands')); + } + _fillCommandToIndexMap(commands, indexPath) { + commands.forEach((command, index) => { + const commandIndexPath = indexPath.concat([index]); + if(command.command !== undefined) { + this._commandToIndexMap[command.command] = commandIndexPath; + } + if(Array.isArray(command.items)) { + this._fillCommandToIndexMap(command.items, commandIndexPath); + } + }); + } + _getCommandByKey(key) { + const indexPath = this._commandToIndexMap[key]; + if(indexPath) { + let command; + let commands = this._commands; + indexPath.forEach(index => { + command = commands[index]; + commands = command.items; + }); + return command; + } + } + _getItems(commands, onlyVisible) { + const result = []; + commands.forEach(command => { + if(command.visible !== false || !onlyVisible) { + result.push(command); + } + }); + return result; } _setItemEnabled(key, enabled) { this._setItemVisible(key, enabled); } _setItemVisible(key, visible) { - if(key in this._commandToIndexMap) { - const command = this._commands[this._commandToIndexMap[key]]; - if(command) command.visible = visible; + const command = this._getCommandByKey(key); + if(command) command.visible = visible; + } + _setItemValue(key, value) { + const command = this._getCommandByKey(key); + if(command) { + if(value === true || value === false) { + this._setHasCheckedItems(-1); + command.checked = value; + } else if(value !== undefined) { + this._setHasCheckedItems(key); + command.items = command.items.map(item => { + return { + 'value': item.value, + 'text': item.text, + 'checked': item.value === value, + 'rootCommand': key + }; + }); + } + } + } + _setItemSubItems(key, items) { + const command = this._getCommandByKey(key); + if(command) { + command.items = items.map(item => { + return { + 'value': DiagramMenuHelper.getItemValue(item), + 'text': item.text, + 'checked': item.checked, + 'rootCommand': key + }; + }); } } _setEnabled(enabled) { @@ -137,6 +192,12 @@ class DiagramContextMenu extends Widget { isVisible() { return this._inOnShowing; } + _setHasCheckedItems(key) { + if(!this._contextMenuInstance._menuHasCheckedItems) { + this._contextMenuInstance._menuHasCheckedItems = {}; + } + this._contextMenuInstance._menuHasCheckedItems[key] = true; + } _createOnVisibleChangedAction() { this._onVisibleChangedAction = this._createActionByOption('onVisibleChanged'); } @@ -161,8 +222,15 @@ class DiagramContextMenu extends Widget { } class ContextMenuBar extends DiagramBar { + constructor(owner) { + super(owner); + } + getCommandKeys() { - return DiagramCommands.getContextMenuCommands().map(c => c.command); + return this._getKeys(this._owner._commands); + } + setItemValue(key, value) { + this._owner._setItemValue(key, value); } setItemEnabled(key, enabled) { this._owner._setItemEnabled(key, enabled); @@ -170,6 +238,9 @@ class ContextMenuBar extends DiagramBar { setItemVisible(key, visible) { this._owner._setItemVisible(key, visible); } + setItemSubItems(key, items) { + this._owner._setItemSubItems(key, items); + } setEnabled(enabled) { this._owner._setEnabled(enabled); } diff --git a/js/ui/diagram/ui.diagram.dialogs.js b/js/ui/diagram/ui.diagram.dialogs.js index e9f6ad7a5d29..19f2c66a1a7c 100644 --- a/js/ui/diagram/ui.diagram.dialogs.js +++ b/js/ui/diagram/ui.diagram.dialogs.js @@ -20,7 +20,7 @@ class DiagramDialog extends Widget { this._$popupElement = $('
') .appendTo(this.$element()); - this._popupInstance = this._createComponent(this._$popupElement, Popup, { + this._popup = this._createComponent(this._$popupElement, Popup, { title: this.option('title'), maxWidth: this.option('maxWidth'), height: this.option('height'), @@ -29,7 +29,7 @@ class DiagramDialog extends Widget { }); } _clean() { - delete this._popupInstance; + delete this._popup; this._$popupElement && this._$popupElement.remove(); } _getDefaultOptions() { @@ -74,7 +74,7 @@ class DiagramDialog extends Widget { case 'maxWidth': case 'height': case 'toolbarItems': - this._popupInstance.option(args.name, args.value); + this._popup.option(args.name, args.value); break; case 'command': this._command = args.value; @@ -96,15 +96,15 @@ class DiagramDialog extends Widget { this._onHiddenAction = this._createActionByOption('onHidden'); } _hide() { - this._popupInstance.hide(); + this._popup.hide(); this._isShown = false; } _show() { - this._popupInstance + this._popup .content() .empty() .append(this._onGetContentAction()); - this._popupInstance.show(); + this._popup.show(); this._isShown = true; } isVisible() { diff --git a/js/ui/diagram/ui.diagram.floating_panel.js b/js/ui/diagram/ui.diagram.floating_panel.js new file mode 100644 index 000000000000..4d2a2358729e --- /dev/null +++ b/js/ui/diagram/ui.diagram.floating_panel.js @@ -0,0 +1,36 @@ +import $ from '../../core/renderer'; +import Widget from '../widget/ui.widget'; +import Popup from '../popup'; + +class DiagramFloatingPanel extends Widget { + _initMarkup() { + super._initMarkup(); + + const $parent = this.$element(); + + const $popupElement = $('
') + .addClass(this._getPopupClass()) + .appendTo($parent); + + this._popup = this._createComponent($popupElement, Popup, this._getPopupOptions()); + } + _getPopupClass() { + return ''; + } + _getPopupOptions() { + const that = this; + return { + animation: null, + shading: false, + focusStateEnabled: false, + height: this.option('height') || 'auto', + position: this.option('position'), + onContentReady: function() { + that._renderPopupContent(that._popup.content()); + } + }; + } + _renderPopupContent($parent) { + } +} +module.exports = DiagramFloatingPanel; diff --git a/js/ui/diagram/ui.diagram.history_toolbar.js b/js/ui/diagram/ui.diagram.history_toolbar.js new file mode 100644 index 000000000000..105bef23da22 --- /dev/null +++ b/js/ui/diagram/ui.diagram.history_toolbar.js @@ -0,0 +1,10 @@ +import DiagramToolbar from './ui.diagram.toolbar'; +import DiagramCommandsManager from './diagram.commands_manager'; + +class DiagramHistoryToolbar extends DiagramToolbar { + _getCommands() { + return DiagramCommandsManager.getHistoryToolbarCommands(this.option('commands')); + } +} + +module.exports = DiagramHistoryToolbar; diff --git a/js/ui/diagram/ui.diagram.js b/js/ui/diagram/ui.diagram.js index 605a8e319699..42db9937b899 100644 --- a/js/ui/diagram/ui.diagram.js +++ b/js/ui/diagram/ui.diagram.js @@ -6,7 +6,18 @@ import registerComponent from '../../core/component_registrator'; import { extend } from '../../core/utils/extend'; import typeUtils from '../../core/utils/type'; import dataCoreUtils from '../../core/utils/data'; -import DiagramToolbar from './ui.diagram.toolbar'; +import positionUtils from '../../animation/position'; +import { getDiagram } from './diagram.importer'; +import { hasWindow, getWindow } from '../../core/utils/window'; +import domUtils from '../../core/utils/dom'; +import eventsEngine from '../../events/core/events_engine'; +import * as eventUtils from '../../events/utils'; +import messageLocalization from '../../localization/message'; +import numberLocalization from '../../localization/number'; + +import DiagramMainToolbar from './ui.diagram.main_toolbar'; +import DiagramHistoryToolbar from './ui.diagram.history_toolbar'; +import DiagramViewToolbar from './ui.diagram.view_toolbar'; import DiagramRightPanel from './ui.diagram.rightpanel'; import DiagramContextMenu from './ui.diagram.context_menu'; import DiagramContextToolbox from './ui.diagram.context_toolbox'; @@ -14,16 +25,9 @@ import DiagramDialog from './ui.diagram.dialogs'; import DiagramToolboxManager from './diagram.toolbox_manager'; import DiagramToolbox from './ui.diagram.toolbox'; import DiagramOptionsUpdateBar from './diagram.options_update'; +import DiagramDialogManager from './ui.diagram.dialog_manager'; import NodesOption from './diagram.nodes_option'; import EdgesOption from './diagram.edges_option'; -import { getDiagram } from './diagram.importer'; -import { hasWindow, getWindow } from '../../core/utils/window'; -import domUtils from '../../core/utils/dom'; -import eventsEngine from '../../events/core/events_engine'; -import * as eventUtils from '../../events/utils'; -import messageLocalization from '../../localization/message'; -import numberLocalization from '../../localization/number'; -import DiagramDialogManager from './ui.diagram.dialog_manager'; const DIAGRAM_CLASS = 'dx-diagram'; const DIAGRAM_FULLSCREEN_CLASS = 'dx-diagram-fullscreen'; @@ -31,7 +35,9 @@ const DIAGRAM_TOOLBAR_WRAPPER_CLASS = DIAGRAM_CLASS + '-toolbar-wrapper'; const DIAGRAM_CONTENT_WRAPPER_CLASS = DIAGRAM_CLASS + '-content-wrapper'; const DIAGRAM_DRAWER_WRAPPER_CLASS = DIAGRAM_CLASS + '-drawer-wrapper'; const DIAGRAM_CONTENT_CLASS = DIAGRAM_CLASS + '-content'; +const DIAGRAM_FLOATING_TOOLBAR_CONTAINER_CLASS = DIAGRAM_CLASS + '-floating-toolbar-container'; const DIAGRAM_LOADING_INDICATOR_CLASS = DIAGRAM_CLASS + '-loading-indicator'; +const DIAGRAM_FLOATING_PANEL_OFFSET = 22; const DIAGRAM_DEFAULT_UNIT = 'in'; const DIAGRAM_DEFAULT_ZOOMLEVEL = 1; @@ -64,15 +70,25 @@ class Diagram extends Widget { const isServerSide = !hasWindow(); this.$element().addClass(DIAGRAM_CLASS); - this._toolbarInstance = undefined; + this._mainToolbar = undefined; if(this.option('toolbar.visible')) { - this._renderToolbar(); + this._renderMainToolbar(); } const $contentWrapper = $('
') .addClass(DIAGRAM_CONTENT_WRAPPER_CLASS) .appendTo(this.$element()); + this._historyToolbar = undefined; + if(this.option('historyToolbar.visible')) { + this._renderHistoryToolbar($contentWrapper); + } + + this._viewToolbar = undefined; + if(this.option('viewToolbar.visible')) { + this._renderViewToolbar($contentWrapper); + } + this._toolbox = undefined; if(this.option('toolbox.visible')) { this._renderToolbox($contentWrapper); @@ -134,7 +150,7 @@ class Diagram extends Widget { component.bar.onChanged.add(this); this._diagramInstance.barManager.registerBar(component.bar); } - _renderToolbar() { + _renderMainToolbar() { const $toolbarWrapper = $('
') .addClass(DIAGRAM_TOOLBAR_WRAPPER_CLASS) .appendTo(this.$element()); @@ -142,20 +158,61 @@ class Diagram extends Widget { if(this.option('propertiesPanel.enabled') && this.option('propertiesPanel.collapsible')) { toolbarWidgetCommandNames.push('options'); } - this._toolbarInstance = this._createComponent($toolbarWrapper, DiagramToolbar, { + this._mainToolbar = this._createComponent($toolbarWrapper, DiagramMainToolbar, { commands: this.option('toolbar.commands'), - onContentReady: (e) => this._registerBar(e.component), + onContentReady: ({ component }) => this._registerBar(component), + onSubMenuVisibleChanged: ({ component }) => this._diagramInstance.barManager.updateBarItemsState(component.bar), onPointerUp: this._onPanelPointerUp.bind(this), export: this.option('export'), widgetCommandNames: toolbarWidgetCommandNames }); } + _adjustFloatingToolbarContainer($container, toolbar, position) { + if(!hasWindow()) return; + + const $toolbarContent = toolbar.$element().find('.dx-toolbar-before'); + $container.width($toolbarContent.width()); + positionUtils.setup($container, position); + } + _renderHistoryToolbar($parent) { + const $container = $('
') + .addClass(DIAGRAM_FLOATING_TOOLBAR_CONTAINER_CLASS) + .appendTo($parent); + this._historyToolbar = this._createComponent($container, DiagramHistoryToolbar, { + commands: this.option('historyToolbar.commands'), + onContentReady: ({ component }) => this._registerBar(component), + onSubMenuVisibleChanged: ({ component }) => this._diagramInstance.barManager.updateBarItemsState(component.bar), + onPointerUp: this._onPanelPointerUp.bind(this) + }); + this._adjustFloatingToolbarContainer($container, this._historyToolbar, { + my: 'left top', + at: 'left top', + of: $parent, + offset: DIAGRAM_FLOATING_PANEL_OFFSET + ' ' + DIAGRAM_FLOATING_PANEL_OFFSET + }); + } _renderToolbox($parent) { const isServerSide = !hasWindow(); const $toolBox = $('
') .appendTo($parent); + let yOffset = DIAGRAM_FLOATING_PANEL_OFFSET; + let height = !isServerSide ? $parent.height() - 2 * DIAGRAM_FLOATING_PANEL_OFFSET : 200; + if(this._historyToolbar && !isServerSide) { + yOffset += this._historyToolbar.$element().height() + DIAGRAM_FLOATING_PANEL_OFFSET; + height -= this._historyToolbar.$element().height() + DIAGRAM_FLOATING_PANEL_OFFSET; + } + if(this._viewToolbar && !isServerSide) { + height -= this._viewToolbar.$element().height() + DIAGRAM_FLOATING_PANEL_OFFSET; + } this._toolbox = this._createComponent($toolBox, DiagramToolbox, { visible: !this.option('readOnly') && !this.option('disabled'), + height: height, + position: { + my: 'left top', + at: 'left top', + of: $parent, + offset: DIAGRAM_FLOATING_PANEL_OFFSET + ' ' + yOffset + }, toolboxGroups: this._getToolboxGroups(), onShapeCategoryRendered: (e) => { if(isServerSide) return; @@ -163,7 +220,7 @@ class Diagram extends Widget { this._diagramInstance.createToolbox(e.$element[0], DIAGRAM_TOOLBOX_ITEM_SIZE, DIAGRAM_TOOLBOX_ITEM_SPACING, { 'data-toggle': e.dataToggle }, - e.shapes || e.category, e.displayMode === 'texts' + e.shapes || e.category, e.displayMode === 'texts', e.width ); }, onFilterChanged: (e) => { @@ -174,40 +231,22 @@ class Diagram extends Widget { onPointerUp: this._onPanelPointerUp.bind(this) }); } - _invalidateContextMenuCommands() { - if(this._contextMenu) { - this._contextMenu.option({ - commands: this.option('contextMenu.commands') - }); - } - } - _invalidatePropertiesPanelGroups() { - if(this._rightPanel) { - this._rightPanel.option({ - propertyGroups: this.option('propertiesPanel.groups') - }); - } - } - _invalidateToolbarCommands() { - if(this._toolbarInstance) { - this._toolbarInstance.option({ - commands: this.option('toolbar.commands') - }); - } - } - _invalidateToolboxGroups() { - if(this._toolbox) { - this._toolbox.option({ - toolboxGroups: this._getToolboxGroups() - }); - } - } - _setToolboxVisible(visible) { - if(this._toolbox) { - this._toolbox.option({ - visible: visible - }); - } + _renderViewToolbar($parent) { + const $container = $('
') + .addClass(DIAGRAM_FLOATING_TOOLBAR_CONTAINER_CLASS) + .appendTo($parent); + this._viewToolbar = this._createComponent($container, DiagramViewToolbar, { + commands: this.option('viewToolbar.commands'), + onContentReady: ({ component }) => this._registerBar(component), + onSubMenuVisibleChanged: ({ component }) => this._diagramInstance.barManager.updateBarItemsState(component.bar), + onPointerUp: this._onPanelPointerUp.bind(this) + }); + this._adjustFloatingToolbarContainer($container, this._viewToolbar, { + my: 'left bottom', + at: 'left bottom', + of: $parent, + offset: DIAGRAM_FLOATING_PANEL_OFFSET + ' -' + DIAGRAM_FLOATING_PANEL_OFFSET + }); } _renderRightPanel($parent) { @@ -225,8 +264,8 @@ class Diagram extends Widget { }); } }); - if(this._toolbarInstance) { - this._toolbarInstance.option('onWidgetCommand', (e) => { + if(this._mainToolbar) { + this._mainToolbar.option('onWidgetCommand', (e) => { if(e.name === 'options') { drawer.toggle(); } @@ -1513,6 +1552,32 @@ class Diagram extends Widget { * @default undefined */ }, + historyToolbar: { + /** + * @name dxDiagramOptions.historyToolbar.visible + * @type boolean + * @default true + */ + visible: true, + /** + * @name dxDiagramOptions.historyToolbar.commands + * @type Array + * @default undefined + */ + }, + viewToolbar: { + /** + * @name dxDiagramOptions.viewToolbar.visible + * @type boolean + * @default true + */ + visible: true, + /** + * @name dxDiagramOptions.viewToolbar.commands + * @type Array + * @default undefined + */ + }, contextMenu: { /** * @name dxDiagramOptions.contextMenu.enabled @@ -1691,6 +1756,57 @@ class Diagram extends Widget { toKey: nativeConnector.toKey }; } + + _invalidateContextMenuCommands() { + if(this._contextMenu) { + this._contextMenu.option({ + commands: this.option('contextMenu.commands') + }); + } + } + _invalidatePropertiesPanelGroups() { + if(this._rightPanel) { + this._rightPanel.option({ + propertyGroups: this.option('propertiesPanel.groups') + }); + } + } + _invalidateMainToolbarCommands() { + if(this._mainToolbar) { + this._mainToolbar.option({ + commands: this.option('toolbar.commands') + }); + } + } + _invalidateHistoryToolbarCommands() { + if(this._historyToolbar) { + this._historyToolbar.option({ + commands: this.option('historyToolbar.commands') + }); + } + } + _invalidateViewToolbarCommands() { + if(this._viewToolbar) { + this._viewToolbar.option({ + commands: this.option('viewToolbar.commands') + }); + } + } + _invalidateToolboxGroups() { + if(this._toolbox) { + this._toolbox.option({ + toolboxGroups: this._getToolboxGroups() + }); + } + } + _setToolboxVisible(visible) { + if(this._toolbox) { + this._toolbox.option({ + visible: visible + }); + } + } + _optionChanged(args) { if(this.optionsUpdateBar.isUpdateLocked()) return; @@ -1800,7 +1916,21 @@ class Diagram extends Widget { break; case 'toolbar': if(args.fullName === 'toolbar.commands') { - this._invalidateToolbarCommands(); + this._invalidateMainToolbarCommands(); + } else { + this._invalidate(); + } + break; + case 'historyToolbar': + if(args.fullName === 'historyToolbar.commands') { + this._invalidateHistoryToolbarCommands(); + } else { + this._invalidate(); + } + break; + case 'viewToolbar': + if(args.fullName === 'viewToolbar.commands') { + this._invalidateViewToolbarCommands(); } else { this._invalidate(); } @@ -1815,8 +1945,8 @@ class Diagram extends Widget { this._createSelectionChangedAction(); break; case 'export': - if(this._toolbarInstance) { - this._toolbarInstance.option('export', args.value); + if(this._mainToolbar) { + this._mainToolbar.option('export', args.value); } break; case 'hasChanges': diff --git a/js/ui/diagram/ui.diagram.main_toolbar.js b/js/ui/diagram/ui.diagram.main_toolbar.js new file mode 100644 index 000000000000..8d9de9251e5c --- /dev/null +++ b/js/ui/diagram/ui.diagram.main_toolbar.js @@ -0,0 +1,26 @@ +import messageLocalization from '../../localization/message'; +import DiagramToolbar from './ui.diagram.toolbar'; +import DiagramCommandsManager from './diagram.commands_manager'; + +class DiagramMainToolbar extends DiagramToolbar { + _getCommands() { + return DiagramCommandsManager.getMainToolbarCommands(this.option('commands')); + } + _getAllWidgetCommands() { + return this._widgetCommands || + (this._widgetCommands = [ + { + command: 'options', + icon: 'preferences', + hint: messageLocalization.format('dxDiagram-commandProperties'), + text: messageLocalization.format('dxDiagram-commandProperties'), + } + ]); + } + _getRightAlignedCommands() { + const widgetCommands = this._getWidgetCommands(); + return this._getAllWidgetCommands().filter(function(c) { return widgetCommands.indexOf(c.command) > -1; }); + } +} + +module.exports = DiagramMainToolbar; diff --git a/js/ui/diagram/ui.diagram.menu_helper.js b/js/ui/diagram/ui.diagram.menu_helper.js new file mode 100644 index 000000000000..5c2df5d6a1fe --- /dev/null +++ b/js/ui/diagram/ui.diagram.menu_helper.js @@ -0,0 +1,53 @@ +import $ from '../../core/renderer'; +import { getImageContainer } from '../../core/utils/icon'; + +const DIAGRAM_CONTEXT_MENU_CLASS = 'dx-diagram-contextmenu'; + +const DiagramMenuHelper = { + getContextMenuItemTemplate(itemData, itemIndex, itemElement, menuHasCheckedItems) { + const itemKey = itemData.rootCommand !== undefined ? itemData.rootCommand : -1; + if(itemData.icon && !itemData.checked) { + const $iconElement = getImageContainer(itemData.icon); + itemElement.append($iconElement); + } else if(menuHasCheckedItems && menuHasCheckedItems[itemKey] === true) { + const $checkElement = getImageContainer('check'); + $checkElement.css('visibility', !itemData.checked ? 'hidden' : ''); + itemElement.append($checkElement); + } + itemElement.append('' + itemData.text + ''); + if(Array.isArray(itemData.items) && itemData.items.length > 0) { + const $popoutElement = $('
'); + itemElement.append($popoutElement); + } + }, + getContextMenuCssClass() { + return DIAGRAM_CONTEXT_MENU_CLASS; + }, + onContextMenuItemClick(itemData, actionHandler) { + if(itemData.command !== undefined && (!Array.isArray(itemData.items) || !itemData.items.length)) { + const parameter = DiagramMenuHelper.getItemCommandParameter(itemData); + actionHandler.call(this, itemData.command, parameter, itemData.onExecuted); + } else if(itemData.rootCommand !== undefined && itemData.value !== undefined) { + const parameter = DiagramMenuHelper.getItemCommandParameter(itemData, itemData.value); + actionHandler.call(this, itemData.rootCommand, parameter, itemData.onExecuted); + } else if(itemData.onExecuted) { + actionHandler.call(this, itemData.command, undefined, itemData.onExecuted); + } + }, + getItemValue(item) { + return (typeof item.value === 'object') ? JSON.stringify(item.value) : item.value; + }, + getItemOptionText(indexPath) { + return indexPath.reduce((r, i) => { + return r + `items[${i}].`; + }, ''); + }, + getItemCommandParameter(item, value) { + if(item.getParameter) { + return item.getParameter(this); + } + return value; + } +}; + +module.exports = DiagramMenuHelper; diff --git a/js/ui/diagram/ui.diagram.rightpanel.js b/js/ui/diagram/ui.diagram.rightpanel.js index 52564b055626..3c6ed2fba978 100644 --- a/js/ui/diagram/ui.diagram.rightpanel.js +++ b/js/ui/diagram/ui.diagram.rightpanel.js @@ -2,7 +2,7 @@ import $ from '../../core/renderer'; import DiagramPanel from './ui.diagram.panel'; import Accordion from '../accordion'; import Form from '../form'; -import DiagramCommands from './diagram.commands'; +import DiagramCommandsManager from './diagram.commands_manager'; import { extend } from '../../core/utils/extend'; import messageLocalization from '../../localization/message'; import DiagramBar from './diagram.bar'; @@ -55,7 +55,7 @@ class DiagramRightPanel extends DiagramPanel { }); } _renderOptions($container) { - const commands = DiagramCommands.getPropertyPanelCommands(this.option('propertyGroups')); + const commands = DiagramCommandsManager.getPropertyPanelCommands(this.option('propertyGroups')); this._formInstance = this._createComponent($container, Form, { items: commands.map(item => { return extend(true, { @@ -147,7 +147,7 @@ class DiagramRightPanel extends DiagramPanel { class OptionsDiagramBar extends DiagramBar { getCommandKeys() { - return DiagramCommands.getPropertyPanelCommands().map(c => c.command); + return DiagramCommandsManager.getPropertyPanelCommands().map(c => c.command); } setItemValue(key, value) { this._owner._setItemValue(key, value); diff --git a/js/ui/diagram/ui.diagram.toolbar.js b/js/ui/diagram/ui.diagram.toolbar.js index 4cde58075c90..34a73b42f5a0 100644 --- a/js/ui/diagram/ui.diagram.toolbar.js +++ b/js/ui/diagram/ui.diagram.toolbar.js @@ -1,61 +1,66 @@ import $ from '../../core/renderer'; -import DiagramPanel from './ui.diagram.panel'; import Toolbar from '../toolbar'; import ContextMenu from '../context_menu'; -import DiagramCommands from './diagram.commands'; import DiagramBar from './diagram.bar'; import { extend } from '../../core/utils/extend'; -import messageLocalization from '../../localization/message'; + +import DiagramPanel from './ui.diagram.panel'; +import DiagramMenuHelper from './ui.diagram.menu_helper'; import '../select_box'; import '../color_box'; import '../check_box'; const ACTIVE_FORMAT_CLASS = 'dx-format-active'; -const TOOLBAR_CLASS = 'dx-diagram-toolbar'; -const TOOLBAR_SEPARATOR_CLASS = 'dx-diagram-toolbar-separator'; -const TOOLBAR_MENU_SEPARATOR_CLASS = 'dx-diagram-toolbar-menu-separator'; +const DIAGRAM_TOOLBAR_CLASS = 'dx-diagram-toolbar'; +const DIAGRAM_TOOLBAR_SEPARATOR_CLASS = 'dx-diagram-toolbar-separator'; +const DIAGRAM_TOOLBAR_MENU_SEPARATOR_CLASS = 'dx-diagram-toolbar-menu-separator'; class DiagramToolbar extends DiagramPanel { _init() { - this.bar = new ToolbarDiagramBar(this); + this._commands = []; this._itemHelpers = {}; this._contextMenus = []; + this.bar = new ToolbarDiagramBar(this); + this._createOnWidgetCommand(); + this._createOnSubMenuVisibleChangedAction(); + super._init(); } - _initMarkup() { super._initMarkup(); - const $toolbar = $('
') - .addClass(TOOLBAR_CLASS) - .appendTo(this._$element); + + this._commands = this._getCommands(); + this._itemHelpers = {}; + this._contextMenus = []; + this._rightAlignedCommands = this._getRightAlignedCommands(); + + const $toolbar = this._createMainElement(); this._renderToolbar($toolbar); } + _createMainElement() { + return $('
') + .addClass(DIAGRAM_TOOLBAR_CLASS) + .appendTo(this._$element); + } + _getCommands() { + return []; + } + _getRightAlignedCommands() { + return []; + } _getWidgetCommands() { - return this._widgetCommands || - (this._widgetCommands = [ - { - command: 'options', - icon: 'preferences', - hint: messageLocalization.format('dxDiagram-commandProperties'), - text: messageLocalization.format('dxDiagram-commandProperties'), - } - ]); + return this.option('widgetCommandNames') || []; } _renderToolbar($toolbar) { - const commands = DiagramCommands.getToolbarCommands(this.option('commands')); - const widgetCommandNames = this.option('widgetCommandNames') || []; - const widgetCommands = this._getWidgetCommands().filter(function(c) { return widgetCommandNames.indexOf(c.command) > -1; }); - let dataSource = this._prepareToolbarItems(commands, 'before', this._execDiagramCommand); - dataSource = dataSource.concat(this._prepareToolbarItems(widgetCommands, 'after', this._execWidgetCommand)); - this._toolbarInstance = this._createComponent($toolbar, Toolbar, { - dataSource - }); + let dataSource = []; + dataSource = dataSource.concat(this._prepareToolbarItems(this._commands, 'before', this._execDiagramCommand)); + dataSource = dataSource.concat(this._prepareToolbarItems(this._rightAlignedCommands, 'after', this._execDiagramCommand)); + this._toolbarInstance = this._createComponent($toolbar, Toolbar, { dataSource }); } - _prepareToolbarItems(items, location, actionHandler) { return items.map(item => extend(true, { location: location, locateInMenu: 'auto' }, @@ -69,10 +74,10 @@ class DiagramToolbar extends DiagramPanel { if(item.widget === 'separator') { return { template: (data, index, element) => { - $(element).addClass(TOOLBAR_SEPARATOR_CLASS); + $(element).addClass(DIAGRAM_TOOLBAR_SEPARATOR_CLASS); }, menuItemTemplate: (data, index, element) => { - $(element).addClass(TOOLBAR_MENU_SEPARATOR_CLASS); + $(element).addClass(DIAGRAM_TOOLBAR_MENU_SEPARATOR_CLASS); } }; } @@ -178,8 +183,8 @@ class DiagramToolbar extends DiagramPanel { return { options: { onValueChanged: (e) => { - const parameter = this._getExecCommandParameter(item, e.component.option('value')); - handler.call(this, item.command, parameter); + const parameter = DiagramMenuHelper.getItemCommandParameter(item, e.component.option('value')); + handler.call(this, item.command, parameter, item.onExecuted); } } }; @@ -188,41 +193,38 @@ class DiagramToolbar extends DiagramPanel { return { options: { onClick: (e) => { - const parameter = this._getExecCommandParameter(item); - handler.call(this, item.command, parameter); + const parameter = DiagramMenuHelper.getItemCommandParameter(item); + handler.call(this, item.command, parameter, item.onExecuted); } } }; } } } - _getExecCommandParameter(item, widgetValue) { - if(item.getParameter) { - return item.getParameter(this, widgetValue); - } - return widgetValue; - } _onItemInitialized(widget, item) { - if(item.command !== undefined) { - this._itemHelpers[item.command] = new ToolbarItemHelper(widget); - } + this._addItemHelper(item.command, new ToolbarItemHelper(widget)); } _onItemContentReady(widget, item, actionHandler) { if(widget.NAME === 'dxButton' && item.items) { const $menuContainer = $('
') .appendTo(this.$element()); this._createComponent($menuContainer, ContextMenu, { - dataSource: item.items, - displayExpr: 'text', - valueExpr: 'command', + items: item.items, target: widget.$element(), + cssClass: DiagramMenuHelper.getContextMenuCssClass(), showEvent: 'dxclick', position: { at: 'left bottom' }, - onItemClick: ({ itemData }) => { - if(itemData.command !== undefined) { - const parameter = this._getExecCommandParameter(itemData); - actionHandler.call(this, itemData.command, parameter); - } + itemTemplate: function(itemData, itemIndex, itemElement) { + DiagramMenuHelper.getContextMenuItemTemplate(itemData, itemIndex, itemElement, this._menuHasCheckedItems); + }, + onItemClick: ({ itemData }) => DiagramMenuHelper.onContextMenuItemClick(itemData, actionHandler.bind(this)), + onShowing: (e) => { + if(this._showingSubMenu) return; + + this._showingSubMenu = e.component; + this._onSubMenuVisibleChangedAction({ visible: true, component: this }); + e.component.option('items', item.items); + delete this._showingSubMenu; }, onInitialized: ({ component }) => this._onContextMenuInitialized(component, item, widget), onDisposing: ({ component }) => this._onContextMenuDisposing(component, item) @@ -231,38 +233,51 @@ class DiagramToolbar extends DiagramPanel { } _onContextMenuInitialized(widget, item, rootButton) { this._contextMenus.push(widget); - this._addContextMenuHelper(item.items, widget, [], rootButton); + this._addContextMenuHelper(item, widget, [], rootButton); } - _addContextMenuHelper(items, widget, indexPath, rootButton) { - if(items) { - items.forEach((item, index) => { + _addItemHelper(command, helper) { + if(command !== undefined) { + if(this._itemHelpers[command]) { + throw new Error('Toolbar cannot contain duplicated commands.'); + } + this._itemHelpers[command] = helper; + } + } + _addContextMenuHelper(item, widget, indexPath, rootButton) { + if(item.items) { + item.items.forEach((subItem, index) => { const itemIndexPath = indexPath.concat(index); - this._itemHelpers[item.command] = new ContextMenuItemHelper(widget, itemIndexPath, rootButton); - this._addContextMenuHelper(item.items, widget, itemIndexPath, rootButton); + this._addItemHelper(subItem.command, new ToolbarSubItemHelper(widget, itemIndexPath, subItem.command, rootButton)); + this._addContextMenuHelper(subItem, widget, itemIndexPath, rootButton); }); } } _onContextMenuDisposing(widget, item) { this._contextMenus = this._contextMenus.filter(cm => cm !== widget); } - _execDiagramCommand(command, value) { - if(!this._updateLocked) { - this.bar.raiseBarCommandExecuted(command, value); + _execDiagramCommand(command, value, onExecuted) { + if(!this._updateLocked && command !== undefined) { + const widgetCommands = this._getWidgetCommands(); + if(widgetCommands.indexOf(command) > -1) { + this._onWidgetCommandAction({ name: command }); + } else { + this.bar.raiseBarCommandExecuted(command, value); + } } - } - _execWidgetCommand(command) { - if(!this._updateLocked) { - this._onWidgetCommandAction({ name: command }); + if(typeof onExecuted === 'function') { + onExecuted.call(this); } } - _createOnWidgetCommand() { this._onWidgetCommandAction = this._createActionByOption('onWidgetCommand'); } _setItemEnabled(command, enabled) { if(command in this._itemHelpers) { - this._itemHelpers[command].setEnabled(enabled); + const helper = this._itemHelpers[command]; + if(helper.canUpdate(this._showingSubMenu)) { + helper.setEnabled(enabled); + } } } _setEnabled(enabled) { @@ -273,7 +288,10 @@ class DiagramToolbar extends DiagramPanel { try { this._updateLocked = true; if(command in this._itemHelpers) { - this._itemHelpers[command].setValue(value); + const helper = this._itemHelpers[command]; + if(helper.canUpdate(this._showingSubMenu)) { + helper.setValue(value); + } } } finally { this._updateLocked = false; @@ -282,12 +300,23 @@ class DiagramToolbar extends DiagramPanel { _setItemSubItems(command, items) { this._updateLocked = true; if(command in this._itemHelpers) { - this._itemHelpers[command].setItems(items); + const helper = this._itemHelpers[command]; + if(helper.canUpdate(this._showingSubMenu)) { + helper.setItems(items); + } } this._updateLocked = false; } + _createOnSubMenuVisibleChangedAction() { + this._hasCheckedItems = false; + this._onSubMenuVisibleChangedAction = this._createActionByOption('onSubMenuVisibleChanged'); + } + _optionChanged(args) { switch(args.name) { + case 'onSubMenuVisibleChanged': + this._createOnSubMenuVisibleChangedAction(); + break; case 'onWidgetCommand': this._createOnWidgetCommand(); break; @@ -312,18 +341,7 @@ class DiagramToolbar extends DiagramPanel { class ToolbarDiagramBar extends DiagramBar { getCommandKeys() { - return this.getKeys(DiagramCommands.getToolbarCommands()); - } - getKeys(items) { - return items.reduce((commands, item) => { - if(item.command !== undefined) { - commands.push(item.command); - } - if(item.items) { - commands = commands.concat(this.getKeys(item.items)); - } - return commands; - }, []); + return this._getKeys(this._owner._commands); } setItemValue(key, value) { this._owner._setItemValue(key, value); @@ -343,6 +361,9 @@ class ToolbarItemHelper { constructor(widget) { this._widget = widget; } + canUpdate(showingSubMenu) { + return showingSubMenu === undefined; + } setEnabled(enabled) { this._widget.option('disabled', !enabled); } @@ -356,9 +377,8 @@ class ToolbarItemHelper { setItems(items) { if('items' in this._widget.option()) { this._widget.option('items', items.map(item => { - const value = (typeof item.value === 'object') ? JSON.stringify(item.value) : item.value; return { - 'value': value, + 'value': DiagramMenuHelper.getItemValue(item), 'title': item.text }; })); @@ -366,17 +386,18 @@ class ToolbarItemHelper { } } -class ContextMenuItemHelper extends ToolbarItemHelper { - constructor(widget, indexPath, rootButton) { +class ToolbarSubItemHelper extends ToolbarItemHelper { + constructor(widget, indexPath, rootCommandKey, rootButton) { super(widget); this._indexPath = indexPath; + this._rootCommandKey = rootCommandKey; this._rootButton = rootButton; } + canUpdate(showingSubMenu) { + return super.canUpdate(showingSubMenu) || showingSubMenu === this._widget; + } setEnabled(enabled) { - const optionText = this._indexPath.reduce((r, i) => { - return r + `items[${i}].`; - }, '') + 'disabled'; - this._widget.option(optionText, !enabled); + this._widget.option(this._getItemOptionText() + 'disabled', !enabled); const rootEnabled = this._hasEnabledCommandItems(this._widget.option('items')); this._rootButton.option('disabled', !rootEnabled); } @@ -388,7 +409,43 @@ class ContextMenuItemHelper extends ToolbarItemHelper { } return false; } - setValue(value) { } + setValue(value) { + const optionText = this._getItemOptionText(); + if(value === true || value === false) { + this._setHasCheckedItems(-1); + this._widget.option(optionText + 'checked', value); + } else if(value !== undefined) { + this._setHasCheckedItems(this._rootCommandKey); + this._subItems.forEach((item, index) => { + item.checked = item.value === value; + }); + this._updateItems(); + } + } + setItems(items) { + this._subItems = items.slice(); + this._updateItems(); + } + _setHasCheckedItems(key) { + if(!this._widget._menuHasCheckedItems) { + this._widget._menuHasCheckedItems = {}; + } + this._widget._menuHasCheckedItems[key] = true; + } + _updateItems(items) { + this._widget.option(this._getItemOptionText() + 'items', this._subItems.map(item => { + return { + 'value': DiagramMenuHelper.getItemValue(item), + 'text': item.text, + 'checked': item.checked, + 'widget': this._widget, + 'rootCommand': this._rootCommandKey + }; + })); + } + _getItemOptionText() { + return DiagramMenuHelper.getItemOptionText(this._indexPath); + } } module.exports = DiagramToolbar; diff --git a/js/ui/diagram/ui.diagram.toolbox.js b/js/ui/diagram/ui.diagram.toolbox.js index 9bc8a92bcf16..ab367317e011 100644 --- a/js/ui/diagram/ui.diagram.toolbox.js +++ b/js/ui/diagram/ui.diagram.toolbox.js @@ -1,14 +1,13 @@ import $ from '../../core/renderer'; -import { Deferred } from '../../core/utils/deferred'; +import { extend } from '../../core/utils/extend'; import { hasWindow } from '../../core/utils/window'; -import Widget from '../widget/ui.widget'; -import Popup from '../popup'; +import { Deferred } from '../../core/utils/deferred'; import TextBox from '../text_box'; import Accordion from '../accordion'; import ScrollView from '../scroll_view'; import Tooltip from '../tooltip'; +import DiagramFloatingPanel from './ui.diagram.floating_panel'; -const DIAGRAM_TOOLBOX_SPACING = 22; const DIAGRAM_TOOLBOX_POPUP_WIDTH = 136; const DIAGRAM_TOOLBOX_POPUP_CLASS = 'dx-diagram-toolbox-popup'; const DIAGRAM_TOOLBOX_PANEL_CLASS = 'dx-diagram-toolbox-panel'; @@ -16,7 +15,7 @@ const DIAGRAM_TOOLBOX_INPUT_CLASS = 'dx-diagram-toolbox-input'; const DIAGRAM_TOOLTIP_DATATOGGLE = 'shape-toolbox-tooltip'; const DIAGRAM_SKIP_GESTURE_CLASS = 'dx-skip-gesture-event'; -class DiagramToolbox extends Widget { +class DiagramToolbox extends DiagramFloatingPanel { _init() { super._init(); @@ -28,25 +27,16 @@ class DiagramToolbox extends Widget { _initMarkup() { super._initMarkup(); - const that = this; - const $parent = this.$element(); - - const popupHeight = hasWindow() ? $parent.height() - 2 * DIAGRAM_TOOLBOX_SPACING : 200; - const $popupElement = $('
') - .addClass(DIAGRAM_TOOLBOX_POPUP_CLASS) - .appendTo($parent); - - this._popupInstance = this._createComponent($popupElement, Popup, { - animation: null, + if(this.option('visible')) { + this._popup.show(); + } + } + _getPopupClass() { + return DIAGRAM_TOOLBOX_POPUP_CLASS; + } + _getPopupOptions() { + return extend(super._getPopupOptions(), { width: DIAGRAM_TOOLBOX_POPUP_WIDTH, - height: popupHeight, - position: { - my: 'left top', - at: 'left top', - of: $parent, - offset: DIAGRAM_TOOLBOX_SPACING + ' ' + DIAGRAM_TOOLBOX_SPACING - }, - shading: false, toolbarItems: [{ widget: 'dxButton', location: 'center', @@ -59,21 +49,14 @@ class DiagramToolbox extends Widget { type: 'normal', } }], - onContentReady: function() { - that._renderPopupContent(that._popupInstance.content()); - } }); - - if(this.option('visible')) { - this._popupInstance.show(); - } } _renderPopupContent($parent) { const that = this; const $input = $('
') .addClass(DIAGRAM_TOOLBOX_INPUT_CLASS) .appendTo($parent); - this._searchInputInstance = this._createComponent($input, TextBox, { + this._searchInput = this._createComponent($input, TextBox, { placeholder: 'Search', onValueChanged: function(data) { that._onInputChanged(data.value); @@ -90,15 +73,16 @@ class DiagramToolbox extends Widget { stylingMode: 'outlined', type: 'normal', onClick: function() { - that._searchInputInstance.focus(); + that._searchInput.focus(); } } }] }); - + const searchInputHeight = !hasWindow() ? '100%' : 'calc(100% - ' + this._searchInput.$element().height() + 'px)'; const $panel = $('
') .addClass(DIAGRAM_TOOLBOX_PANEL_CLASS) - .appendTo($parent); + .appendTo($parent) + .height(searchInputHeight); this._renderScrollView($panel); } _renderScrollView($parent) { @@ -107,10 +91,10 @@ class DiagramToolbox extends Widget { const $scrollViewWrapper = $('
') .appendTo($parent); - this._scrollViewInstance = this._createComponent($scrollViewWrapper, ScrollView); + this._scrollView = this._createComponent($scrollViewWrapper, ScrollView); const $accordion = $('
') - .appendTo(this._scrollViewInstance.content()); + .appendTo(this._scrollView.content()); this._renderAccordion($accordion); } @@ -129,12 +113,17 @@ class DiagramToolbox extends Widget { shapes: toolboxGroups[i].shapes, onTemplate: (widget, $element, data) => { const $toolboxElement = $($element); + let toolboxWidth = DIAGRAM_TOOLBOX_POPUP_WIDTH; + if(hasWindow()) { + toolboxWidth -= ($toolboxElement.parent().width() - $toolboxElement.width() + 2); + } this._onShapeCategoryRenderedAction({ category: data.category, displayMode: data.displayMode, dataToggle: DIAGRAM_TOOLTIP_DATATOGGLE, shapes: data.shapes, - $element: $toolboxElement + $element: $toolboxElement, + width: toolboxWidth }); this._toolboxes.push($toolboxElement); @@ -175,7 +164,7 @@ class DiagramToolbox extends Widget { } _renderAccordion($container) { const data = this._getAccordionDataSource(); - this._accordionInstance = this._createComponent($container, Accordion, { + this._accordion = this._createComponent($container, Accordion, { multiple: true, activeStateEnabled: false, focusStateEnabled: false, @@ -192,24 +181,24 @@ class DiagramToolbox extends Widget { for(let i = 0; i < data.length; i++) { if(data[i].expanded === false) { - this._accordionInstance.collapseItem(i); + this._accordion.collapseItem(i); } else if(data[i].expanded === true) { - this._accordionInstance.expandItem(i); + this._accordion.expandItem(i); } } } _updateScrollAnimateSubscription(component) { component._deferredAnimate = new Deferred(); component._deferredAnimate.done(() => { - this._scrollViewInstance.update(); + this._scrollView.update(); this._updateScrollAnimateSubscription(component); }); } _raiseToolboxDragStart() { - this._scrollViewInstance.$element().addClass(DIAGRAM_SKIP_GESTURE_CLASS); + this._scrollView.$element().addClass(DIAGRAM_SKIP_GESTURE_CLASS); } _raiseToolboxDragEnd() { - this._scrollViewInstance.$element().removeClass(DIAGRAM_SKIP_GESTURE_CLASS); + this._scrollView.$element().removeClass(DIAGRAM_SKIP_GESTURE_CLASS); } _onInputChanged(text) { this.filterText = text; @@ -226,10 +215,10 @@ class DiagramToolbox extends Widget { _optionChanged(args) { switch(args.name) { case 'visible': - this._popupInstance.option('visible', args.value); + this._popup.option('visible', args.value); break; case 'toolboxGroups': - this._accordionInstance.option('dataSource', this._getAccordionDataSource()); + this._accordion.option('dataSource', this._getAccordionDataSource()); break; default: super._optionChanged(args); diff --git a/js/ui/diagram/ui.diagram.view_toolbar.js b/js/ui/diagram/ui.diagram.view_toolbar.js new file mode 100644 index 000000000000..10ec13b8cdc8 --- /dev/null +++ b/js/ui/diagram/ui.diagram.view_toolbar.js @@ -0,0 +1,10 @@ +import DiagramToolbar from './ui.diagram.toolbar'; +import DiagramCommandsManager from './diagram.commands_manager'; + +class DiagramViewToolbar extends DiagramToolbar { + _getCommands() { + return DiagramCommandsManager.getViewToolbarCommands(this.option('commands')); + } +} + +module.exports = DiagramViewToolbar; diff --git a/styles/widgets/common/diagram.less b/styles/widgets/common/diagram.less index c2089a8ba215..82a73b692c77 100644 --- a/styles/widgets/common/diagram.less +++ b/styles/widgets/common/diagram.less @@ -20,6 +20,10 @@ .dx-diagram-toolbar-wrapper { padding: 5px; flex-grow: 0; + + .dx-diagram-toolbar { + background-color: transparent; + } } .dx-diagram-content-wrapper { @@ -51,20 +55,6 @@ width: 100%; } - .dx-diagram-toolbar-separator { - height: 100%; - border-left: @TRANSPARENT_BORDER; - } - - .dx-diagram-toolbar-menu-separator { - width: 100%; - border-top: @TRANSPARENT_BORDER; - - &::before { - content: none; - } - } - &.dx-diagram-fullscreen { left: 0; top: 0; @@ -124,6 +114,7 @@ } .dx-diagram-toolbox-panel { + position: relative; height: 100%; width: 100%; overflow-y: auto; @@ -139,6 +130,32 @@ } } +.dx-diagram-floating-toolbar-container { + position: absolute; + z-index: 1000; + width: 100%; + + .dx-diagram-toolbar .dx-toolbar-items-container > * { + padding-left: 0; + padding-right: 0; + } +} + +.dx-diagram-toolbar { + .dx-diagram-toolbar-separator { + height: 100%; + border-left: @TRANSPARENT_BORDER; + } + + .dx-diagram-toolbar-menu-separator { + width: 100%; + border-top: @TRANSPARENT_BORDER; + + &::before { + content: none; + } + } +} .dx-diagram-toolbar, .dx-dropdownmenu-popup { diff --git a/styles/widgets/generic/diagram.generic.less b/styles/widgets/generic/diagram.generic.less index 1969cd2242b2..ed763fd257d7 100644 --- a/styles/widgets/generic/diagram.generic.less +++ b/styles/widgets/generic/diagram.generic.less @@ -7,6 +7,7 @@ @import (once) "./form.generic.less"; @import (once) "./toolbar.generic.less"; @import (once) "./contextMenu.generic.less"; +@import (once) "./overlay.generic.less"; @import (once) "./fileUploader.generic.less"; @import (once) "./popup.generic.less"; @@ -110,6 +111,18 @@ } } +.dx-diagram-floating-toolbar-container { + border: 1px solid @overlay-border-color; + background: @overlay-content-bg; + box-shadow: 0 6px 12px @overlay-content-shadow-color; + border-radius: @popup-border-radius; + padding: 2px; + + .dx-toolbar-item { + padding: 0 2px 0 0; + } +} + .dx-diagram-toolbox-popup { .dx-popup-title.dx-toolbar { font-size: 0.5em; @@ -181,8 +194,6 @@ } .dx-diagram-toolbar { - background-color: transparent; - .dx-format-active:not(.dx-color-format):not(.dx-background-format) { background-color: @diagram-normal-format-active-bg; diff --git a/styles/widgets/material/diagram.material.less b/styles/widgets/material/diagram.material.less index 5ad90eb49109..ef5f1cb8f39f 100644 --- a/styles/widgets/material/diagram.material.less +++ b/styles/widgets/material/diagram.material.less @@ -7,6 +7,7 @@ @import (once) "./form.material.less"; @import (once) "./toolbar.material.less"; @import (once) "./contextMenu.material.less"; +@import (once) "./overlay.material.less"; @import (once) "./fileUploader.material.less"; @import (once) "./popup.material.less"; @@ -119,6 +120,17 @@ } } +.dx-diagram-floating-toolbar-container { + background: @overlay-content-bg; + box-shadow: 0 11px 15px -7px fade(@base-shadow-color, 20%), 0 24px 38px 3px fade(@base-shadow-color, 14%), 0 9px 46px 8px fade(@base-shadow-color, 12%); + border-radius: @popup-border-radius; + padding: 4px; + + .dx-toolbar-item { + padding: 0 4px 0 0; + } +} + .dx-diagram-toolbox-popup { .dx-popup-title.dx-toolbar { font-size: 0.8em; @@ -207,8 +219,6 @@ } .dx-diagram-toolbar { - background-color: transparent; - .dx-format-active:not(.dx-color-format):not(.dx-background-format) { background-color: @diagram-normal-format-active-bg; diff --git a/testing/tests/DevExpress.ui.widgets/diagram.tests.js b/testing/tests/DevExpress.ui.widgets/diagram.tests.js index f4aa4a9ef7af..0ce72a10813e 100644 --- a/testing/tests/DevExpress.ui.widgets/diagram.tests.js +++ b/testing/tests/DevExpress.ui.widgets/diagram.tests.js @@ -1,634 +1,22 @@ import $ from 'jquery'; -const { test } = QUnit; import 'common.css!'; import 'ui/diagram'; -import { DiagramCommand, DataLayoutType } from 'devexpress-diagram'; + +import './diagramParts/dom.tests.js'; +import './diagramParts/mainToolbar.tests.js'; +import './diagramParts/historyToolbar.tests.js'; +import './diagramParts/viewToolbar.tests.js'; +import './diagramParts/contextMenu.tests.js'; +import './diagramParts/propertiesPanel.tests.js'; +import './diagramParts/toolbox.tests.js'; +import './diagramParts/options.tests.js'; +import './diagramParts/commandManager.tests.js'; +import './diagramParts/clientSideEvents.tests.js'; + +export const SIMPLE_DIAGRAM = '{ "shapes": [{ "key":"107", "type":"Ellipsis", "text":"A new ticket", "x":1440, "y":1080, "width":1440, "height":720, "zIndex":0 }] }'; QUnit.testStart(() => { const markup = '
'; $('#qunit-fixture').html(markup); }); -const TOOLBAR_SELECTOR = '.dx-diagram-toolbar'; -const TOOLBOX_SCROLLVIEW_SELECTOR = '.dx-diagram-toolbox-panel .dx-scrollview'; -const TOOLBOX_ACCORDION_SELECTOR = '.dx-diagram-toolbox-panel .dx-accordion'; -const CONTEXT_MENU_SELECTOR = 'div:not(.dx-diagram-toolbar-wrapper) > .dx-has-context-menu'; -const PROPERTIES_PANEL_ACCORDION_SELECTOR = '.dx-diagram-right-panel .dx-accordion'; -const PROPERTIES_PANEL_FORM_SELECTOR = '.dx-diagram-right-panel .dx-accordion .dx-form'; -const TOOLBAR_ITEM_ACTIVE_CLASS = 'dx-format-active'; -const SIMPLE_DIAGRAM = '{ "shapes": [{ "key":"107", "type":"Ellipsis", "text":"A new ticket", "x":1440, "y":1080, "width":1440, "height":720, "zIndex":0 }] }'; -const DX_MENU_ITEM_SELECTOR = '.dx-menu-item'; -const DIAGRAM_FULLSCREEN_CLASS = 'dx-diagram-fullscreen'; - -const moduleConfig = { - beforeEach: function() { - this.$element = $('#diagram').dxDiagram(); - this.instance = this.$element.dxDiagram('instance'); - } -}; - -function getToolbarIcon(button) { - return button.find('.dx-dropdowneditor-field-template-wrapper').find('.dx-diagram-i, .dx-icon'); -} - - -QUnit.module('Diagram DOM Layout', { - beforeEach: function() { - this.clock = sinon.useFakeTimers(); - moduleConfig.beforeEach.apply(this, arguments); - }, - afterEach: function() { - this.clock.restore(); - this.clock.reset(); - } -}, () => { - test('should return correct size of document container in default options', function(assert) { - assertSizes(assert, - this.$element.find('.dxdi-control'), - this.$element.find('.dx-diagram-drawer-wrapper'), - this.instance); - }); - test('should return correct size of document container if options panel is hidden', function(assert) { - this.instance.option('propertiesPanel.enabled', false); - this.clock.tick(10000); - assertSizes(assert, - this.$element.find('.dxdi-control'), - this.$element.find('.dx-diagram-drawer-wrapper'), - this.instance); - }); - - test('should return correct size of document container if toolbox is hidden', function(assert) { - this.instance.option('toolbox.visible', false); - this.clock.tick(10000); - assertSizes(assert, - this.$element.find('.dxdi-control'), - this.$element.find('.dx-diagram-drawer-wrapper'), - this.instance); - }); - - test('should return correct size of document container if toolbar is hidden', function(assert) { - this.instance.option('toolbar.visible', false); - this.clock.tick(10000); - assertSizes(assert, - this.$element.find('.dxdi-control'), - this.$element.find('.dx-diagram-drawer-wrapper'), - this.instance); - }); - - test('should return correct size of document container if all UI is hidden', function(assert) { - this.instance.option('toolbar.visible', false); - this.instance.option('toolbox.visible', false); - this.instance.option('propertiesPanel.enabled', false); - this.clock.tick(10000); - assertSizes(assert, - this.$element.find('.dxdi-control'), - this.$element.find('.dx-diagram-drawer-wrapper'), - this.instance); - }); - - - function assertSizes(assert, $scrollContainer, $actualContainer, inst) { - assert.equal($scrollContainer.width(), $actualContainer.width()); - assert.equal($scrollContainer.height(), $actualContainer.height()); - const coreScrollSize = inst._diagramInstance.render.view.scrollView.getSize(); - assert.equal(coreScrollSize.width, $actualContainer.width()); - assert.equal(coreScrollSize.height, $actualContainer.height()); - } -}); - -QUnit.module('Diagram Toolbar', { - beforeEach: function() { - this.clock = sinon.useFakeTimers(); - moduleConfig.beforeEach.apply(this, arguments); - }, - afterEach: function() { - this.clock.restore(); - this.clock.reset(); - } -}, () => { - test('should not render if toolbar.visible is false', function(assert) { - this.instance.option('toolbar.visible', false); - const $toolbar = this.$element.find(TOOLBAR_SELECTOR); - assert.equal($toolbar.length, 0); - }); - test('should fill toolbar with default items', function(assert) { - const toolbar = this.$element.find(TOOLBAR_SELECTOR).dxToolbar('instance'); - assert.ok(toolbar.option('dataSource').length > 10); - }); - test('should fill toolbar with custom items', function(assert) { - this.instance.option('toolbar.commands', ['export']); - let toolbar = this.$element.find(TOOLBAR_SELECTOR).dxToolbar('instance'); - assert.equal(toolbar.option('dataSource').length, 2); // + show properties panel - - this.instance.option('propertiesPanel.enabled', false); - toolbar = this.$element.find(TOOLBAR_SELECTOR).dxToolbar('instance'); - assert.equal(toolbar.option('dataSource').length, 1); - this.instance.option('propertiesPanel.enabled', true); - this.instance.option('propertiesPanel.collapsible', false); - toolbar = this.$element.find(TOOLBAR_SELECTOR).dxToolbar('instance'); - assert.equal(toolbar.option('dataSource').length, 1); - }); - test('should enable items on diagram request', function(assert) { - const undoButton = findToolbarItem(this.$element, 'undo').dxButton('instance'); - assert.ok(undoButton.option('disabled')); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageLandscape).execute(true); - assert.notOk(undoButton.option('disabled')); - }); - test('should activate items on diagram request', function(assert) { - assert.ok(findToolbarItem(this.$element, 'center').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); - assert.notOk(findToolbarItem(this.$element, 'left').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.TextLeftAlign).execute(true); - assert.notOk(findToolbarItem(this.$element, 'center').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); - assert.ok(findToolbarItem(this.$element, 'left').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); - }); - test('button should raise diagram commands', function(assert) { - assert.notOk(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.TextLeftAlign).getState().value); - findToolbarItem(this.$element, 'left').trigger('dxclick'); - assert.ok(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.TextLeftAlign).getState().value); - }); - test('selectBox should raise diagram commands', function(assert) { - assert.equal(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontName).getState().value, 'Arial'); - const fontSelectBox = this.$element.find(TOOLBAR_SELECTOR).find('.dx-selectbox').eq(0).dxSelectBox('instance'); - fontSelectBox.option('value', 'Arial Black'); - assert.equal(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontName).getState().value, 'Arial Black'); - }); - test('selectboxes with icon items should be replaced with select buttons', function(assert) { - const $selectButtonTemplates = this.$element.find(TOOLBAR_SELECTOR).find('.dx-diagram-select-b').find('.dx-dropdowneditor-field-template-wrapper'); - assert.ok($selectButtonTemplates.length > 0, 'select buttons are rendered'); - const selectButtonsCount = $selectButtonTemplates.length; - assert.equal($selectButtonTemplates.find('.dx-diagram-i').length, selectButtonsCount, 'icons are rendered'); - assert.equal($selectButtonTemplates.find('.dx-textbox')[0].offsetWidth, 0, 'textbox is hidden'); - }); - test('colorboxes should be replaced with color buttons', function(assert) { - const $selectButtonTemplates = this.$element.find(TOOLBAR_SELECTOR).find('.dx-diagram-color-b').find('.dx-dropdowneditor-field-template-wrapper'); - assert.ok($selectButtonTemplates.length > 0, 'color buttons are rendered'); - const selectButtonsCount = $selectButtonTemplates.length; - assert.equal($selectButtonTemplates.find('.dx-diagram-i, .dx-icon').length, selectButtonsCount, 'icons are rendered'); - assert.equal($selectButtonTemplates.find('.dx-textbox')[0].offsetWidth, 0, 'textbox is hidden'); - }); - test('colorbuttons should show an active color', function(assert) { - const colorButton = this.$element.find(TOOLBAR_SELECTOR).find('.dx-diagram-color-b').first(); - assert.equal(getToolbarIcon(colorButton).css('borderBottomColor'), 'rgb(0, 0, 0)'); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontColor).execute('rgb(255, 0, 0)'); - assert.equal(getToolbarIcon(colorButton).css('borderBottomColor'), 'rgb(255, 0, 0)', 'button changed via command'); - colorButton.find('.dx-dropdowneditor-button').trigger('dxclick'); - const $overlayContent = $('.dx-colorbox-overlay'); - $overlayContent.find('.dx-colorview-label-hex').find('.dx-textbox').dxTextBox('instance').option('value', '00ff00'); - $overlayContent.find('.dx-colorview-buttons-container .dx-colorview-apply-button').trigger('dxclick'); - assert.equal(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontColor).getState().value, '#00ff00', 'color changed by color button'); - assert.equal(getToolbarIcon(colorButton).css('borderBottomColor'), 'rgb(0, 255, 0)', 'button changed via coloredit'); - }); - test('colorbutton should show dropdown on icon click', function(assert) { - const colorButton = this.$element.find(TOOLBAR_SELECTOR).find('.dx-diagram-color-b').first(); - const colorBox = colorButton.find('.dx-colorbox').dxColorBox('instance'); - getToolbarIcon(colorButton).trigger('dxclick'); - assert.ok(colorBox.option('opened'), true); - }); - test('call .update() after accordion item collapsing/expanding', function(assert) { - const clock = sinon.useFakeTimers(); - const $scrollView = $('body').find(TOOLBOX_SCROLLVIEW_SELECTOR); - const scrollView = $scrollView.dxScrollView('instance'); - const updateSpy = sinon.spy(scrollView, 'update'); - const $accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR); - $accordion.find('.dx-accordion-item-title').first().trigger('dxclick'); - clock.tick(2000); - assert.equal(updateSpy.callCount, 1, 'scrollView.update() called once'); - clock.restore(); - }); - test('should toggle fullscreen class name on button click', function(assert) { - assert.notOk(this.$element.hasClass(DIAGRAM_FULLSCREEN_CLASS)); - const fullScreenButton = findToolbarItem(this.$element, 'full screen'); - fullScreenButton.trigger('dxclick'); - assert.ok(this.$element.hasClass(DIAGRAM_FULLSCREEN_CLASS)); - fullScreenButton.trigger('dxclick'); - assert.notOk(this.$element.hasClass(DIAGRAM_FULLSCREEN_CLASS)); - }); - test('diagram should be focused after change font family', function(assert) { - const fontSelectBox = this.$element.find(TOOLBAR_SELECTOR).find('.dx-selectbox').eq(0).dxSelectBox('instance'); - fontSelectBox.focus(); - fontSelectBox.open(); - const item = $(document).find('.dx-list-item-content').filter(function() { - return $(this).text().toLowerCase().indexOf('arial black') >= 0; - }); - assert.notEqual(document.activeElement, this.instance._diagramInstance.render.input.inputElement); - item.trigger('dxclick'); - assert.equal(document.activeElement, this.instance._diagramInstance.render.input.inputElement); - }); - test('diagram should be focused after set font bold', function(assert) { - const boldButton = findToolbarItem(this.$element, 'bold'); - assert.notEqual(document.activeElement, this.instance._diagramInstance.render.input.inputElement); - boldButton.trigger('dxclick'); - assert.equal(document.activeElement, this.instance._diagramInstance.render.input.inputElement); - }); - test('Auto Layout button should be disabled when there is no selection', function(assert) { - const button = findToolbarItem(this.$element, 'auto layout').dxButton('instance'); - assert.ok(button.option('disabled')); - }); - test('Auto Layout button should be disabled in Read Only mode', function(assert) { - this.instance.option('contextMenu.commands', ['selectAll']); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); - const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); - contextMenu.show(); - $(contextMenu.itemsContainer().find(DX_MENU_ITEM_SELECTOR).eq(0)).trigger('dxclick'); // Select All - const button = findToolbarItem(this.$element, 'auto layout').dxButton('instance'); - assert.notOk(button.option('disabled')); - this.instance.option('readOnly', true); - assert.ok(button.option('disabled')); - }); -}); - -QUnit.module('Diagram Toolbox', moduleConfig, () => { - test('should not render if toolbox.visible is false', function(assert) { - this.instance.option('toolbox.visible', false); - const $accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR); - assert.equal($accordion.length, 0); - }); - test('should fill toolbox with default items', function(assert) { - const accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR).dxAccordion('instance'); - assert.ok(accordion.option('dataSource').length > 1); - }); - test('should fill toolbox with custom items', function(assert) { - this.instance.option('toolbox.groups', ['general']); - const accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR).dxAccordion('instance'); - assert.equal(accordion.option('dataSource').length, 1); - }); -}); - -QUnit.module('Diagram Properties Panel', moduleConfig, () => { - test('should not render if propertiesPanel.enabled is false', function(assert) { - this.instance.option('propertiesPanel.enabled', false); - const $accordion = this.$element.find(PROPERTIES_PANEL_ACCORDION_SELECTOR); - assert.equal($accordion.length, 0); - }); - test('should fill properties panel with default items', function(assert) { - const form = this.$element.find(PROPERTIES_PANEL_FORM_SELECTOR).dxForm('instance'); - assert.ok(form.option('items').length > 1); - }); - test('should fill toolbox with custom items', function(assert) { - this.instance.option('propertiesPanel.groups', [{ commands: ['units'] }]); - const form = this.$element.find(PROPERTIES_PANEL_FORM_SELECTOR).dxForm('instance'); - assert.equal(form.option('items').length, 1); - }); -}); - -QUnit.module('Context Menu', { - beforeEach: function() { - this.clock = sinon.useFakeTimers(); - moduleConfig.beforeEach.apply(this, arguments); - }, - afterEach: function() { - this.clock.restore(); - this.clock.reset(); - } -}, () => { - test('should not render if contextMenu.enabled is false', function(assert) { - let $contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR); - assert.equal($contextMenu.length, 1); - this.instance.option('contextMenu.enabled', false); - $contextMenu = this.$element.children(CONTEXT_MENU_SELECTOR); - assert.equal($contextMenu.length, 0); - }); - test('should load default items', function(assert) { - const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); - assert.ok(contextMenu.option('items').length > 1); - }); - test('should load custom items', function(assert) { - this.instance.option('contextMenu.commands', ['copy']); - const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); - assert.equal(contextMenu.option('items').length, 1); - }); - test('should update items on showing', function(assert) { - this.instance.option('contextMenu.commands', ['copy', 'selectAll']); - const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); - assert.notOk(contextMenu.option('visible')); - assert.ok(contextMenu.option('items')[0].text.indexOf('Copy') > -1); - contextMenu.show(); - assert.ok(contextMenu.option('visible')); - assert.ok(contextMenu.option('items')[0].text.indexOf('Select All') > -1); - }); - test('should execute commands on click', function(assert) { - this.instance.option('contextMenu.commands', ['selectAll']); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); - const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); - contextMenu.show(); - assert.ok(this.instance._diagramInstance.selection.isEmpty()); - $(contextMenu.itemsContainer().find(DX_MENU_ITEM_SELECTOR).eq(0)).trigger('dxclick'); - assert.notOk(this.instance._diagramInstance.selection.isEmpty()); - }); -}); - -QUnit.module('Options', moduleConfig, () => { - test('should change readOnly property', function(assert) { - assert.notOk(this.instance._diagramInstance.settings.readOnly); - this.instance.option('readOnly', true); - assert.ok(this.instance._diagramInstance.settings.readOnly); - this.instance.option('readOnly', false); - assert.notOk(this.instance._diagramInstance.settings.readOnly); - }); - test('should change zoomLevel property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); - this.instance.option('zoomLevel', 1.5); - assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1.5); - this.instance.option('zoomLevel', 1); - assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); - }); - test('should sync zoomLevel property', function(assert) { - assert.equal(this.instance.option('zoomLevel'), 1); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ZoomLevel).execute(1.5); - assert.equal(this.instance.option('zoomLevel'), 1.5); - }); - test('should change zoomLevel object property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); - assert.equal(this.instance._diagramInstance.settings.zoomLevelItems.length, 7); - this.instance.option('zoomLevel', { value: 1.5, items: [ 1, 1.5 ] }); - assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1.5); - assert.equal(this.instance._diagramInstance.settings.zoomLevelItems.length, 2); - this.instance.option('zoomLevel', 1); - assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); - assert.equal(this.instance._diagramInstance.settings.zoomLevelItems.length, 2); - }); - test('should sync zoomLevel object property', function(assert) { - this.instance.option('zoomLevel', { value: 1.5, items: [ 1, 1.5, 2 ] }); - assert.equal(this.instance.option('zoomLevel.value'), 1.5); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ZoomLevel).execute(2); - assert.equal(this.instance.option('zoomLevel.value'), 2); - }); - test('should change autoZoom property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.autoZoom, 0); - this.instance.option('autoZoom', 'fitContent'); - assert.equal(this.instance._diagramInstance.settings.autoZoom, 1); - this.instance.option('autoZoom', 'fitWidth'); - assert.equal(this.instance._diagramInstance.settings.autoZoom, 2); - this.instance.option('autoZoom', 'disabled'); - assert.equal(this.instance._diagramInstance.settings.autoZoom, 0); - }); - test('should sync autoZoom property', function(assert) { - assert.equal(this.instance.option('autoZoom'), 'disabled'); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.SwitchAutoZoom).execute(1); - assert.equal(this.instance.option('autoZoom'), 'fitContent'); - }); - test('should change fullScreen property', function(assert) { - assert.notOk(this.instance._diagramInstance.settings.fullscreen); - this.instance.option('fullScreen', true); - assert.ok(this.instance._diagramInstance.settings.fullscreen); - this.instance.option('fullScreen', false); - assert.notOk(this.instance._diagramInstance.settings.fullscreen); - }); - test('should sync fullScreen property', function(assert) { - assert.equal(this.instance.option('fullScreen'), false); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Fullscreen).execute(true); - assert.equal(this.instance.option('fullScreen'), true); - }); - test('should change showGrid property', function(assert) { - assert.ok(this.instance._diagramInstance.settings.showGrid); - this.instance.option('showGrid', false); - assert.notOk(this.instance._diagramInstance.settings.showGrid); - this.instance.option('showGrid', true); - assert.ok(this.instance._diagramInstance.settings.showGrid); - }); - test('should sync showGrid property', function(assert) { - assert.equal(this.instance.option('showGrid'), true); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ShowGrid).execute(false); - assert.equal(this.instance.option('showGrid'), false); - }); - test('should change snapToGrid property', function(assert) { - assert.ok(this.instance._diagramInstance.settings.snapToGrid); - this.instance.option('snapToGrid', false); - assert.notOk(this.instance._diagramInstance.settings.snapToGrid); - this.instance.option('snapToGrid', true); - assert.ok(this.instance._diagramInstance.settings.snapToGrid); - }); - test('should sync snapToGrid property', function(assert) { - assert.equal(this.instance.option('snapToGrid'), true); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.SnapToGrid).execute(false); - assert.equal(this.instance.option('snapToGrid'), false); - }); - test('should change gridSize property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.gridSize, 180); - this.instance.option('gridSize', 0.25); - assert.equal(this.instance._diagramInstance.settings.gridSize, 360); - this.instance.option('gridSize', 0.125); - assert.equal(this.instance._diagramInstance.settings.gridSize, 180); - }); - test('should sync gridSize property', function(assert) { - assert.equal(this.instance.option('gridSize'), 0.125); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.GridSize).execute(0.25); - assert.equal(this.instance.option('gridSize'), 0.25); - }); - test('should change gridSize object property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.gridSize, 180); - assert.equal(this.instance._diagramInstance.settings.gridSizeItems.length, 4); - this.instance.option('gridSize', { value: 0.25, items: [0.25, 1] }); - assert.equal(this.instance._diagramInstance.settings.gridSize, 360); - assert.equal(this.instance._diagramInstance.settings.gridSizeItems.length, 2); - this.instance.option('gridSize', 0.125); - assert.equal(this.instance._diagramInstance.settings.gridSize, 180); - assert.equal(this.instance._diagramInstance.settings.gridSizeItems.length, 2); - }); - test('should sync gridSize object property', function(assert) { - this.instance.option('gridSize', { value: 0.25, items: [0.125, 0.25, 1] }); - assert.equal(this.instance.option('gridSize.value'), 0.25); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.GridSize).execute(1); - assert.equal(this.instance.option('gridSize.value'), 1); - }); - test('should change viewUnits property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.viewUnits, 0); - this.instance.option('viewUnits', 'cm'); - assert.equal(this.instance._diagramInstance.settings.viewUnits, 1); - this.instance.option('viewUnits', 'in'); - assert.equal(this.instance._diagramInstance.settings.viewUnits, 0); - }); - test('should sync viewUnits property', function(assert) { - assert.equal(this.instance.option('viewUnits'), 'in'); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ViewUnits).execute(1); - assert.equal(this.instance.option('viewUnits'), 'cm'); - }); - test('should change units property', function(assert) { - assert.equal(this.instance._diagramInstance.model.units, 0); - this.instance.option('units', 'cm'); - assert.equal(this.instance._diagramInstance.model.units, 1); - this.instance.option('units', 'in'); - assert.equal(this.instance._diagramInstance.model.units, 0); - }); - test('should change pageSize property', function(assert) { - assert.equal(this.instance._diagramInstance.model.pageSize.width, 8391); - assert.equal(this.instance._diagramInstance.model.pageSize.height, 11906); - this.instance.option('pageSize', { width: 3, height: 5 }); - assert.equal(this.instance._diagramInstance.model.pageSize.width, 4320); - assert.equal(this.instance._diagramInstance.model.pageSize.height, 7200); - }); - test('should sync pageSize property', function(assert) { - this.instance.option('pageSize', { width: 3, height: 5 }); - assert.equal(this.instance.option('pageSize.width'), 3); - assert.equal(this.instance.option('pageSize.height'), 5); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageSize).execute({ width: 4, height: 6 }); - assert.equal(this.instance.option('pageSize.width'), 4); - assert.equal(this.instance.option('pageSize.height'), 6); - }); - test('should change pageSize object property', function(assert) { - assert.equal(this.instance._diagramInstance.model.pageSize.width, 8391); - assert.equal(this.instance._diagramInstance.model.pageSize.height, 11906); - assert.equal(this.instance._diagramInstance.settings.pageSizeItems.length, 11); - this.instance.option('pageSize', { width: 3, height: 5, items: [{ width: 3, height: 5, text: 'A10' }] }); - assert.equal(this.instance._diagramInstance.model.pageSize.width, 4320); - assert.equal(this.instance._diagramInstance.model.pageSize.height, 7200); - assert.equal(this.instance._diagramInstance.settings.pageSizeItems.length, 1); - }); - test('should sync pageSize object property', function(assert) { - this.instance.option('pageSize', { width: 3, height: 5, items: [{ width: 3, height: 5, text: 'A10' }, { width: 4, height: 6, text: 'A11' }] }); - assert.equal(this.instance.option('pageSize.width'), 3); - assert.equal(this.instance.option('pageSize.height'), 5); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageSize).execute({ width: 4, height: 6 }); - assert.equal(this.instance.option('pageSize.width'), 4); - assert.equal(this.instance.option('pageSize.height'), 6); - }); - test('should change pageOrientation property', function(assert) { - assert.equal(this.instance._diagramInstance.model.pageLandscape, false); - this.instance.option('pageOrientation', 'landscape'); - assert.equal(this.instance._diagramInstance.model.pageLandscape, true); - this.instance.option('pageOrientation', 'portrait'); - assert.equal(this.instance._diagramInstance.model.pageLandscape, false); - }); - test('should sync pageOrientation property', function(assert) { - assert.equal(this.instance.option('pageOrientation'), 'portrait'); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageLandscape).execute(1); - assert.equal(this.instance.option('pageOrientation'), 'landscape'); - }); - test('should change pageColor property', function(assert) { - assert.equal(this.instance._diagramInstance.model.pageColor, -1); // FFFFFF - this.instance.option('pageColor', 'red'); - assert.equal(this.instance._diagramInstance.model.pageColor, -65536); // FF0000 - this.instance.option('pageColor', 'white'); - assert.equal(this.instance._diagramInstance.model.pageColor, -1); // FFFFFF - }); - test('should sync pageColor property', function(assert) { - assert.equal(this.instance.option('pageColor'), '#ffffff'); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageColor).execute('red'); - assert.equal(this.instance.option('pageColor'), '#ff0000'); // FF0000 - }); - test('should change simpleView property', function(assert) { - assert.equal(this.instance._diagramInstance.settings.simpleView, false); - this.instance.option('simpleView', true); - assert.equal(this.instance._diagramInstance.settings.simpleView, true); - this.instance.option('simpleView', false); - assert.equal(this.instance._diagramInstance.settings.simpleView, false); - }); - test('should sync simpleView property', function(assert) { - assert.equal(this.instance.option('simpleView'), false); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ToggleSimpleView).execute(true); - assert.equal(this.instance.option('simpleView'), true); - }); - test('should return correct autoLayout parameters based on the nodes.autoLayout option', function(assert) { - assert.equal(this.instance.option('nodes.autoLayout'), 'auto'); - assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Sugiyama }); - - this.instance.option('nodes.leftExpr', 'left'); - this.instance.option('nodes.topExpr', 'left'); - assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); - this.instance.option('nodes.autoLayout', { type: 'auto' }); - assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); - - this.instance.option('nodes.leftExpr', ''); - assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Sugiyama }); - this.instance.option('nodes.topExpr', ''); - assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Sugiyama }); - - this.instance.option('nodes.autoLayout', 'off'); - assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); - this.instance.option('nodes.autoLayout', { type: 'off' }); - assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); - - this.instance.option('nodes.autoLayout', 'tree'); - assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Tree }); - this.instance.option('nodes.autoLayout', { type: 'tree' }); - assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Tree }); - }); -}); - -QUnit.module('ClientSideEvents', { - beforeEach: function() { - this.clock = sinon.useFakeTimers(); - moduleConfig.beforeEach.apply(this, arguments); - }, - afterEach: function() { - this.clock.restore(); - this.clock.reset(); - } -}, () => { - test('click on unbound diagram', function(assert) { - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); - let clickedItem; - this.instance.option('onItemClick', function(e) { - clickedItem = e.item; - }); - this.instance._diagramInstance.onNativeAction.raise('notifyItemClick', this.instance._diagramInstance.model.findShape('107').toNative()); - assert.equal(clickedItem.id, '107'); - assert.equal(clickedItem.text, 'A new ticket'); - }); - test('selectionchanged on unbound diagram', function(assert) { - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); - let selectedItems; - this.instance.option('onSelectionChanged', function(e) { - selectedItems = e.items; - }); - this.instance._diagramInstance.selection.set([this.instance._diagramInstance.model.findShape('107').key]); - assert.equal(selectedItems.length, 1); - assert.equal(selectedItems[0].id, '107'); - assert.equal(selectedItems[0].text, 'A new ticket'); - }); - test('click on bound diagram', function(assert) { - this.instance.option('nodes.keyExpr', 'key'); - this.instance.option('nodes.textExpr', 'text'); - this.instance.option('edges.keyExpr', 'key'); - this.instance.option('edges.fromKey', 'from'); - this.instance.option('edges.toKey', 'to'); - this.instance.option('nodes.dataSource', [ - { key: '123', text: 'mytext', foo: 'bar' }, - { key: '345', text: 'myconnector' } - ]); - this.instance.option('edges.dataSource', [ - { key: '1', from: '123', to: '345' } - ]); - let clickedItem; - let dblClickedItem; - this.instance.option('onItemClick', function(e) { - clickedItem = e.item; - }); - this.instance.option('onItemDblClick', function(e) { - dblClickedItem = e.item; - }); - this.instance._diagramInstance.onNativeAction.raise('notifyItemClick', this.instance._diagramInstance.model.findShapeByDataKey('123').toNative()); - assert.equal(clickedItem.dataItem.key, '123'); - assert.equal(clickedItem.dataItem.foo, 'bar'); - assert.equal(clickedItem.text, 'mytext'); - assert.equal(dblClickedItem, undefined); - - this.instance._diagramInstance.onNativeAction.raise('notifyItemDblClick', this.instance._diagramInstance.model.findShapeByDataKey('123').toNative()); - assert.equal(dblClickedItem.dataItem.key, '123'); - assert.equal(dblClickedItem.dataItem.foo, 'bar'); - assert.equal(dblClickedItem.text, 'mytext'); - - this.instance._diagramInstance.onNativeAction.raise('notifyItemClick', this.instance._diagramInstance.model.findConnectorByDataKey('1').toNative()); - assert.equal(clickedItem.dataItem.key, '1'); - assert.equal(clickedItem.fromKey, '123'); - assert.equal(clickedItem.toKey, '345'); - }); - - test('hasChanges changes on import or editing of an unbound diagram', function(assert) { - assert.equal(this.instance.option('hasChanges'), false, 'on init'); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); - assert.equal(this.instance.option('hasChanges'), true, 'on import'); - this.instance.option('hasChanges', false); - this.instance._diagramInstance.selection.set(['107']); - this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Bold).execute(true); - assert.equal(this.instance.option('hasChanges'), true, 'on edit'); - }); -}); - -function findToolbarItem($diagram, label) { - return $diagram.find(TOOLBAR_SELECTOR) - .find('.dx-widget') - .filter(function() { - return $(this).text().toLowerCase().indexOf(label) >= 0; - }); -} diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/clientSideEvents.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/clientSideEvents.tests.js new file mode 100644 index 000000000000..5f881f8df3c8 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/clientSideEvents.tests.js @@ -0,0 +1,93 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; +import { DiagramCommand } from 'devexpress-diagram'; +import { SIMPLE_DIAGRAM } from '../diagram.tests.js'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('ClientSideEvents', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('click on unbound diagram', function(assert) { + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); + let clickedItem; + this.instance.option('onItemClick', function(e) { + clickedItem = e.item; + }); + this.instance._diagramInstance.onNativeAction.raise('notifyItemClick', this.instance._diagramInstance.model.findShape('107').toNative()); + assert.equal(clickedItem.id, '107'); + assert.equal(clickedItem.text, 'A new ticket'); + }); + test('selectionchanged on unbound diagram', function(assert) { + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); + let selectedItems; + this.instance.option('onSelectionChanged', function(e) { + selectedItems = e.items; + }); + this.instance._diagramInstance.selection.set([this.instance._diagramInstance.model.findShape('107').key]); + assert.equal(selectedItems.length, 1); + assert.equal(selectedItems[0].id, '107'); + assert.equal(selectedItems[0].text, 'A new ticket'); + }); + test('click on bound diagram', function(assert) { + this.instance.option('nodes.keyExpr', 'key'); + this.instance.option('nodes.textExpr', 'text'); + this.instance.option('edges.keyExpr', 'key'); + this.instance.option('edges.fromKey', 'from'); + this.instance.option('edges.toKey', 'to'); + this.instance.option('nodes.dataSource', [ + { key: '123', text: 'mytext', foo: 'bar' }, + { key: '345', text: 'myconnector' } + ]); + this.instance.option('edges.dataSource', [ + { key: '1', from: '123', to: '345' } + ]); + let clickedItem; + let dblClickedItem; + this.instance.option('onItemClick', function(e) { + clickedItem = e.item; + }); + this.instance.option('onItemDblClick', function(e) { + dblClickedItem = e.item; + }); + this.instance._diagramInstance.onNativeAction.raise('notifyItemClick', this.instance._diagramInstance.model.findShapeByDataKey('123').toNative()); + assert.equal(clickedItem.dataItem.key, '123'); + assert.equal(clickedItem.dataItem.foo, 'bar'); + assert.equal(clickedItem.text, 'mytext'); + assert.equal(dblClickedItem, undefined); + + this.instance._diagramInstance.onNativeAction.raise('notifyItemDblClick', this.instance._diagramInstance.model.findShapeByDataKey('123').toNative()); + assert.equal(dblClickedItem.dataItem.key, '123'); + assert.equal(dblClickedItem.dataItem.foo, 'bar'); + assert.equal(dblClickedItem.text, 'mytext'); + + this.instance._diagramInstance.onNativeAction.raise('notifyItemClick', this.instance._diagramInstance.model.findConnectorByDataKey('1').toNative()); + assert.equal(clickedItem.dataItem.key, '1'); + assert.equal(clickedItem.fromKey, '123'); + assert.equal(clickedItem.toKey, '345'); + }); + + test('hasChanges changes on import or editing of an unbound diagram', function(assert) { + assert.equal(this.instance.option('hasChanges'), false, 'on init'); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); + assert.equal(this.instance.option('hasChanges'), true, 'on import'); + this.instance.option('hasChanges', false); + this.instance._diagramInstance.selection.set(['107']); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Bold).execute(true); + assert.equal(this.instance.option('hasChanges'), true, 'on edit'); + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/commandManager.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/commandManager.tests.js new file mode 100644 index 000000000000..bcebf5bacf59 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/commandManager.tests.js @@ -0,0 +1,82 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; +import DiagramCommandsManager from 'ui/diagram/diagram.commands_manager.js'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('CommandManager', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('default commands', function(assert) { + assert.equal(DiagramCommandsManager.getMainToolbarCommands().length, 23); + assert.equal(DiagramCommandsManager.getHistoryToolbarCommands().length, 3); + assert.equal(DiagramCommandsManager.getViewToolbarCommands().length, 6); + assert.equal(DiagramCommandsManager.getContextMenuCommands().length, 12); + assert.equal(DiagramCommandsManager.getPropertyPanelCommands().length, 3); + }); + test('custom toolbar commands', function(assert) { + const commands = DiagramCommandsManager.getMainToolbarCommands([ + 'copy', + 'paste', + 'separator', + { 'text': 'AAA' }, + { 'icon': 'BBB' }, + { 'xxx': 'CCC' }, + { + 'text': 'DDD', + 'items': [ + 'cut', + 'separator', + 'selectAll' + ] + }, + ]); + assert.equal(commands.length, 6); + assert.equal(commands[5].items.length, 2); + assert.equal(commands[5].items[1].beginGroup, true); + }); + test('custom context menu commands', function(assert) { + const commands = DiagramCommandsManager.getContextMenuCommands([ + 'copy', + 'paste', + 'separator', + { 'text': 'AAA' }, + { 'icon': 'BBB' }, + { 'xxx': 'CCC' }, + { + 'text': 'DDD', + 'items': [ + 'cut', + 'separator', + 'selectAll' + ] + }, + ]); + assert.equal(commands.length, 5); + assert.equal(commands[2].beginGroup, true); + assert.equal(commands[4].items.length, 2); + assert.equal(commands[4].items[1].beginGroup, true); + }); + test('custom properties panel commands', function(assert) { + const commands = DiagramCommandsManager.getPropertyPanelCommands([ + { commands: [ 'gridSize', 'showGrid' ] }, + { commands: [ 'pageSize', 'pageColor' ] } + ]); + assert.equal(commands.length, 4); + assert.equal(commands[2].beginGroup, true); + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/contextMenu.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/contextMenu.tests.js new file mode 100644 index 000000000000..8610abef762c --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/contextMenu.tests.js @@ -0,0 +1,62 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; +import { DiagramCommand } from 'devexpress-diagram'; +import { SIMPLE_DIAGRAM } from '../diagram.tests.js'; + +const CONTEXT_MENU_SELECTOR = 'div:not(.dx-diagram-toolbar-wrapper):not(.dx-diagram-floating-toolbar-container) > .dx-has-context-menu'; +const DX_MENU_ITEM_SELECTOR = '.dx-menu-item'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('Context Menu', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('should not render if contextMenu.enabled is false', function(assert) { + let $contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR); + assert.equal($contextMenu.length, 1); + this.instance.option('contextMenu.enabled', false); + $contextMenu = this.$element.children(CONTEXT_MENU_SELECTOR); + assert.equal($contextMenu.length, 0); + }); + test('should load default items', function(assert) { + const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); + assert.ok(contextMenu.option('items').length > 1); + }); + test('should load custom items', function(assert) { + this.instance.option('contextMenu.commands', ['copy']); + const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); + assert.equal(contextMenu.option('items').length, 1); + }); + test('should update items on showing', function(assert) { + this.instance.option('contextMenu.commands', ['copy', 'selectAll']); + const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); + assert.notOk(contextMenu.option('visible')); + assert.ok(contextMenu.option('items')[0].text.indexOf('Copy') > -1); + contextMenu.show(); + assert.ok(contextMenu.option('visible')); + assert.ok(contextMenu.option('items')[0].text.indexOf('Select All') > -1); + }); + test('should execute commands on click', function(assert) { + this.instance.option('contextMenu.commands', ['selectAll']); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); + const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); + contextMenu.show(); + assert.ok(this.instance._diagramInstance.selection.isEmpty()); + $(contextMenu.itemsContainer().find(DX_MENU_ITEM_SELECTOR).eq(0)).trigger('dxclick'); + assert.notOk(this.instance._diagramInstance.selection.isEmpty()); + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/dom.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/dom.tests.js new file mode 100644 index 000000000000..a57d7d4e4b6b --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/dom.tests.js @@ -0,0 +1,75 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('DOM Layout', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('should return correct size of document container in default options', function(assert) { + assertSizes(assert, + this.$element.find('.dxdi-control'), + this.$element.find('.dx-diagram-drawer-wrapper'), + this.instance); + }); + test('should return correct size of document container if options panel is hidden', function(assert) { + this.instance.option('propertiesPanel.enabled', false); + this.clock.tick(10000); + assertSizes(assert, + this.$element.find('.dxdi-control'), + this.$element.find('.dx-diagram-drawer-wrapper'), + this.instance); + }); + + test('should return correct size of document container if toolbox is hidden', function(assert) { + this.instance.option('toolbox.visible', false); + this.clock.tick(10000); + assertSizes(assert, + this.$element.find('.dxdi-control'), + this.$element.find('.dx-diagram-drawer-wrapper'), + this.instance); + }); + + test('should return correct size of document container if toolbar is hidden', function(assert) { + this.instance.option('toolbar.visible', false); + this.clock.tick(10000); + assertSizes(assert, + this.$element.find('.dxdi-control'), + this.$element.find('.dx-diagram-drawer-wrapper'), + this.instance); + }); + + test('should return correct size of document container if all UI is hidden', function(assert) { + this.instance.option('toolbar.visible', false); + this.instance.option('toolbox.visible', false); + this.instance.option('propertiesPanel.enabled', false); + this.clock.tick(10000); + assertSizes(assert, + this.$element.find('.dxdi-control'), + this.$element.find('.dx-diagram-drawer-wrapper'), + this.instance); + }); + + + function assertSizes(assert, $scrollContainer, $actualContainer, inst) { + assert.equal($scrollContainer.width(), $actualContainer.width()); + assert.equal($scrollContainer.height(), $actualContainer.height()); + const coreScrollSize = inst._diagramInstance.render.view.scrollView.getSize(); + assert.equal(coreScrollSize.width, $actualContainer.width()); + assert.equal(coreScrollSize.height, $actualContainer.height()); + } +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/historyToolbar.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/historyToolbar.tests.js new file mode 100644 index 000000000000..a43f8e52d6ac --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/historyToolbar.tests.js @@ -0,0 +1,41 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; + +const FLOATING_TOOLBAR_SELECTOR = '.dx-diagram-floating-toolbar-container > .dx-diagram-toolbar'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('History Toolbar', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('should not render if toolbar.visible is false', function(assert) { + let $toolbar = this.$element.find(FLOATING_TOOLBAR_SELECTOR); + assert.equal($toolbar.length, 2); + this.instance.option('historyToolbar.visible', false); + $toolbar = this.$element.find(FLOATING_TOOLBAR_SELECTOR); + assert.equal($toolbar.length, 1); + }); + test('should fill toolbar with default items', function(assert) { + const toolbar = $(this.$element.find(FLOATING_TOOLBAR_SELECTOR).get(0)).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 3); + }); + test('should fill toolbar with custom items', function(assert) { + this.instance.option('historyToolbar.commands', ['copy']); + const toolbar = $(this.$element.find(FLOATING_TOOLBAR_SELECTOR).get(0)).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 1); // + show properties panel + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/mainToolbar.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/mainToolbar.tests.js new file mode 100644 index 000000000000..59649dbde723 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/mainToolbar.tests.js @@ -0,0 +1,151 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; +import { DiagramCommand } from 'devexpress-diagram'; +import { SIMPLE_DIAGRAM } from '../diagram.tests.js'; + +const MAIN_TOOLBAR_SELECTOR = '.dx-diagram-toolbar-wrapper > .dx-diagram-toolbar'; +const CONTEXT_MENU_SELECTOR = 'div:not(.dx-diagram-toolbar-wrapper):not(.dx-diagram-floating-toolbar-container) > .dx-has-context-menu'; +const TOOLBAR_ITEM_ACTIVE_CLASS = 'dx-format-active'; +const DX_MENU_ITEM_SELECTOR = '.dx-menu-item'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('Main Toolbar', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('should not render if toolbar.visible is false', function(assert) { + this.instance.option('toolbar.visible', false); + const $toolbar = this.$element.find(MAIN_TOOLBAR_SELECTOR); + assert.equal($toolbar.length, 0); + }); + test('should fill toolbar with default items', function(assert) { + const toolbar = this.$element.find(MAIN_TOOLBAR_SELECTOR).dxToolbar('instance'); + assert.ok(toolbar.option('dataSource').length > 10); + }); + test('should fill toolbar with custom items', function(assert) { + this.instance.option('toolbar.commands', ['exportSvg']); + let toolbar = this.$element.find(MAIN_TOOLBAR_SELECTOR).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 2); // + show properties panel + + this.instance.option('propertiesPanel.enabled', false); + toolbar = this.$element.find(MAIN_TOOLBAR_SELECTOR).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 1); + this.instance.option('propertiesPanel.enabled', true); + this.instance.option('propertiesPanel.collapsible', false); + toolbar = this.$element.find(MAIN_TOOLBAR_SELECTOR).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 1); + }); + test('should enable items on diagram request', function(assert) { + const undoButton = findToolbarItem(this.$element, 'undo').dxButton('instance'); + assert.ok(undoButton.option('disabled')); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageLandscape).execute(true); + assert.notOk(undoButton.option('disabled')); + }); + test('should activate items on diagram request', function(assert) { + assert.ok(findToolbarItem(this.$element, 'center').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); + assert.notOk(findToolbarItem(this.$element, 'left').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.TextLeftAlign).execute(true); + assert.notOk(findToolbarItem(this.$element, 'center').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); + assert.ok(findToolbarItem(this.$element, 'left').hasClass(TOOLBAR_ITEM_ACTIVE_CLASS)); + }); + test('button should raise diagram commands', function(assert) { + assert.notOk(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.TextLeftAlign).getState().value); + findToolbarItem(this.$element, 'left').trigger('dxclick'); + assert.ok(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.TextLeftAlign).getState().value); + }); + test('selectBox should raise diagram commands', function(assert) { + assert.equal(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontName).getState().value, 'Arial'); + const fontSelectBox = this.$element.find(MAIN_TOOLBAR_SELECTOR).find('.dx-selectbox').eq(0).dxSelectBox('instance'); + fontSelectBox.option('value', 'Arial Black'); + assert.equal(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontName).getState().value, 'Arial Black'); + }); + test('selectboxes with icon items should be replaced with select buttons', function(assert) { + const $selectButtonTemplates = this.$element.find(MAIN_TOOLBAR_SELECTOR).find('.dx-diagram-select-b').find('.dx-dropdowneditor-field-template-wrapper'); + assert.ok($selectButtonTemplates.length > 0, 'select buttons are rendered'); + const selectButtonsCount = $selectButtonTemplates.length; + assert.equal($selectButtonTemplates.find('.dx-diagram-i').length, selectButtonsCount, 'icons are rendered'); + assert.equal($selectButtonTemplates.find('.dx-textbox')[0].offsetWidth, 0, 'textbox is hidden'); + }); + test('colorboxes should be replaced with color buttons', function(assert) { + const $selectButtonTemplates = this.$element.find(MAIN_TOOLBAR_SELECTOR).find('.dx-diagram-color-b').find('.dx-dropdowneditor-field-template-wrapper'); + assert.ok($selectButtonTemplates.length > 0, 'color buttons are rendered'); + const selectButtonsCount = $selectButtonTemplates.length; + assert.equal($selectButtonTemplates.find('.dx-diagram-i, .dx-icon').length, selectButtonsCount, 'icons are rendered'); + assert.equal($selectButtonTemplates.find('.dx-textbox')[0].offsetWidth, 0, 'textbox is hidden'); + }); + test('colorbuttons should show an active color', function(assert) { + const colorButton = this.$element.find(MAIN_TOOLBAR_SELECTOR).find('.dx-diagram-color-b').first(); + assert.equal(getToolbarIcon(colorButton).css('borderBottomColor'), 'rgb(0, 0, 0)'); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontColor).execute('rgb(255, 0, 0)'); + assert.equal(getToolbarIcon(colorButton).css('borderBottomColor'), 'rgb(255, 0, 0)', 'button changed via command'); + colorButton.find('.dx-dropdowneditor-button').trigger('dxclick'); + const $overlayContent = $('.dx-colorbox-overlay'); + $overlayContent.find('.dx-colorview-label-hex').find('.dx-textbox').dxTextBox('instance').option('value', '00ff00'); + $overlayContent.find('.dx-colorview-buttons-container .dx-colorview-apply-button').trigger('dxclick'); + assert.equal(this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.FontColor).getState().value, '#00ff00', 'color changed by color button'); + assert.equal(getToolbarIcon(colorButton).css('borderBottomColor'), 'rgb(0, 255, 0)', 'button changed via coloredit'); + }); + test('colorbutton should show dropdown on icon click', function(assert) { + const colorButton = this.$element.find(MAIN_TOOLBAR_SELECTOR).find('.dx-diagram-color-b').first(); + const colorBox = colorButton.find('.dx-colorbox').dxColorBox('instance'); + getToolbarIcon(colorButton).trigger('dxclick'); + assert.ok(colorBox.option('opened'), true); + }); + test('diagram should be focused after change font family', function(assert) { + const fontSelectBox = this.$element.find(MAIN_TOOLBAR_SELECTOR).find('.dx-selectbox').eq(0).dxSelectBox('instance'); + fontSelectBox.focus(); + fontSelectBox.open(); + const item = $(document).find('.dx-list-item-content').filter(function() { + return $(this).text().toLowerCase().indexOf('arial black') >= 0; + }); + assert.notEqual(document.activeElement, this.instance._diagramInstance.render.input.inputElement); + item.trigger('dxclick'); + assert.equal(document.activeElement, this.instance._diagramInstance.render.input.inputElement); + }); + test('diagram should be focused after set font bold', function(assert) { + const boldButton = findToolbarItem(this.$element, 'bold'); + assert.notEqual(document.activeElement, this.instance._diagramInstance.render.input.inputElement); + boldButton.trigger('dxclick'); + assert.equal(document.activeElement, this.instance._diagramInstance.render.input.inputElement); + }); + test('Auto Layout button should be disabled when there is no selection', function(assert) { + const button = findToolbarItem(this.$element, 'auto layout').dxButton('instance'); + assert.ok(button.option('disabled')); + }); + test('Auto Layout button should be disabled in Read Only mode', function(assert) { + this.instance.option('contextMenu.commands', ['selectAll']); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Import).execute(SIMPLE_DIAGRAM); + const contextMenu = this.$element.find(CONTEXT_MENU_SELECTOR).dxContextMenu('instance'); + contextMenu.show(); + $(contextMenu.itemsContainer().find(DX_MENU_ITEM_SELECTOR).eq(0)).trigger('dxclick'); // Select All + const button = findToolbarItem(this.$element, 'auto layout').dxButton('instance'); + assert.notOk(button.option('disabled')); + this.instance.option('readOnly', true); + assert.ok(button.option('disabled')); + }); +}); + +function getToolbarIcon(button) { + return button.find('.dx-dropdowneditor-field-template-wrapper').find('.dx-diagram-i, .dx-icon'); +} +function findToolbarItem($diagramElement, label) { + return $diagramElement.find(MAIN_TOOLBAR_SELECTOR) + .find('.dx-widget') + .filter(function() { + return $(this).text().toLowerCase().indexOf(label) >= 0; + }); +} diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js new file mode 100644 index 000000000000..8ff3596d7262 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js @@ -0,0 +1,240 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; +import { DiagramCommand, DataLayoutType } from 'devexpress-diagram'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('Options', moduleConfig, () => { + test('should change readOnly property', function(assert) { + assert.notOk(this.instance._diagramInstance.settings.readOnly); + this.instance.option('readOnly', true); + assert.ok(this.instance._diagramInstance.settings.readOnly); + this.instance.option('readOnly', false); + assert.notOk(this.instance._diagramInstance.settings.readOnly); + }); + test('should change zoomLevel property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); + this.instance.option('zoomLevel', 1.5); + assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1.5); + this.instance.option('zoomLevel', 1); + assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); + }); + test('should sync zoomLevel property', function(assert) { + assert.equal(this.instance.option('zoomLevel'), 1); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ZoomLevel).execute(1.5); + assert.equal(this.instance.option('zoomLevel'), 1.5); + }); + test('should change zoomLevel object property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); + assert.equal(this.instance._diagramInstance.settings.zoomLevelItems.length, 7); + this.instance.option('zoomLevel', { value: 1.5, items: [ 1, 1.5 ] }); + assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1.5); + assert.equal(this.instance._diagramInstance.settings.zoomLevelItems.length, 2); + this.instance.option('zoomLevel', 1); + assert.equal(this.instance._diagramInstance.settings.zoomLevel, 1); + assert.equal(this.instance._diagramInstance.settings.zoomLevelItems.length, 2); + }); + test('should sync zoomLevel object property', function(assert) { + this.instance.option('zoomLevel', { value: 1.5, items: [ 1, 1.5, 2 ] }); + assert.equal(this.instance.option('zoomLevel.value'), 1.5); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ZoomLevel).execute(2); + assert.equal(this.instance.option('zoomLevel.value'), 2); + }); + test('should change autoZoom property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.autoZoom, 0); + this.instance.option('autoZoom', 'fitContent'); + assert.equal(this.instance._diagramInstance.settings.autoZoom, 1); + this.instance.option('autoZoom', 'fitWidth'); + assert.equal(this.instance._diagramInstance.settings.autoZoom, 2); + this.instance.option('autoZoom', 'disabled'); + assert.equal(this.instance._diagramInstance.settings.autoZoom, 0); + }); + test('should sync autoZoom property', function(assert) { + assert.equal(this.instance.option('autoZoom'), 'disabled'); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.SwitchAutoZoom).execute(1); + assert.equal(this.instance.option('autoZoom'), 'fitContent'); + }); + test('should change fullScreen property', function(assert) { + assert.notOk(this.instance._diagramInstance.settings.fullscreen); + this.instance.option('fullScreen', true); + assert.ok(this.instance._diagramInstance.settings.fullscreen); + this.instance.option('fullScreen', false); + assert.notOk(this.instance._diagramInstance.settings.fullscreen); + }); + test('should sync fullScreen property', function(assert) { + assert.equal(this.instance.option('fullScreen'), false); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.Fullscreen).execute(true); + assert.equal(this.instance.option('fullScreen'), true); + }); + test('should change showGrid property', function(assert) { + assert.ok(this.instance._diagramInstance.settings.showGrid); + this.instance.option('showGrid', false); + assert.notOk(this.instance._diagramInstance.settings.showGrid); + this.instance.option('showGrid', true); + assert.ok(this.instance._diagramInstance.settings.showGrid); + }); + test('should sync showGrid property', function(assert) { + assert.equal(this.instance.option('showGrid'), true); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ShowGrid).execute(false); + assert.equal(this.instance.option('showGrid'), false); + }); + test('should change snapToGrid property', function(assert) { + assert.ok(this.instance._diagramInstance.settings.snapToGrid); + this.instance.option('snapToGrid', false); + assert.notOk(this.instance._diagramInstance.settings.snapToGrid); + this.instance.option('snapToGrid', true); + assert.ok(this.instance._diagramInstance.settings.snapToGrid); + }); + test('should sync snapToGrid property', function(assert) { + assert.equal(this.instance.option('snapToGrid'), true); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.SnapToGrid).execute(false); + assert.equal(this.instance.option('snapToGrid'), false); + }); + test('should change gridSize property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.gridSize, 180); + this.instance.option('gridSize', 0.25); + assert.equal(this.instance._diagramInstance.settings.gridSize, 360); + this.instance.option('gridSize', 0.125); + assert.equal(this.instance._diagramInstance.settings.gridSize, 180); + }); + test('should sync gridSize property', function(assert) { + assert.equal(this.instance.option('gridSize'), 0.125); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.GridSize).execute(0.25); + assert.equal(this.instance.option('gridSize'), 0.25); + }); + test('should change gridSize object property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.gridSize, 180); + assert.equal(this.instance._diagramInstance.settings.gridSizeItems.length, 4); + this.instance.option('gridSize', { value: 0.25, items: [0.25, 1] }); + assert.equal(this.instance._diagramInstance.settings.gridSize, 360); + assert.equal(this.instance._diagramInstance.settings.gridSizeItems.length, 2); + this.instance.option('gridSize', 0.125); + assert.equal(this.instance._diagramInstance.settings.gridSize, 180); + assert.equal(this.instance._diagramInstance.settings.gridSizeItems.length, 2); + }); + test('should sync gridSize object property', function(assert) { + this.instance.option('gridSize', { value: 0.25, items: [0.125, 0.25, 1] }); + assert.equal(this.instance.option('gridSize.value'), 0.25); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.GridSize).execute(1); + assert.equal(this.instance.option('gridSize.value'), 1); + }); + test('should change viewUnits property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.viewUnits, 0); + this.instance.option('viewUnits', 'cm'); + assert.equal(this.instance._diagramInstance.settings.viewUnits, 1); + this.instance.option('viewUnits', 'in'); + assert.equal(this.instance._diagramInstance.settings.viewUnits, 0); + }); + test('should sync viewUnits property', function(assert) { + assert.equal(this.instance.option('viewUnits'), 'in'); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ViewUnits).execute(1); + assert.equal(this.instance.option('viewUnits'), 'cm'); + }); + test('should change units property', function(assert) { + assert.equal(this.instance._diagramInstance.model.units, 0); + this.instance.option('units', 'cm'); + assert.equal(this.instance._diagramInstance.model.units, 1); + this.instance.option('units', 'in'); + assert.equal(this.instance._diagramInstance.model.units, 0); + }); + test('should change pageSize property', function(assert) { + assert.equal(this.instance._diagramInstance.model.pageSize.width, 8391); + assert.equal(this.instance._diagramInstance.model.pageSize.height, 11906); + this.instance.option('pageSize', { width: 3, height: 5 }); + assert.equal(this.instance._diagramInstance.model.pageSize.width, 4320); + assert.equal(this.instance._diagramInstance.model.pageSize.height, 7200); + }); + test('should sync pageSize property', function(assert) { + this.instance.option('pageSize', { width: 3, height: 5 }); + assert.equal(this.instance.option('pageSize.width'), 3); + assert.equal(this.instance.option('pageSize.height'), 5); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageSize).execute({ width: 4, height: 6 }); + assert.equal(this.instance.option('pageSize.width'), 4); + assert.equal(this.instance.option('pageSize.height'), 6); + }); + test('should change pageSize object property', function(assert) { + assert.equal(this.instance._diagramInstance.model.pageSize.width, 8391); + assert.equal(this.instance._diagramInstance.model.pageSize.height, 11906); + assert.equal(this.instance._diagramInstance.settings.pageSizeItems.length, 11); + this.instance.option('pageSize', { width: 3, height: 5, items: [{ width: 3, height: 5, text: 'A10' }] }); + assert.equal(this.instance._diagramInstance.model.pageSize.width, 4320); + assert.equal(this.instance._diagramInstance.model.pageSize.height, 7200); + assert.equal(this.instance._diagramInstance.settings.pageSizeItems.length, 1); + }); + test('should sync pageSize object property', function(assert) { + this.instance.option('pageSize', { width: 3, height: 5, items: [{ width: 3, height: 5, text: 'A10' }, { width: 4, height: 6, text: 'A11' }] }); + assert.equal(this.instance.option('pageSize.width'), 3); + assert.equal(this.instance.option('pageSize.height'), 5); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageSize).execute({ width: 4, height: 6 }); + assert.equal(this.instance.option('pageSize.width'), 4); + assert.equal(this.instance.option('pageSize.height'), 6); + }); + test('should change pageOrientation property', function(assert) { + assert.equal(this.instance._diagramInstance.model.pageLandscape, false); + this.instance.option('pageOrientation', 'landscape'); + assert.equal(this.instance._diagramInstance.model.pageLandscape, true); + this.instance.option('pageOrientation', 'portrait'); + assert.equal(this.instance._diagramInstance.model.pageLandscape, false); + }); + test('should sync pageOrientation property', function(assert) { + assert.equal(this.instance.option('pageOrientation'), 'portrait'); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageLandscape).execute(1); + assert.equal(this.instance.option('pageOrientation'), 'landscape'); + }); + test('should change pageColor property', function(assert) { + assert.equal(this.instance._diagramInstance.model.pageColor, -1); // FFFFFF + this.instance.option('pageColor', 'red'); + assert.equal(this.instance._diagramInstance.model.pageColor, -65536); // FF0000 + this.instance.option('pageColor', 'white'); + assert.equal(this.instance._diagramInstance.model.pageColor, -1); // FFFFFF + }); + test('should sync pageColor property', function(assert) { + assert.equal(this.instance.option('pageColor'), '#ffffff'); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.PageColor).execute('red'); + assert.equal(this.instance.option('pageColor'), '#ff0000'); // FF0000 + }); + test('should change simpleView property', function(assert) { + assert.equal(this.instance._diagramInstance.settings.simpleView, false); + this.instance.option('simpleView', true); + assert.equal(this.instance._diagramInstance.settings.simpleView, true); + this.instance.option('simpleView', false); + assert.equal(this.instance._diagramInstance.settings.simpleView, false); + }); + test('should sync simpleView property', function(assert) { + assert.equal(this.instance.option('simpleView'), false); + this.instance._diagramInstance.commandManager.getCommand(DiagramCommand.ToggleSimpleView).execute(true); + assert.equal(this.instance.option('simpleView'), true); + }); + test('should return correct autoLayout parameters based on the nodes.autoLayout option', function(assert) { + assert.equal(this.instance.option('nodes.autoLayout'), 'auto'); + assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Sugiyama }); + + this.instance.option('nodes.leftExpr', 'left'); + this.instance.option('nodes.topExpr', 'left'); + assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); + this.instance.option('nodes.autoLayout', { type: 'auto' }); + assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); + + this.instance.option('nodes.leftExpr', ''); + assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Sugiyama }); + this.instance.option('nodes.topExpr', ''); + assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Sugiyama }); + + this.instance.option('nodes.autoLayout', 'off'); + assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); + this.instance.option('nodes.autoLayout', { type: 'off' }); + assert.equal(this.instance._getDataBindingLayoutParameters(), undefined); + + this.instance.option('nodes.autoLayout', 'tree'); + assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Tree }); + this.instance.option('nodes.autoLayout', { type: 'tree' }); + assert.deepEqual(this.instance._getDataBindingLayoutParameters(), { type: DataLayoutType.Tree }); + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/propertiesPanel.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/propertiesPanel.tests.js new file mode 100644 index 000000000000..1dd94ac2b3b1 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/propertiesPanel.tests.js @@ -0,0 +1,31 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; + +const PROPERTIES_PANEL_ACCORDION_SELECTOR = '.dx-diagram-right-panel .dx-accordion'; +const PROPERTIES_PANEL_FORM_SELECTOR = '.dx-diagram-right-panel .dx-accordion .dx-form'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('Properties Panel', moduleConfig, () => { + test('should not render if propertiesPanel.enabled is false', function(assert) { + this.instance.option('propertiesPanel.enabled', false); + const $accordion = this.$element.find(PROPERTIES_PANEL_ACCORDION_SELECTOR); + assert.equal($accordion.length, 0); + }); + test('should fill properties panel with default items', function(assert) { + const form = this.$element.find(PROPERTIES_PANEL_FORM_SELECTOR).dxForm('instance'); + assert.ok(form.option('items').length > 1); + }); + test('should fill toolbox with custom items', function(assert) { + this.instance.option('propertiesPanel.groups', [{ commands: ['units'] }]); + const form = this.$element.find(PROPERTIES_PANEL_FORM_SELECTOR).dxForm('instance'); + assert.equal(form.option('items').length, 1); + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/toolbox.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/toolbox.tests.js new file mode 100644 index 000000000000..37f1b0b23e21 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/toolbox.tests.js @@ -0,0 +1,42 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; + +const TOOLBOX_SCROLLVIEW_SELECTOR = '.dx-diagram-toolbox-panel .dx-scrollview'; +const TOOLBOX_ACCORDION_SELECTOR = '.dx-diagram-toolbox-panel .dx-accordion'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('Toolbox', moduleConfig, () => { + test('should not render if toolbox.visible is false', function(assert) { + this.instance.option('toolbox.visible', false); + const $accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR); + assert.equal($accordion.length, 0); + }); + test('should fill toolbox with default items', function(assert) { + const accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR).dxAccordion('instance'); + assert.ok(accordion.option('dataSource').length > 1); + }); + test('should fill toolbox with custom items', function(assert) { + this.instance.option('toolbox.groups', ['general']); + const accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR).dxAccordion('instance'); + assert.equal(accordion.option('dataSource').length, 1); + }); + test('call .update() after accordion item collapsing/expanding', function(assert) { + const clock = sinon.useFakeTimers(); + const $scrollView = $('body').find(TOOLBOX_SCROLLVIEW_SELECTOR); + const scrollView = $scrollView.dxScrollView('instance'); + const updateSpy = sinon.spy(scrollView, 'update'); + const $accordion = $('body').find(TOOLBOX_ACCORDION_SELECTOR); + $accordion.find('.dx-accordion-item-title').first().trigger('dxclick'); + clock.tick(2000); + assert.equal(updateSpy.callCount, 1, 'scrollView.update() called once'); + clock.restore(); + }); +}); diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/viewToolbar.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/viewToolbar.tests.js new file mode 100644 index 000000000000..1df77432ddca --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/viewToolbar.tests.js @@ -0,0 +1,58 @@ +import $ from 'jquery'; +const { test } = QUnit; +import 'common.css!'; +import 'ui/diagram'; + +const FLOATING_TOOLBAR_SELECTOR = '.dx-diagram-floating-toolbar-container > .dx-diagram-toolbar'; +const DIAGRAM_FULLSCREEN_CLASS = 'dx-diagram-fullscreen'; + +const moduleConfig = { + beforeEach: function() { + this.$element = $('#diagram').dxDiagram(); + this.instance = this.$element.dxDiagram('instance'); + } +}; + +QUnit.module('View Toolbar', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + moduleConfig.beforeEach.apply(this, arguments); + }, + afterEach: function() { + this.clock.restore(); + this.clock.reset(); + } +}, () => { + test('should not render if toolbar.visible is false', function(assert) { + let $toolbar = this.$element.find(FLOATING_TOOLBAR_SELECTOR); + assert.equal($toolbar.length, 2); + this.instance.option('viewToolbar.visible', false); + $toolbar = this.$element.find(FLOATING_TOOLBAR_SELECTOR); + assert.equal($toolbar.length, 1); + }); + test('should fill toolbar with default items', function(assert) { + const toolbar = $(this.$element.find(FLOATING_TOOLBAR_SELECTOR).get(1)).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 6); + }); + test('should fill toolbar with custom items', function(assert) { + this.instance.option('viewToolbar.commands', ['copy']); + const toolbar = $(this.$element.find(FLOATING_TOOLBAR_SELECTOR).get(1)).dxToolbar('instance'); + assert.equal(toolbar.option('dataSource').length, 1); // + show properties panel + }); + test('should toggle fullscreen class name on button click', function(assert) { + assert.notOk(this.$element.hasClass(DIAGRAM_FULLSCREEN_CLASS)); + const fullScreenButton = findToolbarItem(this.$element, 'full screen'); + fullScreenButton.trigger('dxclick'); + assert.ok(this.$element.hasClass(DIAGRAM_FULLSCREEN_CLASS)); + fullScreenButton.trigger('dxclick'); + assert.notOk(this.$element.hasClass(DIAGRAM_FULLSCREEN_CLASS)); + }); +}); + +function findToolbarItem($diagramElement, label) { + return $($diagramElement.find(FLOATING_TOOLBAR_SELECTOR).get(1)) + .find('.dx-widget') + .filter(function() { + return $(this).text().toLowerCase().indexOf(label) >= 0; + }); +} diff --git a/ts/dx.all.d.ts b/ts/dx.all.d.ts index f4fc62f05f81..50ca7c95e24e 100644 --- a/ts/dx.all.d.ts +++ b/ts/dx.all.d.ts @@ -3223,6 +3223,8 @@ declare module DevExpress.ui { gridSize?: number | { items?: Array, value?: number }; /** @name dxDiagram.Options.hasChanges */ hasChanges?: boolean; + /** @name dxDiagram.Options.historyToolbar */ + historyToolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen' | 'zoomLevel' | 'autoZoom' | 'showGrid' | 'snapToGrid' | 'gridSize' | 'units'>, visible?: boolean }; /** @name dxDiagram.Options.nodes */ nodes?: { autoLayout?: 'auto' | 'off' | 'tree' | 'layered' | { orientation?: 'auto' | 'vertical' | 'horizontal', type?: 'auto' | 'off' | 'tree' | 'layered' }, childrenExpr?: string | ((data: any) => any), containerKeyExpr?: string | ((data: any) => any), dataSource?: Array | DevExpress.data.DataSource | DevExpress.data.DataSourceOptions, heightExpr?: string | ((data: any) => any), imageUrlExpr?: string | ((data: any) => any), itemsExpr?: string | ((data: any) => any), keyExpr?: string | ((data: any) => any), leftExpr?: string | ((data: any) => any), lockedExpr?: string | ((data: any) => any), parentKeyExpr?: string | ((data: any) => any), styleExpr?: string | ((data: any) => any), textExpr?: string | ((data: any) => any), textStyleExpr?: string | ((data: any) => any), topExpr?: string | ((data: any) => any), typeExpr?: string | ((data: any) => any), widthExpr?: string | ((data: any) => any), zIndexExpr?: string | ((data: any) => any) }; /** @name dxDiagram.Options.onItemClick */ @@ -3248,11 +3250,13 @@ declare module DevExpress.ui { /** @name dxDiagram.Options.snapToGrid */ snapToGrid?: boolean; /** @name dxDiagram.Options.toolbar */ - toolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen'>, visible?: boolean }; + toolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen' | 'zoomLevel' | 'autoZoom' | 'showGrid' | 'snapToGrid' | 'gridSize' | 'units'>, visible?: boolean }; /** @name dxDiagram.Options.toolbox */ toolbox?: { groups?: Array<{ category?: 'general' | 'flowchart' | 'orgChart' | 'containers' | 'custom' | string, displayMode?: 'icons' | 'texts', expanded?: boolean, shapes?: Array<'text' | 'rectangle' | 'ellipse' | 'cross' | 'triangle' | 'diamond' | 'heart' | 'pentagon' | 'octagon' | 'star' | 'arrowLeft' | 'arrowTop' | 'arrowRight' | 'arrowBottom' | 'arrowNorthSouth' | 'arrowEastWest' | 'process' | 'decision' | 'terminator' | 'predefinedProcess' | 'document' | 'multipleDocuments' | 'manualInput' | 'preparation' | 'data' | 'database' | 'hardDisk' | 'internalStorage' | 'paperTape' | 'manualOperation' | 'delay' | 'storedData' | 'display' | 'merge' | 'connector' | 'or' | 'summingJunction' | 'verticalContainer' | 'horizontalContainer' | 'cardWithImageOnLeft' | 'cardWithImageOnTop' | 'cardWithImageOnRight'> | Array, title?: string }> | Array<'general' | 'flowchart' | 'orgChart' | 'containers' | 'custom'>, visible?: boolean }; /** @name dxDiagram.Options.units */ units?: 'in' | 'cm' | 'px'; + /** @name dxDiagram.Options.viewToolbar */ + viewToolbar?: { commands?: Array<'separator' | 'export' | 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'delete' | 'fontName' | 'fontSize' | 'bold' | 'italic' | 'underline' | 'fontColor' | 'lineColor' | 'fillColor' | 'textAlignLeft' | 'textAlignCenter' | 'textAlignRight' | 'lock' | 'unlock' | 'sendToBack' | 'bringToFront' | 'insertShapeImage' | 'editShapeImage' | 'deleteShapeImage' | 'connectorLineType' | 'connectorLineStart' | 'connectorLineEnd' | 'autoLayout' | 'fullScreen' | 'zoomLevel' | 'autoZoom' | 'showGrid' | 'snapToGrid' | 'gridSize' | 'units'>, visible?: boolean }; /** @name dxDiagram.Options.viewUnits */ viewUnits?: 'in' | 'cm' | 'px'; /** @name dxDiagram.Options.zoomLevel */