From 11c99f78a56fcd4f00b9309b802d5bcdd9bda2f8 Mon Sep 17 00:00:00 2001 From: BingoRUS Date: Mon, 4 May 2020 10:50:21 +0300 Subject: [PATCH] Release 20.1.3 --- .vscode/extensions.json | 3 - .vscode/settings.json | 5 - build/gulp/scss/theme-replacements.js | 2 +- js/file_management/remote_provider.js | 8 +- js/ui/data_grid/ui.data_grid.editing.js | 2 +- js/ui/data_grid/ui.data_grid.grouping.js | 13 +- js/ui/diagram.d.ts | 8 + js/ui/diagram/ui.diagram.js | 120 +++++--- js/ui/draggable.js | 29 +- js/ui/editor/editor.js | 8 +- .../ui.file_manager.adaptivity.js | 15 +- .../file_manager/ui.file_manager.item_list.js | 5 +- .../ui.file_manager.item_list.thumbnails.js | 12 + ..._manager.items_list.thumbnails.list_box.js | 38 ++- js/ui/file_manager/ui.file_manager.js | 8 +- .../ui.file_manager.notification.js | 15 +- ...ile_manager.notification.progress_panel.js | 11 +- js/ui/file_manager/ui.file_manager.toolbar.js | 5 + js/ui/file_uploader.js | 8 +- js/ui/gantt/ui.gantt.js | 2 + js/ui/grid_core/ui.grid_core.editing.js | 2 +- js/ui/grid_core/ui.grid_core.master_detail.js | 9 +- js/ui/grid_core/ui.grid_core.validating.js | 19 +- js/viz/core/annotations.js | 9 +- package.json | 4 +- styles/widgets/common/fileManager.less | 7 +- styles/widgets/common/gantt.less | 2 + .../widgets/generic/fileManager.generic.less | 2 + styles/widgets/generic/toolbar.generic.less | 28 ++ .../material/fileManager.material.less | 2 + styles/widgets/material/toolbar.material.less | 10 +- testing/functional/tests/dataGrid/editing.ts | 261 ++++++++++++++++++ testing/helpers/fileManagerHelpers.js | 40 ++- .../dataGrid.tests.js | 84 ++++++ .../editing.tests.js | 2 +- .../editor.tests.js | 25 ++ .../diagramParts/options.tests.js | 26 ++ .../fileManagerParts/detailsView.tests.js | 2 +- .../fileManagerParts/navigation.tests.js | 86 +++++- .../fileManagerParts/progressPanel.tests.js | 29 +- .../fileManagerParts/remoteProvider.tests.js | 8 +- .../fileManagerParts/selection.tests.js | 60 +++- .../fileManagerParts/thumbnailsView.tests.js | 4 + .../fileManagerParts/toolbar.tests.js | 48 ++++ .../DevExpress.ui.widgets/sortable.tests.js | 86 +++++- .../DevExpress.viz.core/annotations.tests.js | 44 +++ themebuilder/modules/less-template-loader.js | 5 +- .../tests/less-template-loader-spec.js | 25 +- ts/dx.all.d.ts | 2 + 49 files changed, 1115 insertions(+), 133 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 50f889140042..000000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": [ "dbaeumer.vscode-eslint", "stylelint.vscode-stylelint" ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index dc8891d543ca..000000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - } -} \ No newline at end of file diff --git a/build/gulp/scss/theme-replacements.js b/build/gulp/scss/theme-replacements.js index c308208572a4..8b9d04e684f4 100644 --- a/build/gulp/scss/theme-replacements.js +++ b/build/gulp/scss/theme-replacements.js @@ -182,7 +182,7 @@ module.exports = { { import: '../list/sizes', type: 'index' }, { import: '../button', type: 'index' }, { import: '../button/colors', type: 'index' }, - { import: '../menuBase', type: 'index' }, + { import: '../button/sizes', type: 'index' }, { regex: /@mixin dx-toolbar-item-padding\(\$MATERIAL_TOOLBAR_ITEM_SPACING\),/, replacement: '@include dx-toolbar-item-padding($MATERIAL_TOOLBAR_ITEM_SPACING);' }, { regex: /.dx-toolbar-item-padding\(\$MATERIAL_MOBILE_TOOLBAR_ITEM_SPACING\),/, replacement: '@include dx-toolbar-item-padding($MATERIAL_MOBILE_TOOLBAR_ITEM_SPACING);' }, { regex: /(-bg|-color|: 0|MATERIAL_LIST_ITEM_HEIGHT|MATERIAL_LIST_ITEM_HORIZONTAL_PADDING|4px|2 0|50%),/g, replacement: '$1;' }, diff --git a/js/file_management/remote_provider.js b/js/file_management/remote_provider.js index fb5106737209..e61a9a801ca0 100644 --- a/js/file_management/remote_provider.js +++ b/js/file_management/remote_provider.js @@ -31,6 +31,7 @@ class RemoteFileSystemProvider extends FileSystemProviderBase { renameItem(item, name) { return this._executeRequest('Rename', { pathInfo: item.getFullPathInfo(), + isDirectory: item.isDirectory, name }); } @@ -47,12 +48,16 @@ class RemoteFileSystemProvider extends FileSystemProviderBase { } deleteItems(items) { - return items.map(item => this._executeRequest('Remove', { pathInfo: item.getFullPathInfo() })); + return items.map(item => this._executeRequest('Remove', { + pathInfo: item.getFullPathInfo(), + isDirectory: item.isDirectory + })); } moveItems(items, destinationDirectory) { return items.map(item => this._executeRequest('Move', { sourcePathInfo: item.getFullPathInfo(), + sourceIsDirectory: item.isDirectory, destinationPathInfo: destinationDirectory.getFullPathInfo() })); } @@ -60,6 +65,7 @@ class RemoteFileSystemProvider extends FileSystemProviderBase { copyItems(items, destinationFolder) { return items.map(item => this._executeRequest('Copy', { sourcePathInfo: item.getFullPathInfo(), + sourceIsDirectory: item.isDirectory, destinationPathInfo: destinationFolder.getFullPathInfo() })); } diff --git a/js/ui/data_grid/ui.data_grid.editing.js b/js/ui/data_grid/ui.data_grid.editing.js index 213294ddac75..8006c1b14b29 100644 --- a/js/ui/data_grid/ui.data_grid.editing.js +++ b/js/ui/data_grid/ui.data_grid.editing.js @@ -15,7 +15,7 @@ gridCore.registerModule('editing', extend(true, {}, editingModule, { editingController && editingController.refresh(); } - this.callBase.apply(this, arguments); + return this.callBase.apply(this, arguments); } } } diff --git a/js/ui/data_grid/ui.data_grid.grouping.js b/js/ui/data_grid/ui.data_grid.grouping.js index a0f63e9fc51e..bec7ab2fe4cc 100644 --- a/js/ui/data_grid/ui.data_grid.grouping.js +++ b/js/ui/data_grid/ui.data_grid.grouping.js @@ -293,13 +293,14 @@ const GroupingDataControllerExtender = (function() { const that = this; const dataSource = this._dataSource; - if(!dataSource) return; - const d = new Deferred(); - when(dataSource.changeRowExpand(key)).done(function() { - that.load().done(d.resolve).fail(d.reject); - }).fail(d.reject); - + if(!dataSource) { + d.resolve(); + } else { + when(dataSource.changeRowExpand(key)).done(function() { + that.load().done(d.resolve).fail(d.reject); + }).fail(d.reject); + } return d; }, isRowExpanded: function(key) { diff --git a/js/ui/diagram.d.ts b/js/ui/diagram.d.ts index 803e3e4755ad..0980560a94a2 100644 --- a/js/ui/diagram.d.ts +++ b/js/ui/diagram.d.ts @@ -69,6 +69,14 @@ export interface dxDiagramOptions extends WidgetOptions { * @public */ customShapes?: Array<{ allowEditImage?: boolean, allowEditText?: boolean, allowResize?: boolean, backgroundImageHeight?: number, backgroundImageLeft?: number, backgroundImageTop?: number, backgroundImageUrl?: string, backgroundImageToolboxUrl?: string, backgroundImageWidth?: number, baseType?: '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' | string, category?: string, connectionPoints?: Array<{ x?: number, y?: number }>, defaultHeight?: number, defaultImageUrl?: string, defaultText?: string, defaultWidth?: number, imageHeight?: number, imageLeft?: number, imageTop?: number, imageWidth?: number, maxHeight?: number, maxWidth?: number, minHeight?: number, minWidth?: number, template?: template | ((container: dxElement, data: { item?: dxDiagramShape }) => any), templateHeight?: number, templateLeft?: number, templateTop?: number, templateWidth?: number, textHeight?: number, textLeft?: number, textTop?: number, textWidth?: number, title?: string, type?: string }>; + /** + * @docid dxDiagramOptions.defaultItemProperties + * @type Object + * @default {} + * @prevFileNamespace DevExpress.ui + * @public + */ + defaultItemProperties?: { style?: Object, textStyle?: Object, connectorLineType?: 'straight' | 'orthogonal', connectorLineStart?: 'none' | 'arrow' | 'outlinedTriangle' | 'filledTriangle', connectorLineEnd?: 'none' | 'arrow' | 'outlinedTriangle' | 'filledTriangle' }; /** * @docid dxDiagramOptions.edges * @type Object diff --git a/js/ui/diagram/ui.diagram.js b/js/ui/diagram/ui.diagram.js index 7c0e3254c6ce..c4d03fcaebd9 100644 --- a/js/ui/diagram/ui.diagram.js +++ b/js/ui/diagram/ui.diagram.js @@ -167,7 +167,7 @@ class Diagram extends Widget { this._updateFullscreenState(); } - this._diagramInstance.barManager.registerBar(this.optionsUpdateBar); + this._diagramInstance.registerBar(this.optionsUpdateBar); if(hasWindow()) { resizeCallbacks.add(() => { @@ -228,7 +228,7 @@ class Diagram extends Widget { } _registerBar(component) { component.bar.onChanged.add(this); - this._diagramInstance.barManager.registerBar(component.bar); + this._diagramInstance.registerBar(component.bar); } _getExcludeCommands() { const excludeCommands = []; @@ -243,7 +243,7 @@ class Diagram extends Widget { _getToolbarBaseOptions() { return { onContentReady: ({ component }) => this._registerToolbar(component), - onSubMenuVisibilityChanging: ({ component }) => this._diagramInstance.barManager.updateBarItemsState(component.bar), + onSubMenuVisibilityChanging: ({ component }) => this._diagramInstance.updateBarItemsState(component.bar), onPointerUp: this._onPanelPointerUp.bind(this), export: this.option('export'), excludeCommands: this._getExcludeCommands(), @@ -369,7 +369,7 @@ class Diagram extends Widget { } }, onVisibilityChanged: (e) => { - if(!e.visible) { + if(!e.visible && !this._textInputStarted) { this._diagramCaptureFocus(); } @@ -521,7 +521,7 @@ class Diagram extends Widget { } }, onVisibilityChanged: (e) => { - if(!e.visible) { + if(!e.visible && !this._textInputStarted) { this._diagramCaptureFocus(); } }, @@ -541,7 +541,7 @@ class Diagram extends Widget { } _updatePropertiesPanelGroupBars(component) { component.getActiveToolbars().forEach(toolbar => { - this._diagramInstance.barManager.updateBarItemsState(toolbar.bar); + this._diagramInstance.updateBarItemsState(toolbar.bar); }); } _onPanelPointerUp() { @@ -562,7 +562,7 @@ class Diagram extends Widget { this._contextMenu = this._createComponent($contextMenu, DiagramContextMenuWrapper, { commands: this.option('contextMenu.commands'), onContentReady: ({ component }) => this._registerBar(component), - onVisibilityChanging: ({ component }) => this._diagramInstance.barManager.updateBarItemsState(component.bar), + onVisibilityChanging: ({ component }) => this._diagramInstance.updateBarItemsState(component.bar), onItemClick: (itemData) => { return this._onBeforeCommandExecuted(itemData.command); }, export: this.option('export'), excludeCommands: this._getExcludeCommands(), @@ -630,7 +630,7 @@ class Diagram extends Widget { if(this._dialogInstance) { this._dialogInstance.option('onGetContent', dialogParameters.onGetContent); this._dialogInstance.option('onHidden', function() { this._diagramCaptureFocus(); }.bind(this)); - this._dialogInstance.option('command', this._diagramInstance.commandManager.getCommand(dialogParameters.command)); + this._dialogInstance.option('command', this._diagramInstance.getCommand(dialogParameters.command)); this._dialogInstance.option('title', dialogParameters.title); this._dialogInstance._show(); } @@ -674,6 +674,7 @@ class Diagram extends Widget { notifySelectionChanged: this._raiseSelectionChanged.bind(this) }); this._updateEventSubscriptionMethods(); + this._updateDefaultItemProperties(); this._updateShapeTexts(); this._updateUnitItems(); @@ -731,7 +732,7 @@ class Diagram extends Widget { this._diagramInstance = undefined; } _executeDiagramCommand(command, parameter) { - this._diagramInstance.commandManager.getCommand(command).execute(parameter); + this._diagramInstance.getCommand(command).execute(parameter); } _refreshDataSources() { this._beginUpdateDiagram(); @@ -871,12 +872,7 @@ class Diagram extends Widget { setText: this._createOptionSetter('edges.textExpr'), getLineOption: (lineOptionGetter = this._createOptionGetter('edges.lineTypeExpr')) && function(obj) { const lineType = lineOptionGetter(obj); - switch(lineType) { - case 'straight': - return ConnectorLineOption.Straight; - default: - return ConnectorLineOption.Orthogonal; - } + return this._getConnectorLineOption(lineType); }.bind(this), setLineOption: (lineOptionSetter = this._createOptionSetter('edges.lineTypeExpr')) && function(obj, value) { switch(value) { @@ -890,17 +886,8 @@ class Diagram extends Widget { lineOptionSetter(obj, value); }.bind(this), getStartLineEnding: (startLineEndingGetter = this._createOptionGetter('edges.fromLineEndExpr')) && function(obj) { - const lineType = startLineEndingGetter(obj); - switch(lineType) { - case 'arrow': - return ConnectorLineEnding.Arrow; - case 'outlinedTriangle': - return ConnectorLineEnding.OutlinedTriangle; - case 'filledTriangle': - return ConnectorLineEnding.FilledTriangle; - default: - return ConnectorLineEnding.None; - } + const lineEnd = startLineEndingGetter(obj); + return this._getConnectorLineEnding(lineEnd); }.bind(this), setStartLineEnding: (startLineEndingSetter = this._createOptionSetter('edges.fromLineEndExpr')) && function(obj, value) { switch(value) { @@ -920,17 +907,8 @@ class Diagram extends Widget { startLineEndingSetter(obj, value); }.bind(this), getEndLineEnding: (endLineEndingGetter = this._createOptionGetter('edges.toLineEndExpr')) && function(obj) { - const lineType = endLineEndingGetter(obj); - switch(lineType) { - case 'none': - return ConnectorLineEnding.None; - case 'outlinedTriangle': - return ConnectorLineEnding.OutlinedTriangle; - case 'filledTriangle': - return ConnectorLineEnding.FilledTriangle; - default: - return ConnectorLineEnding.Arrow; - } + const lineEnd = endLineEndingGetter(obj); + return this._getConnectorLineEnding(lineEnd); }.bind(this), setEndLineEnding: (endLineEndingSetter = this._createOptionSetter('edges.toLineEndExpr')) && function(obj, value) { switch(value) { @@ -954,6 +932,28 @@ class Diagram extends Widget { }; this._executeDiagramCommand(DiagramCommand.BindDocument, data); } + _getConnectorLineOption(lineType) { + const { ConnectorLineOption } = getDiagram(); + switch(lineType) { + case 'straight': + return ConnectorLineOption.Straight; + default: + return ConnectorLineOption.Orthogonal; + } + } + _getConnectorLineEnding(lineEnd) { + const { ConnectorLineEnding } = getDiagram(); + switch(lineEnd) { + case 'arrow': + return ConnectorLineEnding.Arrow; + case 'outlinedTriangle': + return ConnectorLineEnding.OutlinedTriangle; + case 'filledTriangle': + return ConnectorLineEnding.FilledTriangle; + default: + return ConnectorLineEnding.None; + } + } _getDataBindingLayoutParameters() { const { DataLayoutType, DataLayoutOrientation } = getDiagram(); const layoutParametersOption = this.option('nodes.autoLayout') || 'off'; @@ -1370,6 +1370,19 @@ class Diagram extends Widget { eventsEngine.off(element, eventName, handler); }; } + _updateDefaultItemProperties() { + if(this.option('defaultItemProperties.style')) { + this._diagramInstance.setInitialStyleProperties(this.option('defaultItemProperties.style')); + } + if(this.option('defaultItemProperties.textStyle')) { + this._diagramInstance.setInitialTextStyleProperties(this.option('defaultItemProperties.textStyle')); + } + this._diagramInstance.setInitialConnectorProperties({ + lineOption: this._getConnectorLineOption(this.option('defaultItemProperties.connectorLineType')), + startLineEnding: this._getConnectorLineEnding(this.option('defaultItemProperties.connectorLineStart')), + endLineEnding: this._getConnectorLineEnding(this.option('defaultItemProperties.connectorLineEnd')) + }); + } focus() { this._diagramCaptureFocus(); @@ -1988,6 +2001,34 @@ class Diagram extends Widget { */ }, + defaultItemProperties: { + /** + * @name dxDiagramOptions.defaultItemProperties.style + * @type Object + */ + /** + * @name dxDiagramOptions.defaultItemProperties.textStyle + * @type Object + */ + /** + * @name dxDiagramOptions.defaultItemProperties.connectorLineType + * @type Enums.DiagramConnectorLineType + * @default 'orthogonal' + */ + connectorLineType: 'orthogonal', + /** + * @name dxDiagramOptions.defaultItemProperties.connectorLineStart + * @type Enums.DiagramConnectorLineEnd + * @default 'none' + */ + connectorLineStart: 'none', + /** + * @name dxDiagramOptions.defaultItemProperties.connectorLineEnd + * @type Enums.DiagramConnectorLineEnd + * @default 'arrow' + */ + connectorLineEnd: 'arrow', + }, export: { /** * @name dxDiagramOptions.export.fileName @@ -2095,6 +2136,7 @@ class Diagram extends Widget { } } _raiseTextInputStart() { + this._textInputStarted = true; if(this._propertiesPanel) { if(this.isMobileScreenSize() && this._propertiesPanel.isVisible()) { this._propertiesPanel.hide(); @@ -2121,6 +2163,7 @@ class Diagram extends Widget { delete this._toolboxTextInputHidden; } } + this._textInputStarted = false; } _createItemClickAction() { @@ -2370,6 +2413,9 @@ class Diagram extends Widget { case 'onCustomCommand': this._createCustomCommand(); break; + case 'defaultItemProperties': + this._updateDefaultItemProperties(); + break; case 'export': if(this._mainToolbar) { this._mainToolbar.option('export', args.value); diff --git a/js/ui/draggable.js b/js/ui/draggable.js index 65f13b728744..b097f6c1c0b7 100644 --- a/js/ui/draggable.js +++ b/js/ui/draggable.js @@ -718,8 +718,8 @@ const Draggable = DOMComponent.inherit({ } }, - getElementsFromPoint: function(position) { - const ownerDocument = this._$dragElement.get(0).ownerDocument; + getElementsFromPoint: function(position, dragElement) { + const ownerDocument = (dragElement || this._$dragElement.get(0)).ownerDocument; if(browser.msie) { const msElements = ownerDocument.msElementsFromPoint(position.x, position.y); @@ -817,8 +817,31 @@ const Draggable = DOMComponent.inherit({ } }, + _isTargetOverAnotherDraggable: function(e) { + const sourceDraggable = this._getSourceDraggable(); + + if(this === sourceDraggable) { + return false; + } + + if(!sourceDraggable._dragElementIsCloned()) { + return true; + } + + const $sourceDraggableElement = sourceDraggable.$element(); + const elements = this.getElementsFromPoint({ + x: e.pageX, + y: e.pageY + }, e.target); + const firstWidgetElement = elements.filter((element) => $(element).hasClass(this._addWidgetPrefix()))[0]; + + return firstWidgetElement !== $sourceDraggableElement.get(0); + }, + _dragEnterHandler: function(e) { - this._setTargetDraggable(); + if(this._isTargetOverAnotherDraggable(e)) { + this._setTargetDraggable(); + } const sourceDraggable = this._getSourceDraggable(); sourceDraggable.dragEnter(e); diff --git a/js/ui/editor/editor.js b/js/ui/editor/editor.js index 93bd2a0c1b16..7e657d2e2033 100644 --- a/js/ui/editor/editor.js +++ b/js/ui/editor/editor.js @@ -354,16 +354,16 @@ const Editor = Widget.inherit({ this._refreshFocusState(); break; case 'value': - if(!this._valueChangeActionSuppressed) { - this._raiseValueChangeAction(args.value, args.previousValue); - this._saveValueChangeEvent(undefined); - } if(args.value != args.previousValue) { // eslint-disable-line eqeqeq this.validationRequest.fire({ value: args.value, editor: this }); } + if(!this._valueChangeActionSuppressed) { + this._raiseValueChangeAction(args.value, args.previousValue); + this._saveValueChangeEvent(undefined); + } break; case 'width': this.callBase(args); diff --git a/js/ui/file_manager/ui.file_manager.adaptivity.js b/js/ui/file_manager/ui.file_manager.adaptivity.js index 5df4b35c82a3..027efc660f13 100644 --- a/js/ui/file_manager/ui.file_manager.adaptivity.js +++ b/js/ui/file_manager/ui.file_manager.adaptivity.js @@ -10,6 +10,7 @@ import SplitterControl from '../splitter'; const window = getWindow(); const ADAPTIVE_STATE_SCREEN_WIDTH = 573; +const FILE_MANAGER_ADAPTIVITY_DRAWER_PANEL_CLASS = 'dx-filemanager-adaptivity-drawer-panel'; const DRAWER_PANEL_CONTENT_INITIAL = 'dx-drawer-panel-content-initial'; class FileManagerAdaptivityControl extends Widget { @@ -23,10 +24,9 @@ class FileManagerAdaptivityControl extends Widget { const $drawer = $('
').appendTo(this.$element()); - const contentRenderer = this.option('contentTemplate'); - if(isFunction(contentRenderer)) { - contentRenderer($drawer); - } + $('
') + .addClass(FILE_MANAGER_ADAPTIVITY_DRAWER_PANEL_CLASS) + .appendTo($drawer); this._drawer = this._createComponent($drawer, Drawer); this._drawer.option({ @@ -34,6 +34,13 @@ class FileManagerAdaptivityControl extends Widget { template: this._createDrawerTemplate.bind(this) }); $(this._drawer.content()).addClass(DRAWER_PANEL_CONTENT_INITIAL); + + const $drawerContent = $drawer.find(`.${FILE_MANAGER_ADAPTIVITY_DRAWER_PANEL_CLASS}`).first(); + + const contentRenderer = this.option('contentTemplate'); + if(isFunction(contentRenderer)) { + contentRenderer($drawerContent); + } } _createDrawerTemplate(container) { diff --git a/js/ui/file_manager/ui.file_manager.item_list.js b/js/ui/file_manager/ui.file_manager.item_list.js index ddb5f5c0ddc6..84a70eff6c96 100644 --- a/js/ui/file_manager/ui.file_manager.item_list.js +++ b/js/ui/file_manager/ui.file_manager.item_list.js @@ -109,7 +109,7 @@ class FileManagerItemListBase extends Widget { this._actions.onSelectedItemOpened({ fileItemInfo }); } - _tryRaiseSelectionChanged({ selectedItems, selectedItemKeys, currentSelectedItemKeys, currentDeselectedItemKeys }) { + _tryRaiseSelectionChanged({ selectedItemInfos, selectedItems, selectedItemKeys, currentSelectedItemKeys, currentDeselectedItemKeys }) { const parentDirectoryItem = this._findParentDirectoryItem(this.getSelectedItems()); if(parentDirectoryItem) { this._deselectItem(parentDirectoryItem); @@ -119,11 +119,12 @@ class FileManagerItemListBase extends Widget { raiseEvent = raiseEvent || this._hasValidKeys(currentSelectedItemKeys) || this._hasValidKeys(currentDeselectedItemKeys); if(raiseEvent) { + selectedItemInfos = this._filterOutItemByPredicate(selectedItemInfos, item => item.fileItem.key === this._parentDirectoryItemKey); selectedItems = this._filterOutParentDirectory(selectedItems); selectedItemKeys = this._filterOutParentDirectoryKey(selectedItemKeys, true); currentSelectedItemKeys = this._filterOutParentDirectoryKey(currentSelectedItemKeys, true); currentDeselectedItemKeys = this._filterOutParentDirectoryKey(currentDeselectedItemKeys, true); - this._raiseSelectionChanged({ selectedItems, selectedItemKeys, currentSelectedItemKeys, currentDeselectedItemKeys }); + this._raiseSelectionChanged({ selectedItemInfos, selectedItems, selectedItemKeys, currentSelectedItemKeys, currentDeselectedItemKeys }); } } diff --git a/js/ui/file_manager/ui.file_manager.item_list.thumbnails.js b/js/ui/file_manager/ui.file_manager.item_list.thumbnails.js index 4c5205b245dc..1007e8bb74f8 100644 --- a/js/ui/file_manager/ui.file_manager.item_list.thumbnails.js +++ b/js/ui/file_manager/ui.file_manager.item_list.thumbnails.js @@ -119,6 +119,10 @@ class FileManagerThumbnailsItemList extends FileManagerItemListBase { } _onItemListFocusedItemChanged({ item, itemElement }) { + if(!this._isMultipleSelectionMode()) { + this._selectItemSingleSelection(item); + } + const fileSystemItem = item?.fileItem || null; this._raiseFocusedItemChanged({ item: fileSystemItem, @@ -152,6 +156,14 @@ class FileManagerThumbnailsItemList extends FileManagerItemListBase { this._itemList.unselectItem(itemElement); } + _selectItemSingleSelection(item) { + if(item) { + this._itemList.selectItem(item); + } else { + this._itemList.clearSelection(); + } + } + clearSelection() { this._itemList.clearSelection(); } diff --git a/js/ui/file_manager/ui.file_manager.items_list.thumbnails.list_box.js b/js/ui/file_manager/ui.file_manager.items_list.thumbnails.list_box.js index 5e82e903a323..13d84028b960 100644 --- a/js/ui/file_manager/ui.file_manager.items_list.thumbnails.list_box.js +++ b/js/ui/file_manager/ui.file_manager.items_list.thumbnails.list_box.js @@ -10,6 +10,7 @@ import eventsEngine from '../../events/core/events_engine'; import { BindableTemplate } from '../../core/templates/bindable_template'; +import ScrollView from '../scroll_view/ui.scroll_view'; import CollectionWidget from '../collection/ui.collection_widget.edit'; const FILE_MANAGER_THUMBNAILS_VIEW_PORT_CLASS = 'dx-filemanager-thumbnails-view-port'; @@ -30,12 +31,16 @@ class FileManagerThumbnailListBox extends CollectionWidget { this._lockFocusedItemProcessing = false; this.$element().addClass(FILE_MANAGER_THUMBNAILS_VIEW_PORT_CLASS); + + this._renderScrollView(); this._renderItemsContainer(); + this._createScrollViewControl(); + super._initMarkup(); this.onFocusedItemChanged = this._onFocusedItemChanged.bind(this); - this._layoutUtils = new ListBoxLayoutUtils(this.$element(), this._$itemContainer, this.itemElements().first()); + this._layoutUtils = new ListBoxLayoutUtils(this._scrollView, this.$element(), this._$itemContainer, this.itemElements().first()); this._syncFocusedItemKey(); } @@ -59,11 +64,29 @@ class FileManagerThumbnailListBox extends CollectionWidget { }); } + _createScrollViewControl() { + if(!this._scrollView) { + this._scrollView = this._createComponent(this._$scrollView, ScrollView, { + scrollByContent: true, + scrollByThumb: true, + useKeyboard: false, + showScrollbar: 'onHover' + }); + } + } + + _renderScrollView() { + if(!this._$scrollView) { + this._$scrollView = $('
') + .appendTo(this.$element()); + } + } + _renderItemsContainer() { if(!this._$itemContainer) { this._$itemContainer = $('
') .addClass(FILE_MANAGER_THUMBNAILS_ITEM_LIST_CONTAINER_CLASS) - .appendTo(this.$element()); + .appendTo(this._$scrollView); } } @@ -302,7 +325,7 @@ class FileManagerThumbnailListBox extends CollectionWidget { const items = this.option('items'); const focusedItem = find(items, item => this.keyOf(item) === focusedItemKey); if(focusedItem) { - this._focusItem(focusedItem); + this._focusItem(focusedItem, true); deferred.resolve(); } else { this.option('focusedItemKey', undefined); @@ -362,6 +385,8 @@ class FileManagerThumbnailListBox extends CollectionWidget { } selectAll() { + if(this.option('selectionMode') !== 'multiple') return; + this._selection.selectAll(); this._isPreserveSelectionMode = true; } @@ -413,8 +438,9 @@ class FileManagerThumbnailListBox extends CollectionWidget { } class ListBoxLayoutUtils { - constructor($viewPort, $itemContainer, $item) { + constructor(scrollView, $viewPort, $itemContainer, $item) { this._layoutModel = null; + this._scrollView = scrollView; this._$viewPort = $viewPort; this._$itemContainer = $itemContainer; this._$item = $item; @@ -449,7 +475,7 @@ class ListBoxLayoutUtils { const viewPortWidth = this._$itemContainer.innerWidth(); const viewPortHeight = this._$viewPort.innerHeight(); - const viewPortScrollTop = this._$viewPort.scrollTop(); + const viewPortScrollTop = this._scrollView.scrollTop(); const viewPortScrollBottom = viewPortScrollTop + viewPortHeight; const itemPerRowCount = Math.floor(viewPortWidth / itemWidth); @@ -504,7 +530,7 @@ class ListBoxLayoutUtils { newScrollTop = itemBottom - layout.viewPortHeight; } - this._$viewPort.scrollTop(newScrollTop); + this._scrollView.scrollTo(newScrollTop); } } diff --git a/js/ui/file_manager/ui.file_manager.js b/js/ui/file_manager/ui.file_manager.js index 94a98b531dbc..2e49aa99b73b 100644 --- a/js/ui/file_manager/ui.file_manager.js +++ b/js/ui/file_manager/ui.file_manager.js @@ -219,14 +219,14 @@ class FileManager extends Widget { this._setItemsViewAreaActive(false); } - _onItemViewSelectionChanged(e) { - this._updateToolbar(e.selectedItemInfos); + _onItemViewSelectionChanged({ selectedItemInfos, selectedItems, selectedItemKeys, currentSelectedItemKeys, currentDeselectedItemKeys }) { + this._updateToolbar(selectedItemInfos); this._lockSelectionProcessing = true; - this.option('selectedItemKeys', e.selectedItemKeys); + this.option('selectedItemKeys', selectedItemKeys); this._lockSelectionProcessing = false; - this._actions.onSelectionChanged(e); + this._actions.onSelectionChanged({ selectedItems, selectedItemKeys, currentSelectedItemKeys, currentDeselectedItemKeys }); } _onItemViewFocusedItemChanged(e) { diff --git a/js/ui/file_manager/ui.file_manager.notification.js b/js/ui/file_manager/ui.file_manager.notification.js index f9e3266fd182..789634c40ce4 100644 --- a/js/ui/file_manager/ui.file_manager.notification.js +++ b/js/ui/file_manager/ui.file_manager.notification.js @@ -15,6 +15,7 @@ const ADAPTIVE_STATE_SCREEN_WIDTH = 1000; const FILE_MANAGER_NOTIFICATION_CLASS = 'dx-filemanager-notification'; const FILE_MANAGER_NOTIFICATION_DRAWER_CLASS = `${FILE_MANAGER_NOTIFICATION_CLASS}-drawer`; +const FILE_MANAGER_NOTIFICATION_DRAWER_PANEL_CLASS = `${FILE_MANAGER_NOTIFICATION_DRAWER_CLASS}-panel`; const FILE_MANAGER_NOTIFICATION_POPUP_CLASS = `${FILE_MANAGER_NOTIFICATION_CLASS}-popup`; const FILE_MANAGER_NOTIFICATION_POPUP_ERROR_CLASS = `${FILE_MANAGER_NOTIFICATION_CLASS}-popup-error`; const FILE_MANAGER_NOTIFICATION_COMMON_CLASS = `${FILE_MANAGER_NOTIFICATION_CLASS}-common`; @@ -39,10 +40,9 @@ export default class FileManagerNotificationControl extends Widget { .addClass(FILE_MANAGER_NOTIFICATION_DRAWER_CLASS) .appendTo($progressPanelContainer); - const contentRenderer = this.option('contentTemplate'); - if(isFunction(contentRenderer)) { - contentRenderer($progressDrawer); - } + $('
') + .addClass(FILE_MANAGER_NOTIFICATION_DRAWER_PANEL_CLASS) + .appendTo($progressDrawer); const drawerOptions = extend({ opened: false, @@ -52,6 +52,13 @@ export default class FileManagerNotificationControl extends Widget { this._getProgressDrawerAdaptiveOptions()); this._progressDrawer = this._createComponent($progressDrawer, Drawer, drawerOptions); + + const $drawerContent = $progressDrawer.find(`.${FILE_MANAGER_NOTIFICATION_DRAWER_PANEL_CLASS}`).first(); + + const contentRenderer = this.option('contentTemplate'); + if(isFunction(contentRenderer)) { + contentRenderer($drawerContent); + } } tryShowProgressPanel() { diff --git a/js/ui/file_manager/ui.file_manager.notification.progress_panel.js b/js/ui/file_manager/ui.file_manager.notification.progress_panel.js index 90198eacbbf4..6321ddc72e57 100644 --- a/js/ui/file_manager/ui.file_manager.notification.progress_panel.js +++ b/js/ui/file_manager/ui.file_manager.notification.progress_panel.js @@ -72,9 +72,10 @@ class FileManagerProgressPanel extends Widget { }); this._$infosContainer = $('
') - .text(messageLocalization.format('dxFileManager-notificationProgressPanelEmptyListText')) .addClass(FILE_MANAGER_PROGRESS_PANEL_INFOS_CONTAINER_CLASS) .appendTo($container); + + this._renderEmptyListText(); } _getDefaultOptions() { @@ -231,6 +232,10 @@ class FileManagerProgressPanel extends Widget { this._renderOperationError(detailsItem, errorText); } + _renderEmptyListText() { + this._$infosContainer.text(messageLocalization.format('dxFileManager-notificationProgressPanelEmptyListText')); + } + _renderOperationError(info, errorText) { this._removeProgressBar(info); this.renderError(info.$wrapper, info.$commonText, errorText); @@ -318,6 +323,10 @@ class FileManagerProgressPanel extends Widget { this._raiseOperationClosed(info); info.$info.next(`.${FILE_MANAGER_PROGRESS_PANEL_SEPARATOR_CLASS}`).remove(); info.$info.remove(); + this._operationCount--; + if(!this._operationCount) { + this._renderEmptyListText(); + } } } diff --git a/js/ui/file_manager/ui.file_manager.toolbar.js b/js/ui/file_manager/ui.file_manager.toolbar.js index 68db6c2e66f9..23fc496043fb 100644 --- a/js/ui/file_manager/ui.file_manager.toolbar.js +++ b/js/ui/file_manager/ui.file_manager.toolbar.js @@ -265,6 +265,11 @@ class FileManagerToolbar extends Widget { if(!result.widget) { result.widget = 'dxButton'; } + if(result.widget === 'dxButton' && !result.compactMode && !result.showText && result.options.icon && result.options.text) { + result.compactMode = { + showText: 'inMenu' + }; + } } if(commandName && !result.name) { diff --git a/js/ui/file_uploader.js b/js/ui/file_uploader.js index b1bea8a484e0..cb1f5ec73071 100644 --- a/js/ui/file_uploader.js +++ b/js/ui/file_uploader.js @@ -8,7 +8,7 @@ import { isDefined, isFunction } from '../core/utils/type'; import { each } from '../core/utils/iterator'; import { extend } from '../core/utils/extend'; import { inArray } from '../core/utils/array'; -import { Deferred, when } from '../core/utils/deferred'; +import { Deferred, fromPromise } from '../core/utils/deferred'; import ajax from '../core/utils/ajax'; import Editor from './editor/editor'; import Button from './button'; @@ -1236,7 +1236,7 @@ class FileUploadStrategyBase { let deferred = null; try { const result = abortUpload(file.value, arg); - deferred = when(result); + deferred = fromPromise(result); } catch(error) { deferred = new Deferred().reject(error).promise(); } @@ -1467,7 +1467,7 @@ class CustomChunksFileUploadStrategy extends ChunksFileUploadStrategyBase { const uploadChunk = this.fileUploader.option('uploadChunk'); try { const result = uploadChunk(file.value, chunksInfo); - return when(result); + return fromPromise(result); } catch(error) { return new Deferred().reject(error).promise(); } @@ -1579,7 +1579,7 @@ class CustomWholeFileUploadStrategy extends WholeFileUploadStrategyBase { const uploadFile = this.fileUploader.option('uploadFile'); try { const result = uploadFile(file, progressCallback); - return when(result); + return fromPromise(result); } catch(error) { return new Deferred().reject(error).promise(); } diff --git a/js/ui/gantt/ui.gantt.js b/js/ui/gantt/ui.gantt.js index 0a6ebff0e825..e518055be5bd 100644 --- a/js/ui/gantt/ui.gantt.js +++ b/js/ui/gantt/ui.gantt.js @@ -395,6 +395,8 @@ class Gantt extends Widget { expandedRowKeys.push(parentId); this._treeList.option('expandedRowKeys', expandedRowKeys); } + this._setTreeListOption('selectedRowKeys', this._getArrayFromOneElement(insertedId)); + this._setTreeListOption('focusedRowKey', insertedId); } }); } diff --git a/js/ui/grid_core/ui.grid_core.editing.js b/js/ui/grid_core/ui.grid_core.editing.js index b88d659a793a..94148915ae15 100644 --- a/js/ui/grid_core/ui.grid_core.editing.js +++ b/js/ui/grid_core/ui.grid_core.editing.js @@ -2380,7 +2380,7 @@ const EditingController = modules.ViewController.inherit((function() { isCellModified: function(parameters) { const columnIndex = parameters.columnIndex; const modifiedValues = parameters.row && (parameters.row.isNewRow ? parameters.row.values : parameters.row.modifiedValues); - return modifiedValues && modifiedValues[columnIndex] !== undefined; + return !!modifiedValues && modifiedValues[columnIndex] !== undefined; } }; })()); diff --git a/js/ui/grid_core/ui.grid_core.master_detail.js b/js/ui/grid_core/ui.grid_core.master_detail.js index 3385946f87a2..629014f907d3 100644 --- a/js/ui/grid_core/ui.grid_core.master_detail.js +++ b/js/ui/grid_core/ui.grid_core.master_detail.js @@ -3,7 +3,7 @@ import gridCoreUtils from './ui.grid_core.utils'; import { grep } from '../../core/utils/common'; import { each } from '../../core/utils/iterator'; import { isDefined } from '../../core/utils/type'; -import { when } from '../../core/utils/deferred'; +import { when, Deferred } from '../../core/utils/deferred'; const MASTER_DETAIL_CELL_CLASS = 'dx-master-detail-cell'; const MASTER_DETAIL_ROW_CLASS = 'dx-master-detail-row'; @@ -110,8 +110,9 @@ module.exports = { let expandIndex; let editingController; + let result; if(Array.isArray(key)) { - return that.callBase.apply(that, arguments); + result = that.callBase.apply(that, arguments); } else { expandIndex = gridCoreUtils.getIndexByKey(key, that._expandedItems); if(expandIndex >= 0) { @@ -131,7 +132,11 @@ module.exports = { changeType: 'update', rowIndices: that._getRowIndicesForExpand(key) }); + + result = new Deferred().resolve(); } + + return result; }, _processDataItem: function(data, options) { const that = this; diff --git a/js/ui/grid_core/ui.grid_core.validating.js b/js/ui/grid_core/ui.grid_core.validating.js index 6fe7c6746c61..84d2a7511091 100644 --- a/js/ui/grid_core/ui.grid_core.validating.js +++ b/js/ui/grid_core/ui.grid_core.validating.js @@ -459,7 +459,7 @@ const ValidatingController = modules.Controller.inherit((function() { removeCellValidationResult: function({ editData, columnIndex }) { if(editData && editData.validationResults) { const result = editData.validationResults[columnIndex]; - result.deferred && result.deferred.reject('cancel'); + result && result.deferred && result.deferred.reject('cancel'); delete editData.validationResults[columnIndex]; } }, @@ -476,8 +476,7 @@ const ValidatingController = modules.Controller.inherit((function() { rowKey, columnIndex }); - const editData = this._editingController.getEditDataByKey(rowKey); - return result && result.status === 'invalid' && editData && editData.validated; + return !!result && result.status === VALIDATION_STATUS.invalid; }, getCellValidator: function({ rowKey, columnIndex }) { @@ -760,10 +759,12 @@ module.exports = { }, updateFieldValue: function(e) { - const validatingController = this.getController('validating'); const deferred = new Deferred(); - validatingController.resetRowValidationResults(this.getEditDataByKey(e.key)); + validatingController.removeCellValidationResult({ + editData: this.getEditDataByKey(e.key), + columnIndex: e.column.index + }); this.callBase.apply(this, arguments).done(() => { const currentValidator = validatingController.getCellValidator({ @@ -799,7 +800,7 @@ module.exports = { }, highlightDataCell: function($cell, parameters) { - const isEditableCell = parameters.setValue; + const isEditableCell = !!parameters.setValue; const cellModified = this.isCellModified(parameters); if(!cellModified && isEditableCell) { @@ -821,11 +822,13 @@ module.exports = { isCellModified: function(parameters) { const cellModified = this.callBase(parameters); - const isCellInvalidInNewRow = parameters.row && parameters.row.isNewRow && this.getController('validating').isInvalidCell({ + const editData = this.getEditDataByKey(parameters.key); + const isRowValidated = !!editData && !!editData.validated; + const isCellInvalid = !!parameters.row && this.getController('validating').isInvalidCell({ rowKey: parameters.key, columnIndex: parameters.column.index }); - return cellModified || isCellInvalidInNewRow; + return cellModified || (isRowValidated && isCellInvalid); } }, editorFactory: (function() { diff --git a/js/viz/core/annotations.js b/js/viz/core/annotations.js index 5055914fc8b3..4ac85849c099 100644 --- a/js/viz/core/annotations.js +++ b/js/viz/core/annotations.js @@ -1,5 +1,5 @@ import { getDocument } from '../../core/dom_adapter'; -import { isDefined } from '../../core/utils/type'; +import { isDefined, isFunction } from '../../core/utils/type'; import { Tooltip } from '../core/tooltip'; import { extend } from '../../core/utils/extend'; import { patchFontOptions } from './utils'; @@ -102,6 +102,11 @@ export let createAnnotations = function(widget, items, commonAnnotationSettings const commonImageOptions = getImageObject(commonAnnotationSettings.image); return items.reduce((arr, item) => { const currentImageOptions = getImageObject(item.image); + const customizedItem = isFunction(customizeAnnotation) ? customizeAnnotation(item) : {}; + if(customizedItem) { + customizedItem.image = getImageObject(customizedItem.image);// T881143 + } + const options = extend( true, {}, @@ -109,7 +114,7 @@ export let createAnnotations = function(widget, items, commonAnnotationSettings item, { image: commonImageOptions }, { image: currentImageOptions }, - customizeAnnotation && customizeAnnotation.call ? customizeAnnotation(item) : {} + customizedItem ); const templateFunction = getTemplateFunction(options, widget); const annotation = templateFunction && extend(true, pullOptions(options), coreAnnotation(options, widget._getTemplate(templateFunction))); diff --git a/package.json b/package.json index e712ec418b50..6346df3ce0a0 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "types": "./bundles/dx.all.d.ts", "license": "SEE LICENSE IN README.md", "dependencies": { - "devexpress-diagram": "0.2.44", - "devexpress-gantt": "0.1.20", + "devexpress-diagram": "1.0.0", + "devexpress-gantt": "1.0.0", "jszip": "^2.0.0 || ^3.0.0", "quill": "^1.3.7", "showdown": "^1.8.6", diff --git a/styles/widgets/common/fileManager.less b/styles/widgets/common/fileManager.less index e834684297ab..2bea3b2c8cd5 100644 --- a/styles/widgets/common/fileManager.less +++ b/styles/widgets/common/fileManager.less @@ -22,7 +22,8 @@ flex-direction: column; } - .dx-filemanager-notification-drawer { + .dx-filemanager-notification-drawer, + .dx-filemanager-notification-drawer-panel { height: 100%; } @@ -171,6 +172,10 @@ box-sizing: border-box; overflow: hidden; + .dx-filemanager-adaptivity-drawer-panel { + height: 100%; + } + .dx-filemanager-dirs-panel { padding: 5px 10px; height: 100%; diff --git a/styles/widgets/common/gantt.less b/styles/widgets/common/gantt.less index 0e8a789208c9..6942db61dd60 100644 --- a/styles/widgets/common/gantt.less +++ b/styles/widgets/common/gantt.less @@ -6,6 +6,7 @@ display: flex; flex-direction: column; position: relative; + width: 100%; .dx-gantt-main-wrapper { display: flex; @@ -46,6 +47,7 @@ -webkit-user-select: none; // stylelint-disable-line property-no-vendor-prefix user-select: none; width: 100%; + min-width: 1px; .dx-gantt-tac-hb { margin-top: -1px; diff --git a/styles/widgets/generic/fileManager.generic.less b/styles/widgets/generic/fileManager.generic.less index 962dc63289a5..12138dc2216c 100644 --- a/styles/widgets/generic/fileManager.generic.less +++ b/styles/widgets/generic/fileManager.generic.less @@ -175,6 +175,8 @@ &.dx-filemanager-details { .dx-filemanager-details-item-thumbnail { font-size: @GENERIC_BASE_ICON_SIZE; + width: @GENERIC_BASE_ICON_SIZE; + height: @GENERIC_BASE_ICON_SIZE; } .dx-datagrid .dx-datagrid-content .dx-datagrid-table .dx-command-select { diff --git a/styles/widgets/generic/toolbar.generic.less b/styles/widgets/generic/toolbar.generic.less index f49f6ec473c9..5eb1293c72b3 100644 --- a/styles/widgets/generic/toolbar.generic.less +++ b/styles/widgets/generic/toolbar.generic.less @@ -23,6 +23,15 @@ @GENERIC_MOBILE_TOOLBAR_ITEM_SPACING: 5px; @GENERIC_MOBILE_TOOLBAR_LABEL_FONT_SIZE: 20px; +.dx-size-default() { + @GENERIC_TOOLBAR_MENU_TOP_PADDING: 7px; + @GENERIC_TOOLBAR_MENU_BOTTOM_PADDING: 8px; +} + +.dx-size-compact() { + @GENERIC_TOOLBAR_MENU_TOP_PADDING: 4px; + @GENERIC_TOOLBAR_MENU_BOTTOM_PADDING: 4px; +} .dx-toolbar-sizing(@height, @padding, @label-font-size, @item-spacing) { padding: @padding; @@ -180,3 +189,22 @@ .dx-toolbar .dx-tab { padding: 4px; } + +.dx-toolbar-button { + .dx-menu { + .dx-menu-item { + border: @GENERIC_BUTTON_BORDER_WEIGHT solid transparent; + border-radius: @button-border-radius; + + .dx-menu-item-content { + padding-top: @GENERIC_TOOLBAR_MENU_TOP_PADDING; + padding-bottom: @GENERIC_TOOLBAR_MENU_BOTTOM_PADDING; + line-height: 0; + + .dx-menu-item-text { + line-height: normal; + } + } + } + } +} diff --git a/styles/widgets/material/fileManager.material.less b/styles/widgets/material/fileManager.material.less index 224ab759a294..aa6b6e9868d9 100644 --- a/styles/widgets/material/fileManager.material.less +++ b/styles/widgets/material/fileManager.material.less @@ -172,6 +172,8 @@ &.dx-filemanager-details { .dx-filemanager-details-item-thumbnail { font-size: @MATERIAL_BASE_ICON_SIZE; + width: @MATERIAL_BASE_ICON_SIZE; + height: @MATERIAL_BASE_ICON_SIZE; } .dx-datagrid .dx-datagrid-content .dx-datagrid-table { diff --git a/styles/widgets/material/toolbar.material.less b/styles/widgets/material/toolbar.material.less index ecee6cb90980..136d1218613a 100644 --- a/styles/widgets/material/toolbar.material.less +++ b/styles/widgets/material/toolbar.material.less @@ -14,17 +14,14 @@ .dx-size-default() { @MATERIAL_TOOLBAR_HEIGHT: 76px; - @MATERIAL_TOOLBAR_MENU_ITEM_LINE_HEIGHT: 21px; - @MATERIAL_TOOLBAR_MENU_VERTICAL_PADDING: 6px; + @MATERIAL_TOOLBAR_MENU_VERTICAL_PADDING: 7px; } .dx-size-compact() { @MATERIAL_TOOLBAR_HEIGHT: 56px; - @MATERIAL_TOOLBAR_MENU_ITEM_LINE_HEIGHT: 12px; @MATERIAL_TOOLBAR_MENU_VERTICAL_PADDING: 5px; } - @MATERIAL_TOOLBAR_PADDING: 0; @MATERIAL_TOOLBAR_ITEM_SPACING: 5px; @MATERIAL_TOOLBAR_LABEL_FONT_SIZE: @MATERIAL_M_FONT_SIZE; @@ -213,10 +210,11 @@ color: @toolbar-color; font-size: @MATERIAL_BASE_FONT_SIZE; font-weight: 500; + height: @MATERIAL_BUTTON_HEIGHT; .dx-menu-item-content { - line-height: @MATERIAL_TOOLBAR_MENU_ITEM_LINE_HEIGHT; - padding: @MATERIAL_TOOLBAR_MENU_VERTICAL_PADDING @MATERIAL_MENU_HORIZONTAL_PADDING; + padding-top: @MATERIAL_TOOLBAR_MENU_VERTICAL_PADDING; + padding-bottom: @MATERIAL_TOOLBAR_MENU_VERTICAL_PADDING; } &.dx-menu-item-has-text.dx-menu-item-has-icon { diff --git a/testing/functional/tests/dataGrid/editing.ts b/testing/functional/tests/dataGrid/editing.ts index 885ebd4b606e..b73673bafc4f 100644 --- a/testing/functional/tests/dataGrid/editing.ts +++ b/testing/functional/tests/dataGrid/editing.ts @@ -666,3 +666,264 @@ test("Async Validation(Batch) - Data is not saved when a cell with async setCell } }, 'name', 'lastName'] }))); + +test("Validation(Row) - Unmodified data cell should be marked as invalid when a neighboring cell is modified (reevaluate=false) (T880238)", async t => { + const dataGrid = new DataGrid("#container"); + + const dataRow = dataGrid.getDataRow(0); + const cell0 = dataRow.getDataCell(0); + const editor0 = cell0.getEditor(); + const cell1 = dataRow.getDataCell(1); + const commandCell = dataRow.getCommandCell(2); + + await t + .click(commandCell.getButton(0)) + + .expect(cell1.isInvalid).notOk() + .expect(editor0.element.exists).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 2) + .typeText(editor0.element, '3') + .pressKey('enter') + + .expect(cell1.isInvalid).ok() + + .click(commandCell.getButton(0)) + + .expect(cell1.isInvalid).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 1) + .typeText(editor0.element, '10') + .pressKey('enter') + + .expect(cell1.isInvalid).ok() + + .click(commandCell.getButton(0)) + + .expect(cell1.isInvalid).ok('the second cell is marked as invalid') + .expect(dataRow.isEdited).ok('row is still in editing mode'); + +}).before(() => createWidget("dxDataGrid", getGridConfig({ + editing: { + mode: 'row', + allowUpdating: true + }, + columns: ['age', { + dataField: 'name', + validationRules: [{ + type: 'custom', + validationCallback: function(params) { + return params.data.age >= 10; + } + }] + }] +}))); + +test("Validation(Row) - Unmodified data cell should be marked as invalid when a neighboring cell is modified (reevaluate=true) (T880238)", async t => { + const dataGrid = new DataGrid("#container"); + + const dataRow = dataGrid.getDataRow(0); + const cell0 = dataRow.getDataCell(0); + const editor0 = cell0.getEditor(); + const cell1 = dataRow.getDataCell(1); + const commandCell = dataRow.getCommandCell(2); + + await t + .click(commandCell.getButton(0)) + + .expect(cell1.isInvalid).notOk() + .expect(editor0.element.exists).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 2) + .typeText(editor0.element, '3') + .pressKey('enter') + + .expect(cell1.isInvalid).ok() + + .click(commandCell.getButton(0)) + + .expect(cell1.isInvalid).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 1) + .typeText(editor0.element, '10') + .pressKey('enter') + + .expect(cell1.isInvalid).notOk('cell is not marked as invalid') + .expect(dataRow.isEdited).notOk('row is not in editing mode'); + +}).before(() => createWidget("dxDataGrid", getGridConfig({ + editing: { + mode: 'row', + allowUpdating: true + }, + columns: ['age', { + dataField: 'name', + validationRules: [{ + type: 'custom', + reevaluate: true, + validationCallback: function(params) { + return params.data.age >= 10; + } + }] + }] +}))); + +test("Validation(Cell) - Unmodified data cell should be marked as invalid when a neighboring cell is modified (reevaluate=false) (T880238)", async t => { + const dataGrid = new DataGrid("#container"); + + const dataRow = dataGrid.getDataRow(0); + const cell0 = dataRow.getDataCell(0); + const editor0 = cell0.getEditor(); + const cell1 = dataRow.getDataCell(1); + + await t + .click(cell0.element) + + .expect(cell1.isInvalid).notOk() + .expect(editor0.element.exists).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 2) + .typeText(editor0.element, '3') + .pressKey('enter') + + .expect(cell1.isInvalid).ok() + .expect(cell0.isEditCell).ok() + + .selectText(editor0.element, 0, 1) + .typeText(editor0.element, '10') + .pressKey('enter') + + .expect(cell1.isInvalid).ok('the second cell is still invalid') + .expect(cell0.isEditCell).ok('the first cell is still in editing mode'); + +}).before(() => createWidget("dxDataGrid", getGridConfig({ + editing: { + mode: 'cell', + allowUpdating: true + }, + columns: ['age', { + dataField: 'name', + validationRules: [{ + type: 'custom', + validationCallback: function(params) { + return params.data.age >= 10; + } + }] + }] +}))); + +test("Validation(Cell) - Unmodified data cell should be marked as invalid when a neighboring cell is modified (reevaluate=true) (T880238)", async t => { + const dataGrid = new DataGrid("#container"); + + const dataRow = dataGrid.getDataRow(0); + const cell0 = dataRow.getDataCell(0); + const editor0 = cell0.getEditor(); + const cell1 = dataRow.getDataCell(1); + + await t + .click(cell0.element) + + .expect(cell1.isInvalid).notOk() + .expect(editor0.element.exists).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 2) + .typeText(editor0.element, '3') + .pressKey('enter') + + .expect(cell1.isInvalid).ok() + .expect(cell0.isEditCell).ok() + + .selectText(editor0.element, 0, 1) + .typeText(editor0.element, '10') + .pressKey('enter') + + .expect(cell1.isInvalid).notOk('the second cell is notmarked as invalid') + .expect(cell0.isEditCell).notOk('the first cell is not in editing mode'); + +}).before(() => createWidget("dxDataGrid", getGridConfig({ + editing: { + mode: 'cell', + allowUpdating: true + }, + columns: ['age', { + dataField: 'name', + validationRules: [{ + type: 'custom', + reevaluate: true, + validationCallback: function(params) { + return params.data.age >= 10; + } + }] + }] +}))); + +[false, true].forEach(reevaluate => { + test(`Validation(Batch) - Unmodified data cell should be marked as invalid when a neighboring cell is modified (reevaluate=${reevaluate}) (T880238)`, async t => { + const dataGrid = new DataGrid("#container"); + + const saveButton = dataGrid.getHeaderPanel().getSaveButton(); + const dataRow = dataGrid.getDataRow(0); + const cell0 = dataRow.getDataCell(0); + const editor0 = cell0.getEditor(); + const cell1 = dataRow.getDataCell(1); + + await t + .click(cell0.element) + + .expect(cell1.isInvalid).notOk() + .expect(editor0.element.exists).ok() + + .click(editor0.element) + .selectText(editor0.element, 0, 2) + .typeText(editor0.element, '3') + .pressKey('enter') + + .expect(cell1.isInvalid).notOk() + .expect(cell0.isModified).ok() + + .click(saveButton) + + .expect(cell1.isInvalid).ok() + .expect(cell0.isModified).ok() + + .click(cell0.element) + + .expect(editor0.element.exists).ok() + + .selectText(editor0.element, 0, 1) + .typeText(editor0.element, '10') + .pressKey('enter') + + .expect(cell1.isInvalid).notOk() + .expect(cell0.isModified).ok() + + .click(saveButton) + + .expect(cell1.isInvalid).notOk('the second cell is not marked as invalid') + .expect(cell0.isModified).notOk('the first cell is not marked as modified') + .expect(cell0.isEditCell).notOk('the first cell is not in editing mode'); + + }).before(() => createWidget("dxDataGrid", getGridConfig({ + editing: { + mode: 'batch', + allowUpdating: true + }, + columns: ['age', { + dataField: 'name', + validationRules: [{ + type: 'custom', + reevaluate, + validationCallback: function(params) { + return params.data.age >= 10; + } + }] + }] + }))); +}); + diff --git a/testing/helpers/fileManagerHelpers.js b/testing/helpers/fileManagerHelpers.js index bd9d447d60d3..9210132688c6 100644 --- a/testing/helpers/fileManagerHelpers.js +++ b/testing/helpers/fileManagerHelpers.js @@ -21,6 +21,7 @@ export const Consts = { DIRS_TREE_CLASS: 'dx-filemanager-dirs-tree', ITEMS_VIEW_CLASS: 'dx-filemanager-files-view', DIALOG_CLASS: 'dx-filemanager-dialog', + THUMBNAILS_VIEW_PORT_CLASS: 'dx-filemanager-thumbnails-view-port', THUMBNAILS_ITEM_CLASS: 'dx-filemanager-thumbnails-item', THUMBNAILS_ITEM_NAME_CLASS: 'dx-filemanager-thumbnails-item-name', THUMBNAILS_ITEM_CONTENT_CLASS: 'dx-filemanager-thumbnails-item-content', @@ -61,6 +62,7 @@ export const Consts = { DROPDOWN_MENU_CONTENT_CLASS: 'dx-scrollview-content', DROPDOWN_MENU_LIST_ITEM_CLASS: 'dx-list-item', SCROLLABLE_ClASS: 'dx-scrollable', + SCROLLABLE_CONTAINER_ClASS: 'dx-scrollable-container', EDITING_CONTAINER: 'dx-filemanager-editing-container', FILE_UPLOADER_INPUT: 'dx-fileuploader-input' }; @@ -199,6 +201,10 @@ export class FileManagerWrapper { return this._$element.find(`.${Consts.ITEMS_GRID_VIEW_CLASS}`); } + getThumbnailsViewPort() { + return this._$element.find(`.${Consts.THUMBNAILS_VIEW_PORT_CLASS}`); + } + getThumbnailsItems() { return this._$element.find(`.${Consts.THUMBNAILS_ITEM_CLASS}`); } @@ -227,14 +233,26 @@ export class FileManagerWrapper { return this.findThumbnailsItem(itemName).is(`.${Consts.FOCUSED_STATE_CLASS}`); } + getThumbnailsViewScrollable() { + return this.getThumbnailsViewPort().find(`.${Consts.SCROLLABLE_ClASS}`); + } + + getThumbnailsViewScrollableContainer() { + return this.getThumbnailsViewScrollable().find(`.${Consts.SCROLLABLE_CONTAINER_ClASS}`); + } + findDetailsItem(itemName) { return this._$element.find(`.${Consts.GRID_DATA_ROW_CLASS} > td:contains('${itemName}')`); } - getDetailsItemScrollable() { + getDetailsViewScrollable() { return this.getDetailsItemList().find(`.${Consts.SCROLLABLE_ClASS}`); } + getDetailsViewScrollableContainer() { + return this.getDetailsViewScrollable().find(`.${Consts.SCROLLABLE_CONTAINER_ClASS}`); + } + getDetailsItemsNames() { return this._$element.find(`.${Consts.DETAILS_ITEM_NAME_CLASS}`); } @@ -417,15 +435,18 @@ export class FileManagerProgressPanelWrapper { this._$element = $element; } + getInfosContainer() { + return this._$element.find('.dx-filemanager-progress-panel-infos-container'); + } + getInfos() { - return this._$element - .find('.dx-filemanager-progress-panel-infos-container > .dx-filemanager-progress-panel-info') + return this.getInfosContainer().find('.dx-filemanager-progress-panel-info') .map((_, info) => new FileManagerProgressPanelInfoWrapper($(info))) .get(); } getSeparators() { - return this._$element.find('.dx-filemanager-progress-panel-infos-container > .dx-filemanager-progress-panel-separator'); + return this.getInfosContainer().find('.dx-filemanager-progress-panel-separator'); } findProgressBoxes($container) { @@ -672,6 +693,17 @@ export const createTestFileSystem = () => { ]; }; +export const createHugeFileSystem = () => { + const result = []; + for(let i = 0; i < 10; i++) { + result.push({ + name: `File ${i}.txt`, + isDirectory: false + }); + } + return result; +}; + export const createUploaderFiles = count => { const result = []; diff --git a/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js b/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js index 54404828f645..921aaff660b3 100644 --- a/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js +++ b/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js @@ -18141,6 +18141,90 @@ QUnit.module('API methods', baseModuleConfig, () => { assert.strictEqual(columns[1].width, 200, 'width of the second column'); assert.strictEqual(columns[2].width, 200, 'width of the third column'); }); + + QUnit.test('Group Row - expandRow should resolve its promise only after re-rendering (T880769)', function(assert) { + // arrange + const getRowsInfo = function(element) { + const $rows = $(element).find('.dx-datagrid-rowsview .dx-row[role=\'row\']'); + return { + rowCount: $rows.length, + groupRow: $($rows.eq(0)).hasClass('dx-group-row'), + dataRow: $($rows.eq(1)).hasClass('dx-data-row') + }; + }; + const dataGrid = createDataGrid({ + dataSource: [ + { column1: 'value1', column2: 'value2' } + ], + grouping: { + autoExpandAll: false, + }, + columns: [ + { + dataField: 'column1', + groupIndex: 0 + }, + 'column2' + ], + onRowExpanded: function() { + const info = getRowsInfo(dataGrid.element()); + assert.step(`rowExpanded rowCount: ${info.rowCount}, groupRow: ${info.groupRow}, dataRow: ${info.dataRow}`); + } + }); + this.clock.tick(); + + // act + dataGrid.expandRow(['value1']).done(() => { + const info = getRowsInfo(dataGrid.element()); + + assert.step(`done rowCount: ${info.rowCount}, groupRow: ${info.groupRow}, dataRow: ${info.dataRow}`); + }); + this.clock.tick(); + + assert.verifySteps([ + 'rowExpanded rowCount: 2, groupRow: true, dataRow: true', + 'done rowCount: 2, groupRow: true, dataRow: true' + ]); + }); + + QUnit.test('Master Row - expandRow should resolve its promise only after re-rendering (T880769)', function(assert) { + // arrange + const getRowsInfo = function(element) { + const $rows = $(element).find('.dx-datagrid-rowsview .dx-row[role=\'row\']'); + return { + rowCount: $rows.length, + masterRow: $($rows.eq(0)).hasClass('dx-data-row'), + detailRow: $($rows.eq(1)).hasClass('dx-master-detail-row') + }; + }; + const dataGrid = createDataGrid({ + keyExpr: 'id', + dataSource: [ + { id: 1 } + ], + masterDetail: { + enabled: true + }, + onRowExpanded: function() { + const info = getRowsInfo(dataGrid.element()); + assert.step(`rowExpanded rowCount: ${info.rowCount}, masterRow: ${info.masterRow}, detailRow: ${info.detailRow}`); + } + }); + this.clock.tick(); + + // act + dataGrid.expandRow(1).done(() => { + const info = getRowsInfo(dataGrid.element()); + + assert.step(`done rowCount: ${info.rowCount}, masterRow: ${info.masterRow}, detailRow: ${info.detailRow}`); + }); + this.clock.tick(); + + assert.verifySteps([ + 'rowExpanded rowCount: 2, masterRow: true, detailRow: true', + 'done rowCount: 2, masterRow: true, detailRow: true' + ]); + }); }); QUnit.module('templates', baseModuleConfig, () => { diff --git a/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js b/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js index 76cb46361e21..777f60a3220a 100644 --- a/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js +++ b/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js @@ -12624,7 +12624,7 @@ QUnit.module('Editing with validation', { const key = this.getKeyByRowIndex(0); // assert - assert.notOk(this.validatingController.isInvalidCell({ rowKey: key, columnIndex: 0 }), 'cell should be invalid'); + assert.ok(this.validatingController.isInvalidCell({ rowKey: key, columnIndex: 0 }), 'cell should be invalid'); }); QUnit.test('validatingController.validateCell should not call the validate method of the current validator', function(assert) { diff --git a/testing/tests/DevExpress.ui.widgets.editors/editor.tests.js b/testing/tests/DevExpress.ui.widgets.editors/editor.tests.js index feff48d6dad7..fb3c84da8dea 100644 --- a/testing/tests/DevExpress.ui.widgets.editors/editor.tests.js +++ b/testing/tests/DevExpress.ui.widgets.editors/editor.tests.js @@ -778,6 +778,31 @@ QUnit.module('Validation Events', { // assert assert.ok(!handler.called, 'Validating handler should not be called'); }); + + QUnit.test('validationRequest fires before valueChanged callback', function(assert) { + const editor = this.fixture.createEditor({ + value: 'empty', + onValueChanged: ({ value, previousValue }) => { + assert.step(`Value changed from "${previousValue}" to "${value}"`); + if(value.toUpperCase() !== value) { + editor.option('value', value.toUpperCase()); + } + } + }); + + editor.validationRequest.add(({ value }) => { + assert.step(`Validate value: "${value}"`); + }); + + editor.option('value', 'test'); + + assert.verifySteps([ + 'Validate value: "test"', + 'Value changed from "empty" to "test"', + 'Validate value: "TEST"', + 'Value changed from "test" to "TEST"' + ]); + }); }); QUnit.module('aria accessibility', { diff --git a/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js b/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js index f87b595ca33d..bc28cb0f53c3 100644 --- a/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js +++ b/testing/tests/DevExpress.ui.widgets/diagramParts/options.tests.js @@ -213,6 +213,32 @@ QUnit.module('Options', moduleConfig, () => { assert.equal(this.instance.option('simpleView'), true); }); + test('should change defaultItemProperties', function(assert) { + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.style, undefined); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.textStyle, undefined); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectionProperties, undefined); + this.instance.option('defaultItemProperties.style', { fill: '#ff0000' }); + this.instance.option('defaultItemProperties.textStyle', { fill: '#ff0000' }); + this.instance.option('defaultItemProperties.connectorLineType', 'straight'); + this.instance.option('defaultItemProperties.connectorLineStart', 'filledTriangle'); + this.instance.option('defaultItemProperties.connectorLineEnd', 'filledTriangle'); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.style['fill'], '#ff0000'); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.textStyle['fill'], '#ff0000'); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectorProperties.lineOption, 0); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectorProperties.startLineEnding, 3); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectorProperties.endLineEnding, 3); + this.instance.option('defaultItemProperties.style', { fill: '#ee0000' }); + this.instance.option('defaultItemProperties.textStyle', { fill: '#ee0000' }); + this.instance.option('defaultItemProperties.connectorLineType', 'orthogonal'); + this.instance.option('defaultItemProperties.connectorLineStart', 'outlinedTriangle'); + this.instance.option('defaultItemProperties.connectorLineEnd', 'outlinedTriangle'); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.style['fill'], '#ee0000'); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.textStyle['fill'], '#ee0000'); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectorProperties.lineOption, 1); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectorProperties.startLineEnding, 2); + assert.equal(this.instance._diagramInstance.selection.inputPosition.initialProperties.connectorProperties.endLineEnding, 2); + }); + test('should change dataSource options', function(assert) { assert.equal(this.instance._diagramInstance.documentDataSource, undefined); this.instance.option('nodes.dataSource', [ diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js index 64be3d751d5c..aa2823b0a185 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js @@ -183,7 +183,7 @@ QUnit.module('Details View', moduleConfig, () => { }); test('Details view must has ScrollView', function(assert) { - assert.ok(this.wrapper.getDetailsItemScrollable().length); + assert.ok(this.wrapper.getDetailsViewScrollable().length); }); test('\'Back\' directory must not be sortable', function(assert) { diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/navigation.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/navigation.tests.js index 7047e6da9e69..7f0d2fc2a511 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/navigation.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/navigation.tests.js @@ -5,7 +5,7 @@ import 'ui/file_manager'; import FileItemsController from 'ui/file_manager/file_items_controller'; import FileManagerBreadcrumbs from 'ui/file_manager/ui.file_manager.breadcrumbs'; import fx from 'animation/fx'; -import { FileManagerWrapper, FileManagerBreadcrumbsWrapper, createTestFileSystem } from '../../../helpers/fileManagerHelpers.js'; +import { FileManagerWrapper, FileManagerBreadcrumbsWrapper, createTestFileSystem, createHugeFileSystem, Consts } from '../../../helpers/fileManagerHelpers.js'; const moduleConfig = { @@ -659,4 +659,88 @@ QUnit.module('Navigation operations', moduleConfig, () => { assert.notOk(this.wrapper.isThumbnailsItemSelected(itemName), 'item not selected'); assert.ok(this.wrapper.isThumbnailsItemFocused(itemName), 'item focused'); }); + + test('Details view - must keep scroll position', function(assert) { + this.fileManager.option({ + width: 500, + height: 250, + fileSystemProvider: createHugeFileSystem(), + itemView: { + mode: 'details' + } + }); + this.clock.tick(400); + + const scrollPosition = 100; + this.wrapper.getDetailsViewScrollableContainer().scrollTop(scrollPosition); + this.clock.tick(400); + + this.fileManager.refresh(); + this.clock.tick(800); + + assert.strictEqual(this.wrapper.getDetailsViewScrollableContainer().scrollTop(), scrollPosition, 'scroll position is the same'); + }); + + test('Thumbnails view - must keep scroll position', function(assert) { + const originalFunc = renderer.fn.width; + renderer.fn.width = () => 1200; + + this.fileManager.option({ + width: 500, + height: 250, + fileSystemProvider: createHugeFileSystem() + }); + this.clock.tick(400); + + const scrollPosition = 150; + this.wrapper.getThumbnailsViewScrollableContainer().scrollTop(scrollPosition); + this.clock.tick(400); + + this.fileManager.refresh(); + this.clock.tick(800); + + assert.strictEqual(this.wrapper.getThumbnailsViewScrollableContainer().scrollTop(), scrollPosition, 'scroll position is the same'); + + renderer.fn.width = originalFunc; + }); + + test('All views - must keep scroll position for sync focused item', function(assert) { + // focus item in thumbnails and remember its scroll position + this.fileManager.option('fileSystemProvider', createHugeFileSystem()); + this.clock.tick(400); + + this.wrapper.findThumbnailsItem('Folder 0').trigger('dxpointerdown'); + this.clock.tick(400); + this.wrapper.getThumbnailsViewPort().trigger($.Event('keydown', { key: 'PageDown' })); + this.clock.tick(400); + + const thumbnailsScrollPosition = this.wrapper.getThumbnailsViewScrollableContainer().scrollTop(); + + // switch to details and remember scroll position + this.wrapper.getToolbarDropDownButton().find(`.${Consts.BUTTON_CLASS}`).trigger('dxclick'); + this.clock.tick(400); + let detailsViewSelector = this.wrapper.getToolbarViewSwitcherListItem(0); + $(detailsViewSelector).trigger('dxclick'); + this.clock.tick(400); + + const detailsScrollPosition = this.wrapper.getDetailsViewScrollableContainer().scrollTop(); + + // switch to thumbnails and check scroll position + this.wrapper.getToolbarDropDownButton().find(`.${Consts.BUTTON_CLASS}`).trigger('dxclick'); + this.clock.tick(400); + const thumbnailsViewSelector = this.wrapper.getToolbarViewSwitcherListItem(1); + $(thumbnailsViewSelector).trigger('dxclick'); + this.clock.tick(400); + + assert.strictEqual(this.wrapper.getThumbnailsViewScrollableContainer().scrollTop(), thumbnailsScrollPosition, 'thumbnails scroll position is the same'); + + // switch to details and check scroll position + this.wrapper.getToolbarDropDownButton().find(`.${Consts.BUTTON_CLASS}`).trigger('dxclick'); + this.clock.tick(400); + detailsViewSelector = this.wrapper.getToolbarViewSwitcherListItem(0); + $(detailsViewSelector).trigger('dxclick'); + this.clock.tick(400); + + assert.strictEqual(this.wrapper.getDetailsViewScrollableContainer().scrollTop(), detailsScrollPosition, 'details scroll position is the same'); + }); }); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/progressPanel.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/progressPanel.tests.js index b0d12dc7c342..9131517d707a 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/progressPanel.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/progressPanel.tests.js @@ -360,7 +360,6 @@ QUnit.module('Progress panel tests', moduleConfig, () => { infos[0].common.$closeButton.trigger('dxclick'); this.clock.tick(400); - infos = this.progressPanelWrapper.getInfos(); separators = this.progressPanelWrapper.getSeparators(); assert.equal(separators.length, 1, 'Correct number of separators'); @@ -385,6 +384,34 @@ QUnit.module('Progress panel tests', moduleConfig, () => { separators = this.progressPanelWrapper.getSeparators(); assert.equal(separators.length, 0, 'Correct number of separators'); + infos[0].common.$closeButton.trigger('dxclick'); + this.clock.tick(400); + + separators = this.progressPanelWrapper.getSeparators(); + assert.equal(separators.length, 0, 'Correct number of separators'); + + const operationInfo5 = this.progressPanel.addOperation('Operation 5'); + this.progressPanel.completeOperation(operationInfo5, 'Completed 5'); + + separators = this.progressPanelWrapper.getSeparators(); + assert.equal(separators.length, 0, 'Correct number of separators'); + }); + + test('Empty list text must be displayed when there are no operations on the panel', function(assert) { + createProgressPanel(this); + + assert.strictEqual(this.progressPanelWrapper.getInfosContainer().text(), 'No operations', 'Empty list text rendered after first load'); + + const operationInfo1 = this.progressPanel.addOperation('Operation 1'); + this.progressPanel.completeOperation(operationInfo1, 'Completed 1'); + + assert.notStrictEqual(this.progressPanelWrapper.getInfosContainer().text(), 'No operations', 'There are no empty list text'); + + const infos = this.progressPanelWrapper.getInfos(); + infos[0].common.$closeButton.trigger('dxclick'); + this.clock.tick(400); + + assert.strictEqual(this.progressPanelWrapper.getInfosContainer().text(), 'No operations', 'Empty list text rendered when there are no operatoins left'); }); }); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/remoteProvider.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/remoteProvider.tests.js index e636573a3d36..220333dd119b 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/remoteProvider.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/remoteProvider.tests.js @@ -72,7 +72,7 @@ QUnit.module('Remote Provider', moduleConfig, () => { const done = assert.async(); ajaxMock.setup({ - url: this.options.endpointUrl + '?command=Rename&arguments=%7B%22pathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22name%22%3A%22Test%201%22%7D', + url: this.options.endpointUrl + '?command=Rename&arguments=%7B%22pathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22isDirectory%22%3Afalse%2C%22name%22%3A%22Test%201%22%7D', responseText: { success: true }, @@ -91,7 +91,7 @@ QUnit.module('Remote Provider', moduleConfig, () => { const done = assert.async(); ajaxMock.setup({ - url: this.options.endpointUrl + '?command=Remove&arguments=%7B%22pathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%7D', + url: this.options.endpointUrl + '?command=Remove&arguments=%7B%22pathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22isDirectory%22%3Afalse%7D', responseText: { success: true }, @@ -111,7 +111,7 @@ QUnit.module('Remote Provider', moduleConfig, () => { const done = assert.async(); ajaxMock.setup({ - url: this.options.endpointUrl + '?command=Move&arguments=%7B%22sourcePathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22destinationPathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FImages%22%2C%22name%22%3A%22Images%22%7D%5D%7D', + url: this.options.endpointUrl + '?command=Move&arguments=%7B%22sourcePathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22sourceIsDirectory%22%3Afalse%2C%22destinationPathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FImages%22%2C%22name%22%3A%22Images%22%7D%5D%7D', responseText: { success: true }, @@ -132,7 +132,7 @@ QUnit.module('Remote Provider', moduleConfig, () => { const done = assert.async(); ajaxMock.setup({ - url: this.options.endpointUrl + '?command=Copy&arguments=%7B%22sourcePathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22destinationPathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FImages%22%2C%22name%22%3A%22Images%22%7D%5D%7D', + url: this.options.endpointUrl + '?command=Copy&arguments=%7B%22sourcePathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Documents%22%7D%5D%2C%22sourceIsDirectory%22%3Afalse%2C%22destinationPathInfo%22%3A%5B%7B%22key%22%3A%22Root%22%2C%22name%22%3A%22Root%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%22%2C%22name%22%3A%22Files%22%7D%2C%7B%22key%22%3A%22Root%2FFiles%2FImages%22%2C%22name%22%3A%22Images%22%7D%5D%7D', responseText: { success: true }, diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/selection.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/selection.tests.js index 8a24592ee33c..200f620481b5 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/selection.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/selection.tests.js @@ -2,7 +2,7 @@ import $ from 'jquery'; const { test } = QUnit; import 'ui/file_manager'; import fx from 'animation/fx'; -import { FileManagerWrapper, createTestFileSystem } from '../../../helpers/fileManagerHelpers.js'; +import { FileManagerWrapper, createTestFileSystem, isDesktopDevice } from '../../../helpers/fileManagerHelpers.js'; import { triggerCellClick } from '../../../helpers/fileManager/events.js'; const moduleConfig = { @@ -220,4 +220,62 @@ QUnit.module('Selection', moduleConfig, () => { assert.strictEqual(selectionSpy.callCount, 1, 'no event fired'); }); + test('Details view - select all raises selection changed event', function(assert) { + if(!isDesktopDevice()) { + assert.ok(true); + return; + } + + const selectionSpy = sinon.spy(); + + createFileManager(this, { + selectionMode: 'multiple', + currentPath: 'Folder 1', + onSelectionChanged: selectionSpy + }); + + this.wrapper.getSelectAllCheckBox().trigger('dxclick'); + this.clock.tick(400); + + assert.strictEqual(selectionSpy.callCount, 1, 'event fired'); + assert.strictEqual(selectionSpy.args[0][0].selectedItems.length, 4, 'all items in selection'); + assert.strictEqual(selectionSpy.args[0][0].selectedItemKeys.length, 4, 'all selected keys provided'); + assert.strictEqual(selectionSpy.args[0][0].currentSelectedItemKeys.length, 4, 'all items became selected'); + assert.deepEqual(selectionSpy.args[0][0].currentDeselectedItemKeys, [], 'one item became deselected'); + }); + + test('Thumbnails view - single selection works same as in details', function(assert) { + const selectionSpy = sinon.spy(); + + createFileManager(this, { + selectionMode: 'single', + itemView: { + mode: 'thumbnails' + }, + onSelectionChanged: selectionSpy + }); + + const $item = this.wrapper.findThumbnailsItem('Folder 1'); + triggerCellClick($item); + this.clock.tick(400); + + assert.ok(this.wrapper.isThumbnailsItemSelected('Folder 1'), 'item selected'); + assert.ok(this.wrapper.isThumbnailsItemFocused('Folder 1'), 'item focused'); + assert.strictEqual(this.wrapper.getThumbnailsSelectedItems().length, 1, 'one item is selected in markup'); + assert.strictEqual(selectionSpy.callCount, 1, 'event fired'); + + this.wrapper.getThumbnailsViewPort().trigger($.Event('keydown', { key: 'ArrowRight' })); + this.clock.tick(400); + + assert.ok(this.wrapper.isThumbnailsItemSelected('Folder 2'), 'next item selected'); + assert.ok(this.wrapper.isThumbnailsItemFocused('Folder 2'), 'next item focused'); + assert.strictEqual(this.wrapper.getThumbnailsSelectedItems().length, 1, 'one item is selected in markup'); + assert.strictEqual(selectionSpy.callCount, 2, 'event fired'); + + assert.deepEqual(selectionSpy.args[1][0].selectedItems.map(item => item.key), [ 'Folder 2' ], 'item in selection'); + assert.deepEqual(selectionSpy.args[1][0].selectedItemKeys, [ 'Folder 2' ], 'selected item key provided'); + assert.deepEqual(selectionSpy.args[1][0].currentSelectedItemKeys, [ 'Folder 2' ], 'one item became selected'); + assert.deepEqual(selectionSpy.args[1][0].currentDeselectedItemKeys, [ 'Folder 1' ], 'one item became deselected'); + }); + }); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/thumbnailsView.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/thumbnailsView.tests.js index 3ee5a3f71701..f667623e54af 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/thumbnailsView.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/thumbnailsView.tests.js @@ -275,4 +275,8 @@ QUnit.module('Thumbnails View', moduleConfig, () => { assert.strictEqual($(spy.args[1][0].element).get(0), this.$element.get(0), 'element is correct'); }); + test('Thumbnails view must has ScrollView', function(assert) { + assert.ok(this.wrapper.getThumbnailsViewScrollable().length); + }); + }); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/toolbar.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/toolbar.tests.js index 17fe72b6ae71..c4e30a152fb0 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/toolbar.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/toolbar.tests.js @@ -894,4 +894,52 @@ QUnit.module('Toolbar', moduleConfig, () => { assert.strictEqual($fileToolbarElements.eq(2).text(), 'Rename', 'third file element correct'); }); + test('custom toolbar items have compact mode', function(assert) { + createFileManager(false); + this.clock.tick(400); + + const fileManager = this.wrapper.getInstance(); + fileManager.option('toolbar.items', [ + { + widget: 'dxButton', + options: { + text: 'item with long name 0', + icon: 'upload' + }, + locateInMenu: 'never' + }, + { + widget: 'dxButton', + options: { + text: 'item with long name 1', + icon: 'upload' + }, + locateInMenu: 'never' + }, + { + widget: 'dxButton', + options: { + text: 'item with long name 2' + }, + locateInMenu: 'never' + } + ]); + this.clock.tick(400); + + const originalWidth = renderer.fn.width; + renderer.fn.width = () => 400; + $('#fileManager').css('width', '100%'); + fileManager.repaint(); + this.clock.tick(800); + + const $generalToolbarElements = this.wrapper.getGeneralToolbarElements(); + + assert.strictEqual($generalToolbarElements.length, 3, 'there are three elements in general toolbar'); + assert.strictEqual($generalToolbarElements.eq(0).find(`.${Consts.BUTTON_TEXT_CLASS}:visible`).text(), '', 'fisrt general element correct'); + assert.strictEqual($generalToolbarElements.eq(1).find(`.${Consts.BUTTON_TEXT_CLASS}:visible`).text(), '', 'second general element correct'); + assert.strictEqual($generalToolbarElements.eq(2).find(`.${Consts.BUTTON_TEXT_CLASS}:visible`).text(), 'item with long name 2', 'third general element correct'); + + renderer.fn.width = originalWidth; + }); + }); diff --git a/testing/tests/DevExpress.ui.widgets/sortable.tests.js b/testing/tests/DevExpress.ui.widgets/sortable.tests.js index c17b0fc0664b..d4b946c85f6f 100644 --- a/testing/tests/DevExpress.ui.widgets/sortable.tests.js +++ b/testing/tests/DevExpress.ui.widgets/sortable.tests.js @@ -85,7 +85,15 @@ QUnit.testStart(function() {
item9
-
+
+
item1
+
item2
+ +
+
item3
+
item4
+
+
`; $('#qunit-fixture').html(markup); @@ -2920,3 +2928,79 @@ QUnit.module('Dragging between sortables with scroll', { }); }); }); + +QUnit.module('Drag and drop with nested sortable', crossComponentModuleConfig, () => { + QUnit.test('The onReorder event should be raised for a nested sortable', function(assert) { + // arrange + const onReorder1 = sinon.spy(); + const onReorder2 = sinon.spy(); + + this.createSortable({ + dropFeedbackMode: 'push', + filter: '.draggable', + group: 'shared', + moveItemOnDrop: true, + onReorder: onReorder1 + }, $('#parentSortable')); + + const sortable2 = this.createSortable({ + dropFeedbackMode: 'push', + filter: '.draggable', + group: 'shared', + moveItemOnDrop: true, + onReorder: onReorder2 + }, $('#nestedSortable')); + + // act + const $itemElement = sortable2.$element().children().eq(0); + pointerMock($itemElement).start({ x: 0, y: $itemElement.offset().top }).down().move(0, 25).move(0, 5).up(); + + // assert + assert.strictEqual(onReorder1.callCount, 0, 'parent sortable - onReorder event is not called'); + assert.strictEqual(onReorder2.callCount, 1, 'nested sortable - onReorder event is called'); + assert.deepEqual(onReorder2.getCall(0).args[0].fromComponent, sortable2, 'onReorder args - fromComponent'); + assert.deepEqual(onReorder2.getCall(0).args[0].toComponent, sortable2, 'onReorder args - toComponent'); + }); + + QUnit.test('Dragging item into a nested sortable', function(assert) { + // arrange + const onReorder1 = sinon.spy(); + const onReorder2 = sinon.spy(); + const onAdd = sinon.spy(); + const onRemove = sinon.spy(); + + const sortable1 = this.createSortable({ + dropFeedbackMode: 'push', + filter: '.draggable', + group: 'shared', + moveItemOnDrop: true, + onReorder: onReorder1, + onRemove: onRemove + }, $('#parentSortable')); + + const sortable2 = this.createSortable({ + dropFeedbackMode: 'push', + filter: '.draggable', + group: 'shared', + moveItemOnDrop: true, + onReorder: onReorder2, + onAdd: onAdd + }, $('#nestedSortable')); + + // act + const $itemElement = sortable1.$element().children().eq(0); + const offsetTop = sortable2.$element().offset().top; + + pointerMock($itemElement).start({ x: 0, y: $itemElement.offset().top }).down().move(0, offsetTop + 5).move(0, 5).up(); + + // assert + assert.strictEqual(onReorder1.callCount, 0, 'parent sortable - onReorder event is not called'); + assert.strictEqual(onReorder2.callCount, 0, 'nested sortable - onReorder event is not called'); + + assert.strictEqual(onRemove.callCount, 1, 'parent sortable - onRemove event is called'); + assert.strictEqual(onAdd.callCount, 1, 'nested sortable - onAdd event is called'); + assert.deepEqual(onAdd.getCall(0).args[0].fromComponent, sortable1, 'onAdd args - fromComponent'); + assert.deepEqual(onAdd.getCall(0).args[0].toComponent, sortable2, 'onAdd args - toComponent'); + assert.strictEqual(onAdd.getCall(0).args[0].toIndex, 1, 'onAdd args - toIndex'); + }); +}); diff --git a/testing/tests/DevExpress.viz.core/annotations.tests.js b/testing/tests/DevExpress.viz.core/annotations.tests.js index c359659dd5d6..018c9cb79963 100644 --- a/testing/tests/DevExpress.viz.core/annotations.tests.js +++ b/testing/tests/DevExpress.viz.core/annotations.tests.js @@ -104,6 +104,36 @@ QUnit.test('Merge customizeAnnotation result and common+item options', function( assert.equal(customizeAnnotation.getCall(0).args[0], itemOptions); }); +// T881143 +QUnit.test('Merge customizeAnnotation result and common. Image as string', function(assert) { + const customizeAnnotation = sinon.stub().returns({ image: 'customized_url' }); + const itemOptions = { + x: 10, y: 20, + type: 'image', + image: { url: 'some_url', width: 10 } + }; + const annotation = this.createAnnotations([itemOptions], { image: { height: 10 } }, customizeAnnotation)[0]; + + annotation.draw(this.widget, this.group); + + assert.deepEqual(this.renderer.image.firstCall.args, [0, 0, 10, 10, 'customized_url', 'center']); +}); + +// T881143 +QUnit.test('Merge customizeAnnotation result and common. Customize callback returned undefined', function(assert) { + const customizeAnnotation = sinon.stub().returns(undefined); + const itemOptions = { + x: 10, y: 20, + type: 'image', + image: { url: 'some_url', width: 10 } + }; + const annotation = this.createAnnotations([itemOptions], { image: { height: 10 } }, customizeAnnotation)[0]; + + annotation.draw(this.widget, this.group); + + assert.deepEqual(this.renderer.image.firstCall.args, [0, 0, 10, 10, 'some_url', 'center']); +}); + QUnit.test('Image option is string', function(assert) { const itemOptions = { x: 10, y: 20, @@ -1151,6 +1181,20 @@ QUnit.test('customizeTooltip in item', function(assert) { QUnit.module('Misc', environment); +QUnit.test('customizeAnnotation is not a function', function(assert) { + const customizeAnnotation = { call: true }; + const itemOptions = { + x: 10, y: 20, + type: 'image', + image: { url: 'some_url', width: 10 } + }; + const annotation = this.createAnnotations([itemOptions], { image: { height: 10 } }, customizeAnnotation)[0]; + + annotation.draw(this.widget, this.group); + + assert.deepEqual(this.renderer.image.firstCall.args, [0, 0, 10, 10, 'some_url', 'center']); +}); + QUnit.test('Do not create annotation with wrong type', function(assert) { const annotations = this.createAnnotations([ { type: 'image' }, diff --git a/themebuilder/modules/less-template-loader.js b/themebuilder/modules/less-template-loader.js index 9413fcb78bbd..7b2c6b8f0b33 100644 --- a/themebuilder/modules/less-template-loader.js +++ b/themebuilder/modules/less-template-loader.js @@ -71,14 +71,13 @@ class LessMetadataPostCompiler { if(this.swatchSelector) { const escapedSelector = this.swatchSelector.replace('.', '\\.'); - const customStylesDuplicateRegex = new RegExp(`\\s+${escapedSelector}\\s+\\.dx-theme-.*?-typography\\s+\\.dx-theme-.*?{[\\s\\S]*?}[\\r\\n]*?`, 'g'); const swatchOrderRegex = new RegExp(`([ \\t]*)([\\w\\.#:\\*][\\w\\.#:\\*\\->()\\s]*)(${escapedSelector}\\s)([^,{+~]*)`, 'gm'); + const changeTypographyRulesOrderRegex = /(\.dx-swatch-.*?)\s(\.dx-theme-.*?-typography)(.*?)\s{/g; const themeMarkerRegex = /(\.dx-theme-marker\s*{\s*font-family:\s*['"]dx\..*?\.)(.*)(['"])/g; css = css .replace(swatchOrderRegex, '$1$3$2$4') - .replace(customStylesDuplicateRegex, '') - .replace(/\s\.dx-theme-.*?-typography/g, '') + .replace(changeTypographyRulesOrderRegex, '$2 $1$3,$2$1$3 {') .replace(themeMarkerRegex, '$1' + this.colorScheme + '$3'); } diff --git a/themebuilder/tests/less-template-loader-spec.js b/themebuilder/tests/less-template-loader-spec.js index 16076607cea2..55e60a1aa492 100644 --- a/themebuilder/tests/less-template-loader-spec.js +++ b/themebuilder/tests/less-template-loader-spec.js @@ -190,9 +190,12 @@ describe('LessTemplateLoader', () => { themeName, colorScheme, metadata).then(data => { - assert.equal(data.css, `.dx-swatch-my-custom, -.dx-swatch-my-custom .dx-theme-accent-as-text-color, -.dx-swatch-my-custom div { + assert.equal(data.css, `.dx-swatch-my-custom .dx-theme-accent-as-text-color, +.dx-swatch-my-custom div, +.dx-theme-generic-typography .dx-swatch-my-custom, +.dx-theme-generic-typography .dx-swatch-my-custom .dx-theme-accent-as-text-color, +.dx-theme-generic-typography.dx-swatch-my-custom, +.dx-theme-generic-typography.dx-swatch-my-custom .dx-theme-accent-as-text-color { color: #fff; } `); @@ -271,9 +274,12 @@ describe('LessTemplateLoader', () => { const lessTemplateLoader = new LessTemplateLoader(config); lessTemplateLoader._makeInfoHeader = emptyHeader; return lessTemplateLoader.compileLess(less, {}, metadataVariables).then(data => { - assert.equal(data.css, `.dx-swatch-my-custom, -.dx-swatch-my-custom .dx-theme-accent-as-text-color, -.dx-swatch-my-custom div { + assert.equal(data.css, `.dx-swatch-my-custom .dx-theme-accent-as-text-color, +.dx-swatch-my-custom div, +.dx-theme-generic-typography .dx-swatch-my-custom, +.dx-theme-generic-typography .dx-swatch-my-custom .dx-theme-accent-as-text-color, +.dx-theme-generic-typography.dx-swatch-my-custom, +.dx-theme-generic-typography.dx-swatch-my-custom .dx-theme-accent-as-text-color { color: #fff; } `); @@ -362,9 +368,12 @@ describe('LessTemplateLoader', () => { assert.equal(data.css, `.dx-swatch-my-custom .dx-theme-marker { font-family: 'dx.generic.my-custom'; } -.dx-swatch-my-custom, .dx-swatch-my-custom .dx-theme-accent-as-text-color, -.dx-swatch-my-custom div { +.dx-swatch-my-custom div, +.dx-theme-generic-typography .dx-swatch-my-custom, +.dx-theme-generic-typography .dx-swatch-my-custom .dx-theme-accent-as-text-color, +.dx-theme-generic-typography.dx-swatch-my-custom, +.dx-theme-generic-typography.dx-swatch-my-custom .dx-theme-accent-as-text-color { color: #fff; } `); diff --git a/ts/dx.all.d.ts b/ts/dx.all.d.ts index d1ef208e7ed6..ab54dc037b9e 100644 --- a/ts/dx.all.d.ts +++ b/ts/dx.all.d.ts @@ -3232,6 +3232,8 @@ declare module DevExpress.ui { customShapeTemplate?: DevExpress.core.template | ((container: DevExpress.core.dxElement, data: { item?: dxDiagramShape }) => any); /** @name dxDiagram.Options.customShapes */ customShapes?: Array<{ allowEditImage?: boolean, allowEditText?: boolean, allowResize?: boolean, backgroundImageHeight?: number, backgroundImageLeft?: number, backgroundImageToolboxUrl?: string, backgroundImageTop?: number, backgroundImageUrl?: string, backgroundImageWidth?: number, baseType?: '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' | string, category?: string, connectionPoints?: Array<{ x?: number, y?: number }>, defaultHeight?: number, defaultImageUrl?: string, defaultText?: string, defaultWidth?: number, imageHeight?: number, imageLeft?: number, imageTop?: number, imageWidth?: number, maxHeight?: number, maxWidth?: number, minHeight?: number, minWidth?: number, template?: DevExpress.core.template | ((container: DevExpress.core.dxElement, data: { item?: dxDiagramShape }) => any), templateHeight?: number, templateLeft?: number, templateTop?: number, templateWidth?: number, textHeight?: number, textLeft?: number, textTop?: number, textWidth?: number, title?: string, type?: string }>; + /** @name dxDiagram.Options.defaultItemProperties */ + defaultItemProperties?: { connectorLineEnd?: 'none' | 'arrow' | 'outlinedTriangle' | 'filledTriangle', connectorLineStart?: 'none' | 'arrow' | 'outlinedTriangle' | 'filledTriangle', connectorLineType?: 'straight' | 'orthogonal', style?: any, textStyle?: any }; /** @name dxDiagram.Options.edges */ edges?: { dataSource?: Array | DevExpress.data.DataSource | DevExpress.data.DataSourceOptions, fromExpr?: string | ((data: any) => any), fromLineEndExpr?: string | ((data: any) => any), fromPointIndexExpr?: string | ((data: any) => any), keyExpr?: string | ((data: any) => any), lineTypeExpr?: string | ((data: any) => any), lockedExpr?: string | ((data: any) => any), pointsExpr?: string | ((data: any) => any), styleExpr?: string | ((data: any) => any), textExpr?: string | ((data: any) => any), textStyleExpr?: string | ((data: any) => any), toExpr?: string | ((data: any) => any), toLineEndExpr?: string | ((data: any) => any), toPointIndexExpr?: string | ((data: any) => any), zIndexExpr?: string | ((data: any) => any) }; /** @name dxDiagram.Options.export */