diff --git a/js/docEnums.js b/js/docEnums.js index 58b7a57869db..8e787e61eff6 100644 --- a/js/docEnums.js +++ b/js/docEnums.js @@ -598,7 +598,7 @@ /** * @typedef {string} Enums.ToolbarItemWidget - * @enum {'dxAutocomplete'|'dxButton'|'dxCheckBox'|'dxDateBox'|'dxMenu'|'dxSelectBox'|'dxTabs'|'dxTextBox'|'dxButtonGroup'} + * @enum {'dxAutocomplete'|'dxButton'|'dxCheckBox'|'dxDateBox'|'dxMenu'|'dxSelectBox'|'dxTabs'|'dxTextBox'|'dxButtonGroup'|'dxDropDownButton'} */ /** @@ -837,8 +837,18 @@ */ /** - * @typedef {string} Enums.TextEditorButtonName - * @enum {'clear'|'spins'|'dropDown'} + * @typedef {string} Enums.TextBoxButtonName + * @enum {'clear'} + */ + +/** + * @typedef {string} Enums.NumberBoxButtonName + * @enum {'clear'|'spins'} + */ + +/** + * @typedef {string} Enums.DropDownEditorButtonName + * @enum {'clear'|'dropDown'} */ /** diff --git a/js/ui/diagram/ui.diagram.js b/js/ui/diagram/ui.diagram.js index d6fa35562b65..cb18bf29d20b 100644 --- a/js/ui/diagram/ui.diagram.js +++ b/js/ui/diagram/ui.diagram.js @@ -77,18 +77,25 @@ class Diagram extends Widget { } _renderLeftPanel($parent) { const isServerSide = !hasWindow(); - const dataSources = this._getDataSources(); + const $leftPanel = $("
") .appendTo($parent); - var customShapes = this.option("customShapes"); - this._createComponent($leftPanel, DiagramLeftPanel, { - dataSources, - showCustomShapes: Array.isArray(customShapes) && customShapes.length > 0, + this._leftPanel = this._createComponent($leftPanel, DiagramLeftPanel, { + dataSources: this._getDataSources(), + customShapes: this._getCustomShapes(), onShapeCategoryRendered: (e) => !isServerSide && this._diagramInstance.createToolbox(e.$element[0], 40, 8, {}, e.category), onDataToolboxRendered: (e) => !isServerSide && this._diagramInstance.createDataSourceToolbox(e.key, e.$element[0]) }); } + _invalidateLeftPanel() { + if(this._leftPanel) { + this._leftPanel.option({ + dataSources: this._getDataSources(), + customShapes: this._getCustomShapes(), + }); + } + } _renderRightPanel($parent) { const drawer = this._createComponent($parent, Drawer, { @@ -129,8 +136,10 @@ class Diagram extends Widget { this._diagramInstance.onNodeInserted = this._raiseNodeInsertedAction.bind(this); this._diagramInstance.onNodeUpdated = this._raiseNodeUpdatedAction.bind(this); this._diagramInstance.onNodeRemoved = this._raiseNodeRemovedAction.bind(this); + this._diagramInstance.onToolboxDragStart = this._raiseToolboxDragStart.bind(this); + this._diagramInstance.onToolboxDragEnd = this._raiseToolboxDragEnd.bind(this); - this._updateCustomShapes(this.option("customShapes")); + this._updateCustomShapes(this._getCustomShapes()); this._refreshDataSources(); } _refreshDataSources() { @@ -177,6 +186,9 @@ class Diagram extends Widget { }); } + _getDataSources() { + return this.option("dataSources") || {}; + } _createDiagramDataSource(parameters) { const key = parameters.key || "0"; const title = parameters.title || "Data Source"; @@ -196,7 +208,9 @@ class Diagram extends Widget { setType: this._createSetter(nodes.typeExpr || DIAGRAM_TYPE_FIELD), getParentKey: this._createGetter(nodes.parentKeyExpr || DIAGRAM_PARENT_KEY_FIELD), - getItems: this._createGetter(nodes.itemsExpr || DIAGRAM_ITEMS_FIELD) + setParentKey: this._createSetter(nodes.parentKeyExpr || DIAGRAM_PARENT_KEY_FIELD), + getItems: this._createGetter(nodes.itemsExpr || DIAGRAM_ITEMS_FIELD), + setItems: this._createSetter(nodes.itemsExpr || DIAGRAM_ITEMS_FIELD) }, edgeDataImporter: { getKey: this._createGetter(edges.keyExpr || DIAGRAM_KEY_FIELD), @@ -208,8 +222,12 @@ class Diagram extends Widget { }, layoutType: this._getDataSourceLayoutType(parameters.layout) }; - this._addDiagramDataSource(key, data); - this._importDiagramDataSource(key); + const { DiagramCommand } = getDiagram(); + this._diagramInstance.commandManager.getCommand(DiagramCommand.ImportDataSource).execute(data); + + var dataSources = this._getDataSources(); + dataSources[key] = data; + this.option("dataSources", dataSources); } _getDataSourceLayoutType(layout) { const { DataLayoutType } = getDiagram(); @@ -220,39 +238,16 @@ class Diagram extends Widget { return DataLayoutType.Sugiyama; } } - _getDataSources() { - return this.option("dataSources") || {}; - } - _addDiagramDataSource(key, data) { - var dataSources = this._getDataSources(); - dataSources[key] = data; - this.option("dataSources", dataSources); - } - _importDiagramDataSource(key) { - const { DiagramCommand } = getDiagram(); - - var dataSources = this._getDataSources(); - if(dataSources[key]) { - this._diagramInstance.commandManager.getCommand(DiagramCommand.ImportDataSource).execute(dataSources[key]); - } - } _deleteDiagramDataSource(key) { - this._closeDiagramDataSource(key); - this._removeDiagramDataSource(key); - } - _closeDiagramDataSource(key) { - const { DiagramCommand } = getDiagram(); - var dataSources = this._getDataSources(); if(dataSources[key]) { + const { DiagramCommand } = getDiagram(); this._diagramInstance.commandManager.getCommand(DiagramCommand.CloseDataSource).execute(key); + + delete dataSources[key]; + this.option("dataSources", dataSources); } } - _removeDiagramDataSource(key) { - var dataSources = this._getDataSources(); - delete dataSources[key]; - this.option("dataSources", dataSources); - } _nodesDataSourceChanged(nodes) { this._nodes = nodes; @@ -295,7 +290,9 @@ class Diagram extends Widget { setType: this._createOptionSetter("nodes.typeExpr"), getParentKey: this._createOptionGetter("nodes.parentKeyExpr"), - getItems: this._createOptionGetter("nodes.itemsExpr") + setParentKey: this._createOptionSetter("nodes.parentKeyExpr"), + getItems: this._createOptionGetter("nodes.itemsExpr"), + setItems: this._createOptionSetter("nodes.itemsExpr") }, edgeDataImporter: { getKey: this._createOptionGetter("edges.keyExpr"), @@ -331,6 +328,9 @@ class Diagram extends Widget { } } + _getCustomShapes() { + return this.option("customShapes") || []; + } _updateCustomShapes(customShapes, prevCustomShapes) { if(Array.isArray(prevCustomShapes)) { this._diagramInstance.removeCustomShapes(customShapes.map( @@ -366,12 +366,12 @@ class Diagram extends Widget { } /** * @name dxDiagramMethods.setData - * @publicName setData(data, keepExistingItems) + * @publicName setData(data, updateExistingItemsOnly) * @param1 data:string - * @param2 keepExistingItems:boolean + * @param2 updateExistingItemsOnly:boolean */ - setData(data, keepExistingItems) { - this._setDiagramData(data, keepExistingItems); + setData(data, updateExistingItemsOnly) { + this._setDiagramData(data, updateExistingItemsOnly); this._raiseDataChangeAction(); } @@ -581,39 +581,35 @@ class Diagram extends Widget { /** * @name dxDiagramOptions.customShapes - * @type Array - * @default null - */ - customShapes: [], - /** - * @name DiagramCustomShapeItem - * @type object - */ - /** - * @name DiagramCustomShapeItem.id - * @type Number - */ - /** - * @name DiagramCustomShapeItem.title - * @type String - */ - /** - * @name DiagramCustomShapeItem.svgUrl - * @type String - */ - /** - * @name DiagramCustomShapeItem.defaultWidth - * @type Number - */ - /** - * @name DiagramCustomShapeItem.defaultHeight - * @type Number - */ - /** - * @name DiagramCustomShapeItem.allowHasText - * @type Boolean + * @type Array + * @default [] */ - + customShapes: [ + /** + * @name dxDiagramOptions.customShapes.id + * @type Number + */ + /** + * @name dxDiagramOptions.customShapes.title + * @type String + */ + /** + * @name dxDiagramOptions.customShapes.svgUrl + * @type String + */ + /** + * @name dxDiagramOptions.customShapes.defaultWidth + * @type Number + */ + /** + * @name dxDiagramOptions.customShapes.defaultHeight + * @type Number + */ + /** + * @name dxDiagramOptions.customShapes.allowHasText + * @type Boolean + */ + ], /** * @name dxDiagramOptions.export * @type object @@ -678,6 +674,16 @@ class Diagram extends Widget { this._nodesOption.remove(key, callback); } } + _raiseToolboxDragStart() { + if(this._leftPanel) { + this._leftPanel.$element().addClass("dx-skip-gesture-event"); + } + } + _raiseToolboxDragEnd() { + if(this._leftPanel) { + this._leftPanel.$element().removeClass("dx-skip-gesture-event"); + } + } _optionChanged(args) { switch(args.name) { @@ -692,13 +698,13 @@ class Diagram extends Widget { break; case "customShapes": this._updateCustomShapes(args.value, args.previousValue); - this._invalidate(); + this._invalidateLeftPanel(); break; case "onDataChanged": this._createDataChangeAction(); break; case "dataSources": - this._invalidate(); + this._invalidateLeftPanel(); break; case "export": this._toolbarInstance.option("export", this.option("export")); diff --git a/js/ui/diagram/ui.diagram.leftpanel.js b/js/ui/diagram/ui.diagram.leftpanel.js index 4244e76d4c24..9b214426a804 100644 --- a/js/ui/diagram/ui.diagram.leftpanel.js +++ b/js/ui/diagram/ui.diagram.leftpanel.js @@ -10,7 +10,9 @@ const DIAGRAM_LEFT_PANEL_CLASS = "dx-diagram-left-panel"; class DiagramLeftPanel extends Widget { _init() { super._init(); - this._showCustomShapes = this.option("showCustomShapes"); + + this._dataSources = this.option("dataSources") || {}; + this._customShapes = this.option("customShapes") || []; this._onShapeCategoryRenderedAction = this._createActionByOption("onShapeCategoryRendered"); this._onDataToolboxRenderedAction = this._createActionByOption("onDataToolboxRendered"); } @@ -27,12 +29,9 @@ class DiagramLeftPanel extends Widget { this._renderAccordion($accordion); } - _getDataSources() { - return this.option("dataSources") || {}; - } _getAccordionDataSource() { var result = []; - var categories = ShapeCategories.load(this._showCustomShapes); + var categories = ShapeCategories.load(this._customShapes.length > 0); for(var i = 0; i < categories.length; i++) { result.push({ category: categories[i].category, @@ -42,12 +41,11 @@ class DiagramLeftPanel extends Widget { } }); } - var dataSources = this._getDataSources(); - for(var key in dataSources) { - if(dataSources.hasOwnProperty(key)) { + for(var key in this._dataSources) { + if(this._dataSources.hasOwnProperty(key)) { result.push({ key, - title: dataSources[key].title, + title: this._dataSources[key].title, onTemplate: (widget, $element, data) => { this._onDataToolboxRenderedAction({ key: data.key, $element }); } @@ -67,11 +65,26 @@ class DiagramLeftPanel extends Widget { itemTemplate: (data, index, $element) => data.onTemplate(this, $element, data) }); // TODO option for expanded item - if(this._showCustomShapes || this._hasDataSources) { + if(this._customShapes.length > 0 || this._hasDataSources) { this._accordionInstance.collapseItem(0); this._accordionInstance.expandItem(data.length - 1); } } + + _optionChanged(args) { + switch(args.name) { + case "customShapes": + this._customShapes = args.value || []; + this._invalidate(); + break; + case "dataSources": + this._dataSources = args.value || {}; + this._invalidate(); + break; + default: + super._optionChanged(args); + } + } } module.exports = DiagramLeftPanel; diff --git a/js/ui/drop_down_editor/ui.drop_down_editor.js b/js/ui/drop_down_editor/ui.drop_down_editor.js index 0c3032d07e88..579966093401 100644 --- a/js/ui/drop_down_editor/ui.drop_down_editor.js +++ b/js/ui/drop_down_editor/ui.drop_down_editor.js @@ -196,6 +196,14 @@ var DropDownEditor = TextBox.inherit({ */ showDropDownButton: true, + /** + * @name dxDropDownEditorOptions.buttons + * @type Array + * @default undefined + * @inheritdoc + */ + buttons: void 0, + dropDownOptions: {}, popupPosition: this._getDefaultPopupPosition(), onPopupInitialized: null, diff --git a/js/ui/file_manager/file_provider/ajax.js b/js/ui/file_manager/file_provider/ajax.js index 04eb3b7871ae..7c86e8543251 100644 --- a/js/ui/file_manager/file_provider/ajax.js +++ b/js/ui/file_manager/file_provider/ajax.js @@ -1,4 +1,5 @@ import ajax from "../../../core/utils/ajax"; +import { ensureDefined } from "../../../core/utils/common"; import { Deferred } from "../../../core/utils/deferred"; import { extend } from "../../../core/utils/extend"; import { FileProvider } from "./file_provider"; @@ -14,29 +15,15 @@ import ArrayFileProvider from "./array"; class AjaxFileProvider extends FileProvider { constructor(options) { + options = ensureDefined(options, { }); super(options); + /** * @name AjaxFileProviderOptions.url * @type string */ /** - * @name AjaxFileProviderOptions.nameExpr - * @type string|function(fileItem) - */ - /** - * @name AjaxFileProviderOptions.isFolderExpr - * @type string|function(fileItem) - */ - /** - * @name AjaxFileProviderOptions.sizeExpr - * @type string|function(fileItem) - */ - /** - * @name AjaxFileProviderOptions.dateModifiedExpr - * @type string|function(fileItem) - */ - /** - * @name AjaxFileProviderOptions.thumbnailExpr + * @name AjaxFileProviderOptions.itemsExpr * @type string|function(fileItem) */ this._options = options; @@ -101,7 +88,7 @@ class AjaxFileProvider extends FileProvider { _getData() { return ajax.sendRequest({ - url: this.options.url, + url: this._options.url, dataType: "json", cache: false }); diff --git a/js/ui/file_manager/file_provider/array.js b/js/ui/file_manager/file_provider/array.js index 7a68917bf004..773ff02f7717 100644 --- a/js/ui/file_manager/file_provider/array.js +++ b/js/ui/file_manager/file_provider/array.js @@ -1,5 +1,7 @@ import { ensureDefined } from "../../../core/utils/common"; +import { compileGetter, compileSetter } from "../../../core/utils/data"; import { each } from "../../../core/utils/iterator"; +import typeUtils from "../../../core/utils/type"; import { errors } from "../../../data/errors"; import { FileProvider } from "./file_provider"; @@ -14,6 +16,7 @@ import { FileProvider } from "./file_provider"; class ArrayFileProvider extends FileProvider { constructor(options) { + options = ensureDefined(options, { }); super(options); const initialArray = options.data; @@ -26,25 +29,19 @@ class ArrayFileProvider extends FileProvider { * @type Array */ /** - * @name ArrayFileProviderOptions.nameExpr - * @type string|function(fileItem) - */ - /** - * @name ArrayFileProviderOptions.isFolderExpr - * @type string|function(fileItem) - */ - /** - * @name ArrayFileProviderOptions.sizeExpr - * @type string|function(fileItem) - */ - /** - * @name ArrayFileProviderOptions.dateModifiedExpr - * @type string|function(fileItem) - */ - /** - * @name ArrayFileProviderOptions.thumbnailExpr + * @name ArrayFileProviderOptions.itemsExpr * @type string|function(fileItem) */ + const itemsExpr = options.itemsExpr || "items"; + this._subFileItemsGetter = compileGetter(itemsExpr); + this._subFileItemsSetter = typeUtils.isFunction(itemsExpr) ? itemsExpr : compileSetter(itemsExpr); + + const nameExpr = this._getNameExpr(options); + this._nameSetter = typeUtils.isFunction(nameExpr) ? nameExpr : compileSetter(nameExpr); + + const isDirExpr = this._getIsDirExpr(options); + this._getIsDirSetter = typeUtils.isFunction(isDirExpr) ? isDirExpr : compileSetter(isDirExpr); + this._data = initialArray || [ ]; } @@ -57,10 +54,10 @@ class ArrayFileProvider extends FileProvider { } createFolder(parentFolder, name) { - const newItem = { - name, - isFolder: true - }; + let newItem = { }; + this._nameSetter(newItem, name); + this._getIsDirSetter(newItem, true); + const array = this._getChildrenArray(parentFolder.dataItem); array.push(newItem); } @@ -70,7 +67,7 @@ class ArrayFileProvider extends FileProvider { } moveItems(items, destinationFolder) { - const array = this._getChildrenArray(destinationFolder.dataItem); + let array = this._getChildrenArray(destinationFolder.dataItem); each(items, (_, item) => { this._deleteItem(item); array.push(item.dataItem); @@ -85,48 +82,53 @@ class ArrayFileProvider extends FileProvider { }); } - _createCopy({ name, children, isFolder }) { - const result = { - name, - isFolder - }; - if(children) { - result.children = []; - each(children, (_, childItem) => { + _createCopy(dataObj) { + let copyObj = { }; + this._nameSetter(copyObj, this._nameGetter(dataObj)); + this._getIsDirSetter(copyObj, this._isDirGetter(dataObj)); + + const items = this._subFileItemsGetter(dataObj); + if(Array.isArray(items)) { + let itemsCopy = []; + each(items, (_, childItem) => { const childCopy = this._createCopy(childItem); - result.children.push(childCopy); + itemsCopy.push(childCopy); }); + this._subFileItemsSetter(copyObj, itemsCopy); } - return result; + return copyObj; } _deleteItem({ parentPath, dataItem }) { let array = this._data; if(parentPath !== "") { - const { children } = this._findItem(parentPath); - array = children; + const folder = this._findItem(parentPath); + array = this._subFileItemsGetter(folder); } const index = array.indexOf(dataItem); array.splice(index, 1); } _getChildrenArray(dataItem) { - let array = null; if(!dataItem) { - array = this._data; - } else { - array = dataItem.children = ensureDefined(dataItem.children, []); + return this._data; } - return array; + + let subItems = this._subFileItemsGetter(dataItem); + if(!Array.isArray(subItems)) { + subItems = []; + this._subFileItemsSetter(dataItem, subItems); + } + return subItems; } _getItems(path, itemType) { - if(path === "") { - return this._convertDataObjectsToFileItems(this._data, path, itemType); + if(path === "" || path === undefined) { + return this._convertDataObjectsToFileItems(this._data, "", itemType); } const folderEntry = this._findItem(path); - const entries = folderEntry && folderEntry.children || []; + const entries = folderEntry && this._subFileItemsGetter(folderEntry) || []; return this._convertDataObjectsToFileItems(entries, path, itemType); } @@ -140,10 +142,11 @@ class ArrayFileProvider extends FileProvider { const parts = path.split("/"); for(let i = 0; i < parts.length; i++) { const part = parts[i]; - result = data.filter(entry => entry.isFolder && entry.name === part)[0]; + result = data.filter(entry => this._isDirGetter(entry) && this._nameGetter(entry) === part)[0]; if(result) { - if(result.children) { - data = result.children; + const children = this._subFileItemsGetter(result); + if(children) { + data = children; } else if(i !== parts.length - 1) { return null; } @@ -155,6 +158,21 @@ class ArrayFileProvider extends FileProvider { return result; } + _hasSubDirs(dataObj) { + const subItems = ensureDefined(this._subFileItemsGetter(dataObj), []); + + if(!Array.isArray(subItems)) { + return true; + } + + for(let i = 0; i < subItems.length; i++) { + if(this._isDirGetter(subItems[i]) === true) { + return true; + } + } + return false; + } + } module.exports = ArrayFileProvider; diff --git a/js/ui/file_manager/file_provider/file_provider.js b/js/ui/file_manager/file_provider/file_provider.js index 92dbc049488f..796c0f6fbfe5 100644 --- a/js/ui/file_manager/file_provider/file_provider.js +++ b/js/ui/file_manager/file_provider/file_provider.js @@ -1,5 +1,6 @@ import { compileGetter } from "../../../core/utils/data"; import { pathCombine, getFileExtension, getParentPath, getName } from "../ui.file_manager.utils"; +import { ensureDefined } from "../../../core/utils/common"; import { deserializeDate } from "../../../core/utils/date_serialization"; import { each } from "../../../core/utils/iterator"; @@ -15,10 +16,32 @@ const DEFAULT_FILE_UPLOAD_CHUNK_SIZE = 200000; class FileProvider { constructor(options) { - this._nameGetter = compileGetter(options.nameExpr || "name"); - this._isFolderGetter = compileGetter(options.isFolderExpr || "isFolder"); + options = ensureDefined(options, {}); + + /** + * @name FileProviderOptions.nameExpr + * @type string|function(fileItem) + */ + this._nameGetter = compileGetter(this._getNameExpr(options)); + /** + * @name FileProviderOptions.isDirectoryExpr + * @type string|function(fileItem) + */ + this._isDirGetter = compileGetter(this._getIsDirExpr(options)); + /** + * @name FileProviderOptions.sizeExpr + * @type string|function(fileItem) + */ this._sizeGetter = compileGetter(options.sizeExpr || "size"); + /** + * @name FileProviderOptions.dateModifiedExpr + * @type string|function(fileItem) + */ this._dateModifiedGetter = compileGetter(options.dateModifiedExpr || "dateModified"); + /** + * @name FileProviderOptions.thumbnailExpr + * @type string|function(fileItem) + */ this._thumbnailGetter = compileGetter(options.thumbnailExpr || "thumbnail"); } @@ -66,7 +89,7 @@ class FileProvider { } _getItemsByType(path, folders) { - return this.getItems(path).filter(item => item.isFolder === folders); + return this.getItems(path).filter(item => item.isDirectory === folders); } _convertDataObjectsToFileItems(entries, path, itemType) { @@ -74,14 +97,14 @@ class FileProvider { const result = []; each(entries, (_, entry) => { const fileItem = this._createFileItem(entry, path); - if(!itemType || fileItem.isFolder === useFolders) { + if(!itemType || fileItem.isDirectory === useFolders) { result.push(fileItem); } }); return result; } _createFileItem(dataObj, path) { - let fileItem = new FileManagerItem(path, this._nameGetter(dataObj), !!this._isFolderGetter(dataObj)); + let fileItem = new FileManagerItem(path, this._nameGetter(dataObj), !!this._isDirGetter(dataObj)); fileItem.size = this._sizeGetter(dataObj); if(fileItem.size === undefined) { @@ -93,19 +116,35 @@ class FileProvider { fileItem.dateModified = new Date(); } + if(fileItem.isDirectory) { + fileItem.hasSubDirs = this._hasSubDirs(dataObj); + } + fileItem.thumbnail = this._thumbnailGetter(dataObj) || ""; - fileItem.dataItem = dataObj; // TODO remove if do not need + fileItem.dataItem = dataObj; return fileItem; } + _hasSubDirs(dataObj) { + return true; + } + + _getNameExpr(options) { + return options.nameExpr || "name"; + } + + _getIsDirExpr(options) { + return options.isDirectoryExpr || "isDirectory"; + } + } class FileManagerItem { - constructor(parentPath, name, isFolder) { + constructor(parentPath, name, isDirectory) { this.parentPath = parentPath; this.name = name; this.relativeName = pathCombine(this.parentPath, name); - this.isFolder = isFolder || false; + this.isDirectory = isDirectory || false; this.size = 0; this.dateModified = new Date(); @@ -115,7 +154,7 @@ class FileManagerItem { } getExtension() { - return this.isFolder ? "" : getFileExtension(this.name); + return this.isDirectory ? "" : getFileExtension(this.name); } getParent() { @@ -135,11 +174,13 @@ class FileManagerItem { } createClone() { - const result = new FileManagerItem(this.parentPath, this.name, this.isFolder); + const result = new FileManagerItem(this.parentPath, this.name, this.isDirectory); result.size = this.size; result.dateModified = this.dateModified; result.thumbnail = this.thumbnail; result.tooltipText = this.tooltipText; + result.hasSubDirs = this.hasSubDirs; + result.dataItem = this.dataItem; return result; } } diff --git a/js/ui/file_manager/file_provider/onedrive.js b/js/ui/file_manager/file_provider/onedrive.js index 86ef0cb4a8c9..46ad4571add0 100644 --- a/js/ui/file_manager/file_provider/onedrive.js +++ b/js/ui/file_manager/file_provider/onedrive.js @@ -19,9 +19,30 @@ const APP_ROOT_URL = DRIVE_API_URL + "/special/approot"; class OneDriveFileProvider extends FileProvider { constructor(options) { + /** + * @name OneDriveFileProvider.nameExpr + * @hidden + */ + /** + * @name OneDriveFileProvider.isDirectoryExpr + * @hidden + */ + /** + * @name OneDriveFileProvider.sizeExpr + * @hidden + */ + /** + * @name OneDriveFileProvider.dateModifiedExpr + * @hidden + */ + /** + * @name OneDriveFileProvider.thumbnailExpr + * @hidden + */ + options = options || {}; options.dateModifiedExpr = "lastModifiedDateTime"; - options.isFolderExpr = "folder"; + options.isDirectoryExpr = "folder"; super(options); this._getAccessTokenUrl = options.getAccessTokenUrl || ""; @@ -175,6 +196,10 @@ class OneDriveFileProvider extends FileProvider { return path === "" ? "" : `:/${path}:`; } + _hasSubDirs(dataObj) { + return dataObj.hasOwnProperty("folder") && dataObj.folder.childCount > 0; + } + } module.exports = OneDriveFileProvider; diff --git a/js/ui/file_manager/file_provider/webapi.js b/js/ui/file_manager/file_provider/webapi.js index 627b79469448..0798da5963c4 100644 --- a/js/ui/file_manager/file_provider/webapi.js +++ b/js/ui/file_manager/file_provider/webapi.js @@ -1,11 +1,12 @@ import ajax from "../../../core/utils/ajax"; -import { noop } from "../../../core/utils/common"; +import { ensureDefined, noop } from "../../../core/utils/common"; import Guid from "../../../core/guid"; import { getWindow } from "../../../core/utils/window"; import { each } from "../../../core/utils/iterator"; import { Deferred } from "../../../core/utils/deferred"; import { FileProvider } from "./file_provider"; +import { compileGetter } from "../../../core/utils/data"; const window = getWindow(); const FILE_CHUNK_BLOB_NAME = "chunk"; @@ -20,6 +21,7 @@ const FILE_CHUNK_BLOB_NAME = "chunk"; class WebApiFileProvider extends FileProvider { constructor(options) { + options = ensureDefined(options, { }); super(options); /** * @name WebApiFileProviderOptions.endpointUrl @@ -27,25 +29,10 @@ class WebApiFileProvider extends FileProvider { */ this._endpointUrl = options.endpointUrl; /** - * @name WebApiFileProviderOptions.nameExpr - * @type string|function(fileItem) - */ - /** - * @name WebApiFileProviderOptions.isFolderExpr - * @type string|function(fileItem) - */ - /** - * @name WebApiFileProviderOptions.sizeExpr - * @type string|function(fileItem) - */ - /** - * @name WebApiFileProviderOptions.dateModifiedExpr - * @type string|function(fileItem) - */ - /** - * @name WebApiFileProviderOptions.thumbnailExpr + * @name WebApiFileProviderOptions.hasSubDirectoriesExpr * @type string|function(fileItem) */ + this._hasSubDirsGetter = compileGetter(options.hasSubDirectoriesExpr || "hasSubDirectories"); } getItems(path, itemType) { @@ -80,7 +67,7 @@ class WebApiFileProvider extends FileProvider { copyItems(items, destinationFolder) { return items.map(item => this._executeRequest("Copy", { sourceId: item.relativeName, - destinationId: destinationFolder.relativeName + destinationId: destinationFolder.relativeName + "/" + item.name })); } @@ -147,9 +134,12 @@ class WebApiFileProvider extends FileProvider { arguments: JSON.stringify(args) }); + const method = command === "GetDirContents" ? "GET" : "POST"; + const deferred = new Deferred(); ajax.sendRequest({ url: this._endpointUrl + "?" + queryString, + method, dataType: "json", cache: false }).then(result => { @@ -197,6 +187,11 @@ class WebApiFileProvider extends FileProvider { return encodeURIComponent(key) + "=" + encodeURIComponent(value); } + _hasSubDirs(dataObj) { + const hasSubDirs = this._hasSubDirsGetter(dataObj); + return typeof hasSubDirs === "boolean" ? hasSubDirs : true; + } + } module.exports = WebApiFileProvider; diff --git a/js/ui/file_manager/ui.file_manager.adaptivity.js b/js/ui/file_manager/ui.file_manager.adaptivity.js new file mode 100644 index 000000000000..e6b86da991f4 --- /dev/null +++ b/js/ui/file_manager/ui.file_manager.adaptivity.js @@ -0,0 +1,103 @@ +import $ from "../../core/renderer"; +import { extend } from "../../core/utils/extend"; +import { isFunction } from "../../core/utils/type"; +import { getWindow } from "../../core/utils/window"; + +import Widget from "../widget/ui.widget"; +import Drawer from "../drawer/ui.drawer"; + +const window = getWindow(); +const ADAPTIVE_STATE_SCREEN_WIDTH = 573; + +class FileManagerAdaptivityControl extends Widget { + + _initMarkup() { + super._initMarkup(); + + this._initActions(); + + this._isInAdaptiveState = false; + + const $drawer = $("
").appendTo(this.$element()); + + const contentRenderer = this.option("contentTemplate"); + if(isFunction(contentRenderer)) { + contentRenderer($drawer); + } + + this._drawer = this._createComponent($drawer, Drawer, { + opened: true, + template: this.option("drawerTemplate") + }); + } + + _render() { + super._render(); + this._checkAdaptiveState(); + } + + _dimensionChanged(dimension) { + if(!dimension || dimension !== "height") { + this._checkAdaptiveState(); + } + } + + _checkAdaptiveState() { + const oldState = this._isInAdaptiveState; + this._isInAdaptiveState = this._isSmallScreen(); + if(oldState !== this._isInAdaptiveState) { + this.toggleDrawer(!this._isInAdaptiveState, true); + this._raiseAdaptiveStateChanged(this._isInAdaptiveState); + } + } + + _isSmallScreen() { + return $(window).width() <= ADAPTIVE_STATE_SCREEN_WIDTH; + } + + _initActions() { + this._actions = { + onAdaptiveStateChanged: this._createActionByOption("onAdaptiveStateChanged") + }; + } + + _raiseAdaptiveStateChanged(enabled) { + this._actions.onAdaptiveStateChanged({ enabled }); + } + + _getDefaultOptions() { + return extend(super._getDefaultOptions(), { + drawerTemplate: null, + contentTemplate: null, + onAdaptiveStateChanged: null + }); + } + + _optionChanged(args) { + const name = args.name; + + switch(name) { + case "drawerTemplate": + case "contentTemplate": + this.repaint(); + break; + case "onAdaptiveStateChanged": + this._actions[name] = this._createActionByOption(name); + break; + default: + super._optionChanged(args); + } + } + + isInAdaptiveState() { + return this._isInAdaptiveState; + } + + toggleDrawer(showing, skipAnimation) { + this._drawer.option("animationEnabled", !skipAnimation); + this._drawer.toggle(showing); + } + +} + +module.exports = FileManagerAdaptivityControl; diff --git a/js/ui/file_manager/ui.file_manager.command_manager.js b/js/ui/file_manager/ui.file_manager.command_manager.js index e03bddde2d32..eab19524e0fd 100644 --- a/js/ui/file_manager/ui.file_manager.command_manager.js +++ b/js/ui/file_manager/ui.file_manager.command_manager.js @@ -78,6 +78,12 @@ export class FileManagerCommandManager { text: "Clear selection", icon: "remove", enabled: true + }, + { + name: "showDirsPanel", + icon: "menu", + enabled: false, + noFileItemRequired: true } ]; @@ -97,6 +103,13 @@ export class FileManagerCommandManager { } } + setCommandEnabled(commandName, enabled) { + const command = this.getCommandByName(commandName); + if(command) { + command.enabled = enabled; + } + } + getCommandByName(name) { return this._commandMap[name]; } @@ -106,8 +119,16 @@ export class FileManagerCommandManager { if(!command || !command.enabled) { return false; } + + if(command.noFileItemRequired) { + return true; + } + const itemsLength = items && items.length || 0; - return command.noFileItemRequired || itemsLength > 0 && (!command.isSingleFileItemCommand || itemsLength === 1); + if(itemsLength === 0 || items.some(item => item.isRoot() || item.isParentFolder)) { + return false; + } + return !command.isSingleFileItemCommand || itemsLength === 1; } } diff --git a/js/ui/file_manager/ui.file_manager.context_menu.js b/js/ui/file_manager/ui.file_manager.context_menu.js index 3c8d5d9ae66b..2ae9f359955a 100644 --- a/js/ui/file_manager/ui.file_manager.context_menu.js +++ b/js/ui/file_manager/ui.file_manager.context_menu.js @@ -5,6 +5,8 @@ import { isObject, isString } from "../../core/utils/type"; import Widget from "../widget/ui.widget"; import ContextMenu from "../context_menu/ui.context_menu"; +const FILEMANAGER_CONTEXT_MEMU_CLASS = "dx-filemanager-context-menu"; + const DEFAULT_CONTEXT_MENU_ITEMS = [ "create", "upload", @@ -27,6 +29,7 @@ class FileManagerContextMenu extends Widget { const $menu = $("
").appendTo(this.$element()); this._contextMenu = this._createComponent($menu, ContextMenu, { + cssClass: FILEMANAGER_CONTEXT_MEMU_CLASS, showEvent: "", onItemClick: ({ itemData: { commandName } }) => this._onContextMenuItemClick(commandName), onHidden: () => this._onContextMenuHidden() diff --git a/js/ui/file_manager/ui.file_manager.dialog.folder_chooser.js b/js/ui/file_manager/ui.file_manager.dialog.folder_chooser.js index 259f7568d4b4..57911447131b 100644 --- a/js/ui/file_manager/ui.file_manager.dialog.folder_chooser.js +++ b/js/ui/file_manager/ui.file_manager.dialog.folder_chooser.js @@ -5,6 +5,7 @@ import FileManagerDialogBase from "./ui.file_manager.dialog.js"; import FileManagerFilesTreeView from "./ui.file_manager.files_tree_view"; const FILE_MANAGER_DIALOG_FOLDER_CHOOSER = "dx-filemanager-dialog-folder-chooser"; +const FILE_MANAGER_DIALOG_FOLDER_CHOOSER_POPUP = "dx-filemanager-dialog-folder-chooser-popup"; class FileManagerFolderChooserDialog extends FileManagerDialogBase { @@ -17,10 +18,10 @@ class FileManagerFolderChooserDialog extends FileManagerDialogBase { _getDialogOptions() { return extend(super._getDialogOptions(), { - width: 400, - height: "80%", title: "Select Destination Folder", - buttonText: "Select" + buttonText: "Select", + contentCssClass: FILE_MANAGER_DIALOG_FOLDER_CHOOSER, + popupCssClass: FILE_MANAGER_DIALOG_FOLDER_CHOOSER_POPUP }); } @@ -38,10 +39,6 @@ class FileManagerFolderChooserDialog extends FileManagerDialogBase { return { folder: this._filesTreeView.getCurrentFolder() }; } - _getCssClass() { - return FILE_MANAGER_DIALOG_FOLDER_CHOOSER; - } - _getDefaultOptions() { return extend(super._getDefaultOptions(), { getItems: null diff --git a/js/ui/file_manager/ui.file_manager.dialog.js b/js/ui/file_manager/ui.file_manager.dialog.js index e370dcf07181..2aa62f08ac28 100644 --- a/js/ui/file_manager/ui.file_manager.dialog.js +++ b/js/ui/file_manager/ui.file_manager.dialog.js @@ -5,17 +5,26 @@ import Widget from "../widget/ui.widget"; import Popup from "../popup"; const FILE_MANAGER_DIALOG_CONTENT = "dx-filemanager-dialog"; +const FILE_MANAGER_DIALOG_POPUP = "dx-filemanager-dialog-popup"; class FileManagerDialogBase extends Widget { _initMarkup() { + super._initMarkup(); + this._createOnClosedAction(); const options = this._getDialogOptions(); - this._popup = this._createComponent(this.$element(), Popup, { - width: options.width, - height: options.height, + const $popup = $("
") + .addClass(FILE_MANAGER_DIALOG_POPUP) + .appendTo(this.$element()); + + if(options.popupCssClass) { + $popup.addClass(options.popupCssClass); + } + + this._popup = this._createComponent($popup, Popup, { showTitle: true, title: options.title, visible: false, @@ -43,10 +52,10 @@ class FileManagerDialogBase extends Widget { _getDialogOptions() { return { - width: 340, - height: 200, title: "Title", - buttonText: "ButtonText" + buttonText: "ButtonText", + contentCssClass: "", + popupCssClass: "" }; } @@ -55,7 +64,7 @@ class FileManagerDialogBase extends Widget { .appendTo(element) .addClass(FILE_MANAGER_DIALOG_CONTENT); - const cssClass = this._getCssClass(); + const cssClass = this._getDialogOptions().contentCssClass; if(cssClass) { this._$contentElement.addClass(cssClass); } @@ -65,10 +74,6 @@ class FileManagerDialogBase extends Widget { return null; } - _getCssClass() { - return ""; - } - _onButtonClick() { const result = this._getDialogResult(); if(result) { diff --git a/js/ui/file_manager/ui.file_manager.dialog.name_editor.js b/js/ui/file_manager/ui.file_manager.dialog.name_editor.js index 1a9f5f856b2d..8644c86ea75e 100644 --- a/js/ui/file_manager/ui.file_manager.dialog.name_editor.js +++ b/js/ui/file_manager/ui.file_manager.dialog.name_editor.js @@ -5,6 +5,7 @@ import TextBox from "../text_box"; import FileManagerDialogBase from "./ui.file_manager.dialog.js"; const FILE_MANAGER_DIALOG_NAME_EDITOR = "dx-filemanager-dialog-name-editor"; +const FILE_MANAGER_DIALOG_NAME_EDITOR_POPUP = "dx-filemanager-dialog-name-editor-popup"; class FileManagerNameEditorDialog extends FileManagerDialogBase { @@ -22,10 +23,10 @@ class FileManagerNameEditorDialog extends FileManagerDialogBase { _getDialogOptions() { return extend(super._getDialogOptions(), { - width: 340, - height: 180, title: this.option("title"), - buttonText: this.option("buttonText") + buttonText: this.option("buttonText"), + contentCssClass: FILE_MANAGER_DIALOG_NAME_EDITOR, + popupCssClass: FILE_MANAGER_DIALOG_NAME_EDITOR_POPUP }); } @@ -45,10 +46,6 @@ class FileManagerNameEditorDialog extends FileManagerDialogBase { return nameValue ? { name: nameValue } : null; } - _getCssClass() { - return FILE_MANAGER_DIALOG_NAME_EDITOR; - } - _getDefaultOptions() { return extend(super._getDefaultOptions(), { title: "", diff --git a/js/ui/file_manager/ui.file_manager.editing.js b/js/ui/file_manager/ui.file_manager.editing.js index 13cf7a716918..255e2f5ee57e 100644 --- a/js/ui/file_manager/ui.file_manager.editing.js +++ b/js/ui/file_manager/ui.file_manager.editing.js @@ -23,26 +23,24 @@ class FileManagerEditingControl extends Widget { this._renameItemDialog = this._createEnterNameDialog("Rename", "Save"); this._createFolderDialog = this._createEnterNameDialog("Folder", "Create"); - this._chooseFolderDialog = this._createComponent($("
"), FileManagerFolderChooserDialog, { + + const $chooseFolderDialog = $("
").appendTo(this.$element()); + this._chooseFolderDialog = this._createComponent($chooseFolderDialog, FileManagerFolderChooserDialog, { provider: this._provider, getItems: this._model.getFolders, onClosed: this._onDialogClosed.bind(this) }); + this._confirmationDialog = this._createConfirmationDialog(); this._fileUploader = this._createFileUploader(); - this.$element() - .append(this._renameItemDialog.$element()) - .append(this._createFolderDialog.$element()) - .append(this._chooseFolderDialog.$element()) - .append(this._fileUploader.$element()); - this._createEditActions(); } _createFileUploader() { - return this._createComponent($("
"), FileManagerFileUploader, { + const $fileUploader = $("
").appendTo(this.$element()); + return this._createComponent($fileUploader, FileManagerFileUploader, { getController: this._getFileUploaderController.bind(this), onFilesUploaded: result => this._raiseOnSuccess("Files uploaded", true), onErrorOccurred: ({ info }) => { @@ -78,7 +76,8 @@ class FileManagerEditingControl extends Widget { } _createEnterNameDialog(title, buttonText) { - return this._createComponent($("
"), FileManagerNameEditorDialog, { + const $dialog = $("
").appendTo(this.$element()); + return this._createComponent($dialog, FileManagerNameEditorDialog, { title: title, buttonText: buttonText, onClosed: this._onDialogClosed.bind(this) @@ -173,7 +172,7 @@ class FileManagerEditingControl extends Widget { if(!items) { items = action.useCurrentFolder ? [ this._model.getCurrentFolder() ] : this._model.getMultipleSelectedItems(); } - const onlyFiles = !action.affectsAllItems && items.every(item => !item.isFolder); + const onlyFiles = !action.affectsAllItems && items.every(item => !item.isDirectory); const dialogArgumentGetter = action.getDialogArgument || noop; this._showDialog(action.dialog, dialogArgumentGetter(items)) diff --git a/js/ui/file_manager/ui.file_manager.files_tree_view.js b/js/ui/file_manager/ui.file_manager.files_tree_view.js index 01724e5d91dc..693e5e83ff7e 100644 --- a/js/ui/file_manager/ui.file_manager.files_tree_view.js +++ b/js/ui/file_manager/ui.file_manager.files_tree_view.js @@ -42,6 +42,7 @@ class FileManagerFilesTreeView extends Widget { rootValue: "", createChildren: this._onFilesTreeViewCreateChildren.bind(this), itemTemplate: this._createFilesTreeViewItemTemplate.bind(this), + hasItemsExpr: "dataItem.hasSubDirs", onItemClick: this._onFilesTreeViewItemClick.bind(this), onItemExpanded: ({ itemData }) => this._model.changeItemExpandState(itemData, true), onItemCollapsed: ({ itemData }) => this._model.changeItemExpandState(itemData, false), @@ -304,10 +305,12 @@ class FilesTreeViewModel { item.expanded = expanded; } - getItemByDataItem(dataItem) { + getItemByDataItem(dataItem, updateIfExists) { let result = this._itemMap[dataItem.relativeName]; if(!result) { result = this._createItem(dataItem); + } else if(updateIfExists) { + result.dataItem = dataItem; } return result; } @@ -373,7 +376,7 @@ class FilesTreeViewModel { .then(dataItems => { item.children = []; dataItems.forEach(dataItem => { - const childItem = this.getItemByDataItem(dataItem); + const childItem = this.getItemByDataItem(dataItem, true); item.children.push(childItem); this._onItemLoaded(childItem); }); diff --git a/js/ui/file_manager/ui.file_manager.item_list.details.js b/js/ui/file_manager/ui.file_manager.item_list.details.js index 5a9c4d320836..e0293c7432f3 100644 --- a/js/ui/file_manager/ui.file_manager.item_list.details.js +++ b/js/ui/file_manager/ui.file_manager.item_list.details.js @@ -11,7 +11,7 @@ import { getDisplayFileSize } from "./ui.file_manager.utils.js"; const FILE_MANAGER_DETAILS_ITEM_LIST_CLASS = "dx-filemanager-details"; const FILE_MANAGER_DETAILS_ITEM_THUMBNAIL_CLASS = "dx-filemanager-details-item-thumbnail"; const DATA_GRID_DATA_ROW_CLASS = "dx-data-row"; -const PREDEFINED_COLUMN_NAMES = [ "name", "isFolder", "size", "thumbnail", "dateModified" ]; +const PREDEFINED_COLUMN_NAMES = [ "name", "isDirectory", "size", "thumbnail", "dateModified" ]; class FileManagerDetailsItemList extends FileManagerItemListBase { @@ -37,6 +37,7 @@ class FileManagerDetailsItemList extends FileManagerItemListBase { }, showColumnLines: false, showRowLines: false, + columnHidingEnabled: true, columns: this._createColumns(), onRowPrepared: this._onRowPrepared.bind(this), onContextMenuPreparing: this._onContextMenuPreparing.bind(this), @@ -79,13 +80,15 @@ class FileManagerDetailsItemList extends FileManagerItemListBase { { dataField: "dateModified", caption: "Date Modified", - width: 110 + width: 110, + hidingPriority: 1, }, { dataField: "size", caption: "File Size", width: 90, alignment: "right", + hidingPriority: 0, calculateCellValue: this._calculateSizeColumnCellValue.bind(this) } ]; @@ -163,7 +166,7 @@ class FileManagerDetailsItemList extends FileManagerItemListBase { } _calculateSizeColumnCellValue(rowData) { - return rowData.isFolder ? "" : getDisplayFileSize(rowData.size); + return rowData.isDirectory ? "" : getDisplayFileSize(rowData.size); } _ensureItemSelected(item) { 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 0a57ae68a2e0..cdb57df19831 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 @@ -368,7 +368,7 @@ class FileManagerThumbnailsItemList extends FileManagerItemListBase { } let text = `${item.name}\r\n`; - if(!item.isFolder) { + if(!item.isDirectory) { text += `Size: ${getDisplayFileSize(item.size)}\r\n`; } text += `Date Modified: ${item.dateModified}`; diff --git a/js/ui/file_manager/ui.file_manager.js b/js/ui/file_manager/ui.file_manager.js index 841570f2c41c..a78b5e7fbfd6 100644 --- a/js/ui/file_manager/ui.file_manager.js +++ b/js/ui/file_manager/ui.file_manager.js @@ -16,6 +16,7 @@ import FileManagerThumbnailsItemList from "./ui.file_manager.item_list.thumbnail import FileManagerToolbar from "./ui.file_manager.toolbar"; import FileManagerEditingControl from "./ui.file_manager.editing"; import FileManagerBreadcrumbs from "./ui.file_manager.breadcrumbs"; +import FileManagerAdaptivityControl from "./ui.file_manager.adaptivity"; import { getName, getParentPath } from "./ui.file_manager.utils"; import { FileProvider, FileManagerItem } from "./file_provider/file_provider"; @@ -27,7 +28,6 @@ import WebApiFileProvider from "./file_provider/webapi"; const FILE_MANAGER_CLASS = "dx-filemanager"; const FILE_MANAGER_CONTAINER_CLASS = FILE_MANAGER_CLASS + "-container"; const FILE_MANAGER_DIRS_PANEL_CLASS = FILE_MANAGER_CLASS + "-dirs-panel"; -const FILE_MANAGER_VIEW_SEPARATOR_CLASS = FILE_MANAGER_CLASS + "-view-separator"; const FILE_MANAGER_INACTIVE_AREA_CLASS = FILE_MANAGER_CLASS + "-inactive-area"; const FILE_MANAGER_EDITING_CONTAINER_CLASS = FILE_MANAGER_CLASS + "-editing-container"; const FILE_MANAGER_ITEMS_PANEL_CLASS = FILE_MANAGER_CLASS + "-items-panel"; @@ -48,26 +48,39 @@ class FileManager extends Widget { this._commandManager = new FileManagerCommandManager(this.option("permissions")); + this.$element().addClass(FILE_MANAGER_CLASS); + const $toolbar = $("
").appendTo(this.$element()); this._toolbar = this._createComponent($toolbar, FileManagerToolbar, { commandManager: this._commandManager, itemViewMode: this.option("itemView").mode }); + this._createAdaptivityControl(); this._createEditing(); - this._$viewContainer = this._createViewContainer(); - this.$element() - .append(this._$viewContainer) - .append(this._editing.$element()) - .addClass(FILE_MANAGER_CLASS); - this._initCommandManager(); this._setItemsViewAreaActive(false); } + _createAdaptivityControl() { + const $container = $("
") + .addClass(FILE_MANAGER_CONTAINER_CLASS) + .appendTo(this.$element()); + + this._adaptivityControl = this._createComponent($container, FileManagerAdaptivityControl, { + drawerTemplate: container => this._createFilesTreeView(container), + contentTemplate: container => this._createItemsPanel(container), + onAdaptiveStateChanged: e => this._onAdaptiveStateChanged(e) + }); + } + _createEditing() { - this._editing = this._createComponent($("
"), FileManagerEditingControl, { + const $editingContainer = $("
") + .addClass(FILE_MANAGER_EDITING_CONTAINER_CLASS) + .appendTo(this.$element()); + + this._editing = this._createComponent($editingContainer, FileManagerEditingControl, { model: { provider: this._provider, getFolders: this._getFilesTreeViewItems.bind(this), @@ -81,45 +94,31 @@ class FileManager extends Widget { onError: ({ title, details }) => this._showError(title + ": " + this._getErrorText(details)), onCreating: () => this._setItemsViewAreaActive(false) }); - this._editing.$element().addClass(FILE_MANAGER_EDITING_CONTAINER_CLASS); } - _createViewContainer() { - const $container = $("
"); - $container.addClass(FILE_MANAGER_CONTAINER_CLASS); - - this._createFilesTreeView(); - $container.append(this._filesTreeView.$element()); + _createItemsPanel($container) { + this._$itemsPanel = $("
") + .addClass(FILE_MANAGER_ITEMS_PANEL_CLASS) + .appendTo($container); - const $viewSeparator = $("
"); - $viewSeparator.addClass(FILE_MANAGER_VIEW_SEPARATOR_CLASS); - $container.append($viewSeparator); - - this._createBreadcrumbs(); - this._createItemView(); - - this._$itemsPanel = $("
").addClass(FILE_MANAGER_ITEMS_PANEL_CLASS); - this._$itemsPanel.append( - this._breadcrumbs.$element(), - this._itemView.$element() - ); - - $container.append(this._$itemsPanel); - - return $container; + this._createBreadcrumbs(this._$itemsPanel); + this._createItemView(this._$itemsPanel); } - _createFilesTreeView() { - this._filesTreeView = this._createComponent($("
"), FileManagerFilesTreeView, { + _createFilesTreeView(container) { + const $filesTreeView = $("
") + .addClass(FILE_MANAGER_DIRS_PANEL_CLASS) + .appendTo(container); + + this._filesTreeView = this._createComponent($filesTreeView, FileManagerFilesTreeView, { contextMenu: this._createContextMenu(), getItems: this._getFilesTreeViewItems.bind(this), onCurrentFolderChanged: this._onFilesTreeViewCurrentFolderChanged.bind(this), onClick: () => this._setItemsViewAreaActive(false) }); - this._filesTreeView.$element().addClass(FILE_MANAGER_DIRS_PANEL_CLASS); } - _createItemView(viewMode) { + _createItemView($container, viewMode) { const itemViewOptions = this.option("itemView"); const options = { @@ -134,15 +133,18 @@ class FileManager extends Widget { customizeDetailColumns: this.option("customizeDetailColumns") }; + const $itemView = $("
").appendTo($container); + viewMode = viewMode || itemViewOptions.mode; const widgetClass = viewMode === "thumbnails" ? FileManagerThumbnailsItemList : FileManagerDetailsItemList; - this._itemView = this._createComponent($("
"), widgetClass, options); + this._itemView = this._createComponent($itemView, widgetClass, options); - eventsEngine.on(this._itemView.$element(), "click", this._onItemViewClick.bind(this)); + eventsEngine.on($itemView, "click", this._onItemViewClick.bind(this)); } - _createBreadcrumbs() { - this._breadcrumbs = this._createComponent($("
"), FileManagerBreadcrumbs, { + _createBreadcrumbs($container) { + const $breadcrumbs = $("
").appendTo($container); + this._breadcrumbs = this._createComponent($breadcrumbs, FileManagerBreadcrumbs, { path: "", onPathChanged: e => this.setCurrentFolderPath(e.newPath), onOutsideClick: () => this._clearSelection() @@ -161,7 +163,8 @@ class FileManager extends Widget { refresh: () => this._refreshData(), thumbnails: () => this._switchView("thumbnails"), details: () => this._switchView("details"), - clear: () => this._clearSelection() + clear: () => this._clearSelection(), + showDirsPanel: () => this._adaptivityControl.toggleDrawer() }); this._commandManager.registerActions(actions); } @@ -171,6 +174,15 @@ class FileManager extends Widget { } _onItemViewSelectionChanged() { + this._updateToolbar(); + } + + _onAdaptiveStateChanged({ enabled }) { + this._commandManager.setCommandEnabled("showDirsPanel", enabled); + this._updateToolbar(); + } + + _updateToolbar() { const items = this.getSelectedItems(); this._toolbar.update(items); } @@ -207,7 +219,7 @@ class FileManager extends Widget { item = items[0]; } } - if(!item || !item.isFolder) { + if(!item || !item.isDirectory) { return; } @@ -223,8 +235,7 @@ class FileManager extends Widget { this._disposeWidget(this._itemView.option("contextMenu")); this._disposeWidget(this._itemView); - this._createItemView(viewMode); - this._$itemsPanel.append(this._itemView.$element()); + this._createItemView(this._$itemsPanel, viewMode); } _disposeWidget(widget) { @@ -305,13 +316,6 @@ class FileManager extends Widget { } _getFileProvider() { - const getterOptions = { - nameExpr: this.option("nameExpr"), - isFolderExpr: this.option("isFolderExpr"), - sizeExpr: this.option("sizeExpr"), - dateModifiedExpr: this.option("dateModifiedExpr"), - thumbnailExpr: this.option("thumbnailExpr") - }; let fileProvider = this.option("fileProvider"); if(!fileProvider) { @@ -319,11 +323,11 @@ class FileManager extends Widget { } if(Array.isArray(fileProvider)) { - return new ArrayFileProvider(extend(getterOptions, { data: fileProvider })); + return new ArrayFileProvider({ data: fileProvider }); } if(typeof fileProvider === "string") { - return new AjaxFileProvider(extend(getterOptions, { url: fileProvider })); + return new AjaxFileProvider({ url: fileProvider }); } if(fileProvider instanceof FileProvider) { @@ -339,7 +343,7 @@ class FileManager extends Widget { } } - return new ArrayFileProvider(getterOptions); + return new ArrayFileProvider(fileProvider); } _getItemThumbnailInfo(item) { @@ -357,7 +361,7 @@ class FileManager extends Widget { } _getPredefinedThumbnail(item) { - if(item.isFolder) { + if(item.isDirectory) { return "folder"; } @@ -508,43 +512,7 @@ class FileManager extends Widget { * @default false */ upload: false - }, - - /** - * @name dxFileManagerOptions.nameExpr - * @type string|function(fileItem) - * @type_function_param1 fileItem:object - * @default 'name' - */ - nameExpr: "name", - /** - * @name dxFileManagerOptions.isFolderExpr - * @type string|function(fileItem) - * @type_function_param1 fileItem:object - * @default 'isFolder' - */ - isFolderExpr: "isFolder", - /** - * @name dxFileManagerOptions.sizeExpr - * @type string|function(fileItem) - * @type_function_param1 fileItem:object - * @default 'size' - */ - sizeExpr: "size", - /** - * @name dxFileManagerOptions.dateModifiedExpr - * @type string|function(fileItem) - * @type_function_param1 fileItem:object - * @default 'dateModified' - */ - dateModifiedExpr: "dateModifiedExpr", - /** - * @name dxFileManagerOptions.thumbnailExpr - * @type string|function(fileItem) - * @type_function_param1 fileItem:object - * @default 'thumbnail' - */ - thumbnailExpr: "thumbnail" + } }); } @@ -558,11 +526,6 @@ class FileManager extends Widget { case "customizeThumbnail": case "customizeDetailColumns": case "permissions": - case "nameExpr": - case "isFolderExpr": - case "sizeExpr": - case "dateModifiedExpr": - case "thumbnailExpr": this.repaint(); break; case "onSelectedFileOpened": @@ -607,7 +570,7 @@ class FileManager extends Widget { } _onSelectedItemOpened({ item }) { - if(!item.isFolder) { + if(!item.isDirectory) { this._onSelectedFileOpenedAction({ fileItem: item }); } this._tryOpen(item); diff --git a/js/ui/file_manager/ui.file_manager.toolbar.js b/js/ui/file_manager/ui.file_manager.toolbar.js index 9eecd490b71a..99b2cae32961 100644 --- a/js/ui/file_manager/ui.file_manager.toolbar.js +++ b/js/ui/file_manager/ui.file_manager.toolbar.js @@ -15,7 +15,11 @@ const FILE_MANAGER_TOOLBAR_VIEWMODE_ITEM_CLASS = FILE_MANAGER_TOOLBAR_CLASS + "- const DEFAULT_TOOLBAR_FILE_ITEMS = [ { commandName: "download", - location: "before" + location: "before", + compactMode: { + showText: "inMenu", + locateInMenu: "auto" + } }, { commandName: "separator", @@ -23,15 +27,24 @@ const DEFAULT_TOOLBAR_FILE_ITEMS = [ }, { commandName: "move", - location: "before" + location: "before", + compactMode: { + locateInMenu: "auto" + } }, { commandName: "copy", - location: "before" + location: "before", + compactMode: { + locateInMenu: "auto" + } }, { commandName: "rename", - location: "before" + location: "before", + compactMode: { + locateInMenu: "auto" + } }, { commandName: "separator", @@ -39,27 +52,50 @@ const DEFAULT_TOOLBAR_FILE_ITEMS = [ }, { commandName: "delete", - location: "before" + location: "before", + compactMode: { + showText: "inMenu" + } }, { commandName: "clear", - location: "after" + location: "after", + locateInMenu: "never", + compactMode: { + showText: "inMenu" + } } ]; const DEFAULT_TOOLBAR_GENERAL_ITEMS = [ { - commandName: "create", + commandName: "showDirsPanel", location: "before" }, + { + commandName: "create", + location: "before", + compactMode: { + showText: "inMenu", + locateInMenu: "auto" + } + }, { commandName: "upload", - location: "before" + location: "before", + compactMode: { + showText: "inMenu", + locateInMenu: "auto" + } }, { commandName: "refresh", location: "after", - showText: "inMenu" + showText: "inMenu", + compactMode: { + showText: "inMenu", + locateInMenu: "auto" + } }, { commandName: "separator", @@ -80,22 +116,38 @@ class FileManagerToolbar extends Widget { this._generalToolbarVisible = true; - const generalToolbarItems = this._getGeneralToolbarDefaultItems(); - this._generalToolbar = this._createToolbar(generalToolbarItems); - - const fileToolbarItems = this._getFileToolbarDefaultItems(); - this._fileToolbar = this._createToolbar(fileToolbarItems, true); + this._generalToolbar = this._createToolbar(DEFAULT_TOOLBAR_GENERAL_ITEMS); + this._fileToolbar = this._createToolbar(DEFAULT_TOOLBAR_FILE_ITEMS, true); this.$element().addClass(FILE_MANAGER_TOOLBAR_CLASS + " " + FILE_MANAGER_GENERAL_TOOLBAR_CLASS); } + _render() { + super._render(); + const toolbar = this._getVisibleToolbar(); + this._checkCompactMode(toolbar); + } + + _dimensionChanged(dimension) { + if(!dimension || dimension !== "height") { + const toolbar = this._getVisibleToolbar(); + this._checkCompactMode(toolbar); + } + } + + _getVisibleToolbar() { + return this._generalToolbarVisible ? this._generalToolbar : this._fileToolbar; + } + _createToolbar(items, hidden) { const toolbarItems = this._getToolbarItems(items); const $toolbar = $("
").appendTo(this.$element()); - return this._createComponent($toolbar, Toolbar, { + const result = this._createComponent($toolbar, Toolbar, { items: toolbarItems, visible: !hidden }); + result.compactMode = false; + return result; } _getToolbarItems(items) { @@ -115,7 +167,9 @@ class FileManagerToolbar extends Widget { preparedItem.visible = groupHasItems; groupHasItems = false; } else { - const itemVisible = ensureDefined(preparedItem.visible, true); + preparedItem.available = this._isCommandAvailable(commandName); + const itemVisible = preparedItem.available && ensureDefined(preparedItem.visible, true); + preparedItem.visible = itemVisible; groupHasItems = groupHasItems || itemVisible; } @@ -144,6 +198,7 @@ class FileManagerToolbar extends Widget { widget: "dxButton", options: { text: command.text, + commandText: command.text, icon: command.icon, stylingMode: "text", onClick: e => this._executeCommand(command) @@ -180,40 +235,87 @@ class FileManagerToolbar extends Widget { }; } - _getFileToolbarDefaultItems() { - return DEFAULT_TOOLBAR_FILE_ITEMS; + _checkCompactMode(toolbar) { + if(toolbar.compactMode) { + this._toggleCompactMode(toolbar, false); + } + + const toolbarWidth = toolbar.$element().width(); + const itemsWidth = toolbar._getItemsWidth(); + const useCompactMode = toolbarWidth < itemsWidth; + + if(toolbar.compactMode !== useCompactMode) { + if(!toolbar.compactMode) { + this._toggleCompactMode(toolbar, useCompactMode); + } + toolbar.compactMode = useCompactMode; + } else if(toolbar.compactMode) { + this._toggleCompactMode(toolbar, true); + } } - _getGeneralToolbarDefaultItems() { - return DEFAULT_TOOLBAR_GENERAL_ITEMS.filter(item => this._isCommandAvailable(item.commandName)); + _toggleCompactMode(toolbar, useCompactMode) { + toolbar.beginUpdate(); + + const items = toolbar.option("items"); + items.forEach((item, index) => { + if(item.compactMode) { + let optionsSource = null; + + if(useCompactMode) { + item.saved = this._getCompactModeOptions(item, item.available); + optionsSource = item.compactMode; + } else { + optionsSource = item.saved; + } + + const options = this._getCompactModeOptions(optionsSource, item.available); + toolbar.option(`items[${index}]`, options); + } + }); + + toolbar.endUpdate(); + } + + _getCompactModeOptions({ visible, showText, locateInMenu }, available) { + return { + visible: available && ensureDefined(visible, true), + showText: ensureDefined(showText, "always"), + locateInMenu: ensureDefined(locateInMenu, "never") + }; } - _updateFileToolbar(fileItems) { + _ensureAvailableCommandsVisible(toolbar, fileItems) { + toolbar.beginUpdate(); + let groupHasItems = false; - const items = this._fileToolbar.option("items"); + const items = toolbar.option("items"); - items.forEach(({ visible, commandName }, index) => { - const itemVisible = ensureDefined(visible, true); + items.forEach((item, index) => { + const itemVisible = item.available; let showItem = false; - if(commandName === "separator") { + if(item.commandName === "separator") { showItem = groupHasItems; groupHasItems = false; } else { - showItem = this._isCommandAvailable(commandName, fileItems); + item.available = this._isCommandAvailable(item.commandName, fileItems); + showItem = item.available; groupHasItems = groupHasItems || showItem; } if(showItem !== itemVisible) { const optionName = `items[${index}].visible`; - this._fileToolbar.option(optionName, showItem); + toolbar.option(optionName, showItem); } }); + + toolbar.endUpdate(); } - _fileToolbarHasEffectiveItems() { + _fileToolbarHasEffectiveItems(fileItems) { const items = this._fileToolbar.option("items"); - return items.some(item => item.commandName !== "clear" && ensureDefined(item.visible, true)); + return items.some(({ commandName }) => commandName !== "clear" && this._commandManager.isCommandAvailable(commandName, fileItems)); } _executeCommand(command) { @@ -247,9 +349,7 @@ class FileManagerToolbar extends Widget { update(fileItems) { fileItems = ensureDefined(fileItems, []); - this._updateFileToolbar(fileItems); - - const showGeneralToolbar = fileItems.length === 0 || !this._fileToolbarHasEffectiveItems(); + const showGeneralToolbar = fileItems.length === 0 || !this._fileToolbarHasEffectiveItems(fileItems); if(this._generalToolbarVisible !== showGeneralToolbar) { this._generalToolbar.option("visible", showGeneralToolbar); this._fileToolbar.option("visible", !showGeneralToolbar); @@ -258,6 +358,10 @@ class FileManagerToolbar extends Widget { this.$element().toggleClass(FILE_MANAGER_GENERAL_TOOLBAR_CLASS, showGeneralToolbar); this.$element().toggleClass(FILE_MANAGER_FILE_TOOLBAR_CLASS, !showGeneralToolbar); } + + const toolbar = this._getVisibleToolbar(); + this._ensureAvailableCommandsVisible(toolbar, fileItems); + this._checkCompactMode(toolbar); } } diff --git a/js/ui/list/ui.list.edit.decorator.selection.js b/js/ui/list/ui.list.edit.decorator.selection.js index 4b89827e2600..a29a88ea1baf 100644 --- a/js/ui/list/ui.list.edit.decorator.selection.js +++ b/js/ui/list/ui.list.edit.decorator.selection.js @@ -119,12 +119,16 @@ registerDecorator( }, _renderSelectAll: function() { - var $selectAll = this._$selectAll = $("
").addClass(SELECT_DECORATOR_SELECT_ALL_CLASS); + var $selectAll = this._$selectAll = $("
").addClass(SELECT_DECORATOR_SELECT_ALL_CLASS), + list = this._list, + downArrowHandler = list._supportedKeys().downArrow.bind(list); - this._selectAllCheckBox = this._list._createComponent($("
") + this._selectAllCheckBox = list._createComponent($("
") .addClass(SELECT_DECORATOR_SELECT_ALL_CHECKBOX_CLASS) .appendTo($selectAll), CheckBox); + this._selectAllCheckBox.registerKeyHandler("downArrow", downArrowHandler); + $("
").addClass(SELECT_DECORATOR_SELECT_ALL_LABEL_CLASS) .text(this._list.option("selectAllText")) .appendTo($selectAll); diff --git a/js/ui/number_box/number_box.base.js b/js/ui/number_box/number_box.base.js index 694d8725fe2f..4d27a800bb86 100644 --- a/js/ui/number_box/number_box.base.js +++ b/js/ui/number_box/number_box.base.js @@ -93,7 +93,15 @@ var NumberBoxBase = TextEditor.inherit({ * @type string * @default "Value must be a number" */ - invalidValueMessage: messageLocalization.format("dxNumberBox-invalidValueMessage") + invalidValueMessage: messageLocalization.format("dxNumberBox-invalidValueMessage"), + + /** + * @name dxNumberBoxOptions.buttons + * @type Array + * @default undefined + * @inheritdoc + */ + buttons: void 0, /** * @name dxNumberBoxOptions.mask diff --git a/js/ui/pivot_grid/data_source.js b/js/ui/pivot_grid/data_source.js index ad2388645a75..a6960ffe646d 100644 --- a/js/ui/pivot_grid/data_source.js +++ b/js/ui/pivot_grid/data_source.js @@ -197,12 +197,14 @@ module.exports = Class.inherit((function() { if(headerItem && headerItem.index >= 0) { applyingItemIndexesToCurrent[newItem.index] = headerItem.index; } else if(emptyIndex) { - headerItem = findHeaderItem(headerItems, createPath(newItems.slice(1))); + var path = createPath(newItems.slice(1)); + headerItem = findHeaderItem(headerItems, path); - var parentItems = headerItem && headerItem.children || headerItems; - - parentItems[index] = newItem; - newItem.index = applyingItemIndexesToCurrent[newItem.index] = emptyIndex++; + var parentItems = path.length ? headerItem && headerItem.children : headerItems; + if(parentItems) { + parentItems[index] = newItem; + newItem.index = applyingItemIndexesToCurrent[newItem.index] = emptyIndex++; + } } } })).done(function() { diff --git a/js/ui/scheduler/ui.scheduler.appointment_form.js b/js/ui/scheduler/ui.scheduler.appointment_form.js index c3d863aef372..3f52057b4ffd 100644 --- a/js/ui/scheduler/ui.scheduler.appointment_form.js +++ b/js/ui/scheduler/ui.scheduler.appointment_form.js @@ -54,7 +54,7 @@ const SchedulerAppointmentForm = { colCount: 2, showColonAfterLabel: false, screenByWidth: () => { - const formWidth = $container.outerWidth(); + const formWidth = $container.parent().outerWidth(); this._updateLabelLocation(formWidth); return formWidth < SCREEN_SIZE_OF_SINGLE_COLUMN ? "xs" : "lg"; } diff --git a/js/ui/scheduler/ui.scheduler.js b/js/ui/scheduler/ui.scheduler.js index dcf2d84bdafe..4b7ed775faff 100644 --- a/js/ui/scheduler/ui.scheduler.js +++ b/js/ui/scheduler/ui.scheduler.js @@ -65,6 +65,7 @@ const RECURRENCE_EDITOR_ITEM_CLASS = "dx-scheduler-recurrence-rule-item"; const RECURRENCE_EDITOR_OPENED_ITEM_CLASS = "dx-scheduler-recurrence-rule-item-opened"; const WIDGET_SMALL_WIDTH = 400; const APPOINTMENT_POPUP_WIDTH = 610; +const APPOINTMENT_POPUP_FULLSCREEN_WINDOW_WIDTH = 768; const TOOLBAR_ITEM_AFTER_LOCATION = "after"; const TOOLBAR_ITEM_BEFORE_LOCATION = "before"; @@ -1538,6 +1539,8 @@ const Scheduler = Widget.inherit({ } this.hideAppointmentTooltip(); + this.resizePopup(); + this._updatePopupFullScreenMode(); }, _clean: function() { @@ -2158,9 +2161,10 @@ const Scheduler = Widget.inherit({ this._popup = this._createComponent(this._$popup, Popup, this._popupConfig(appointmentData)); }, - _popupContent: function(appointmentData, processTimeZone) { - var $popupContent = this._popup.$content(); - this._createOrUpdateForm(appointmentData, processTimeZone, $popupContent); + _popupContent(appointmentData, processTimeZone) { + const $popupContent = this._popup.$content(); + const $form = $("
").appendTo($popupContent); + this._createOrUpdateForm(appointmentData, processTimeZone, $form); return $popupContent; }, @@ -2250,34 +2254,38 @@ const Scheduler = Widget.inherit({ }); }, - _popupConfig: function(appointmentData) { - var template = this._getTemplateByOption("appointmentPopupTemplate"); + _isPopupFullScreenNeeded() { + if(windowUtils.hasWindow()) { + const window = windowUtils.getWindow(); + return $(window).width() < APPOINTMENT_POPUP_FULLSCREEN_WINDOW_WIDTH; + } + return false; + }, + _updatePopupFullScreenMode() { + if(this._popup && this._popup.option("visible")) { + const isFullScreen = this._isPopupFullScreenNeeded(); + this._popup.option({ + maxWidth: isFullScreen ? "100%" : APPOINTMENT_POPUP_WIDTH, + fullScreen: isFullScreen + }); + } + }, + + _popupConfig(appointmentData) { + const template = this._getTemplateByOption("appointmentPopupTemplate"); return { - maxWidth: APPOINTMENT_POPUP_WIDTH, - height: 'auto', - maxHeight: this._popupMaxHeight(), - onHiding: (function() { - this.focus(); - }).bind(this), - contentTemplate: new FunctionTemplate(function(options) { - return template.render({ + height: "auto", + maxHeight: "100%", + onHiding: () => this.focus(), + contentTemplate: new FunctionTemplate(options => + template.render({ model: appointmentData, container: options.container - }); - }), - onShown: () => { - this._setPopupContentMaxHeight(); - }, + }) + ), + onShowing: () => this._updatePopupFullScreenMode(), defaultOptionsRules: [ - { - device: function() { - return !devices.current().generic; - }, - options: { - fullScreen: true - } - }, { device: () => devices.current().android, options: { @@ -2288,16 +2296,6 @@ const Scheduler = Widget.inherit({ }; }, - _popupMaxHeight: function() { - let windowHeight = $(windowUtils.getWindow()).height(); - - if(this._popup && this._popup.option("fullScreen")) { - return windowHeight; - } - - return windowHeight * 0.8; - }, - _getPopupToolbarItems: function() { const isIOs = devices.current().platform === "ios"; return [ @@ -2928,34 +2926,12 @@ const Scheduler = Widget.inherit({ } }, - resizePopup: function() { - domUtils.triggerResizeEvent(this._popup.$element()); - - // NOTE: WA because of T731123 - this._setPopupContentMaxHeight(); - }, - - _setPopupContentMaxHeight: function() { - let popupContent = this._popup.$content(); - let $scrollable = popupContent.find(".dx-scrollable-content"); - - $scrollable.css("height", "initial"); - - if($scrollable.length && $scrollable.get(0).getBoundingClientRect().height > this._getMaxPopupContentHeight()) { - $scrollable.outerHeight(popupContent.height()); + resizePopup() { + if(this.getAppointmentPopup()) { + domUtils.triggerResizeEvent(this.getAppointmentPopup().$element()); } }, - _getMaxPopupContentHeight: function() { - let $bottom = this._popup.bottomToolbar(); - let $top = this._popup.topToolbar(); - let bottomHeight = $bottom && $bottom.length ? $bottom.get(0).getBoundingClientRect().height : 0; - let topHeight = $top && $top.length ? $top.get(0).getBoundingClientRect().height : 0; - - return this._popupMaxHeight() - bottomHeight - topHeight; - - }, - dayHasAppointment: function(day, appointment, trimTime) { var startDate = new Date(this.fire("getField", "startDate", appointment)), endDate = new Date(this.fire("getField", "endDate", appointment)), diff --git a/js/ui/speed_dial_action/speed_dial_action.js b/js/ui/speed_dial_action/speed_dial_action.js index aeaf8e6d210f..2a28cf66266f 100644 --- a/js/ui/speed_dial_action/speed_dial_action.js +++ b/js/ui/speed_dial_action/speed_dial_action.js @@ -56,10 +56,10 @@ const SpeedDialAction = Widget.inherit({ animation: { show: { type: "pop", - duration: 150, - easing: "linear", + duration: 200, + easing: "cubic-bezier(0.4, 0, 0.2, 1)", from: { - scale: 0.3, + scale: 0, opacity: 0 }, to: { @@ -69,13 +69,15 @@ const SpeedDialAction = Widget.inherit({ }, hide: { type: "pop", - duration: 100, - easing: "linear", + duration: 200, + easing: "cubic-bezier(0.4, 0, 0.2, 1)", from: { - scale: 1 + scale: 1, + opacity: 1 }, to: { - scale: 0 + scale: 0, + opacity: 0 } } }, diff --git a/js/ui/speed_dial_action/speed_dial_main_item.js b/js/ui/speed_dial_action/speed_dial_main_item.js index 3ebe23a8bd3b..7a8baa0611c5 100644 --- a/js/ui/speed_dial_action/speed_dial_main_item.js +++ b/js/ui/speed_dial_action/speed_dial_main_item.js @@ -123,6 +123,7 @@ const SpeedDialMainItem = SpeedDialItem.inherit({ }); const actionOffsetY = this.initialOption("indent") + this.initialOption("childIndent") * i; + const actionAnimationDelay = 30; action._options.position = { of: this.$content(), @@ -134,8 +135,8 @@ const SpeedDialMainItem = SpeedDialItem.inherit({ } }; - action._options.animation.show.delay = action._options.animation.show.duration * i; - action._options.animation.hide.delay = action._options.animation.hide.duration * (lastActionIndex - i); + action._options.animation.show.delay = actionAnimationDelay * i; + action._options.animation.hide.delay = actionAnimationDelay * (lastActionIndex - i); this._actionItems.push(this._createComponent($actionElement, SpeedDialItem, action._options)); } diff --git a/js/ui/text_box/ui.text_editor.base.js b/js/ui/text_box/ui.text_editor.base.js index a8fdc280071a..bb11226c0537 100644 --- a/js/ui/text_box/ui.text_editor.base.js +++ b/js/ui/text_box/ui.text_editor.base.js @@ -106,7 +106,7 @@ var TextEditorBase = Editor.inherit({ /** * @name dxTextEditorOptions.buttons - * @type Array + * @type Array * @default undefined */ buttons: void 0, diff --git a/js/viz/axes/tick_generator.js b/js/viz/axes/tick_generator.js index 296ac8504d1d..841061b55dac 100644 --- a/js/viz/axes/tick_generator.js +++ b/js/viz/axes/tick_generator.js @@ -66,7 +66,7 @@ function discreteGenerator(options) { const getValue = value => value; const getLogValue = base => value => getLog(value, base); const raiseTo = base => value => mathPow(base, value); -const correctValueByInterval = (post, round, getValue) => (value, interval) => post(adjust(round(adjust(getValue(value) / interval)) * interval)); +const correctValueByInterval = (post, round, getValue) => (value, interval) => adjust(post(round(adjust(getValue(value) / interval)) * interval)); function correctMinValueByEndOnTick(floorFunc, ceilFunc, resolveEndOnTick, endOnTick) { if(isDefined(endOnTick)) { diff --git a/package.json b/package.json index 33e836338fa1..e6d4d61cfee0 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "cldr-core": "latest", "cldrjs": "^0.5.0", "del": "^2.2.2", - "devexpress-diagram": "0.0.14", + "devexpress-diagram": "0.0.20", "dot": "1.1.2", "eslint": "^5.9.0", "eslint-plugin-spellcheck": "0.0.11", diff --git a/styles/widgets/common/fileManager.less b/styles/widgets/common/fileManager.less index 5fe8763c2c45..433639ce2d12 100644 --- a/styles/widgets/common/fileManager.less +++ b/styles/widgets/common/fileManager.less @@ -1,4 +1,6 @@ .dx-filemanager { + height: 500px; + display: flex; flex-direction: column; border: 1px solid; @@ -17,19 +19,20 @@ height: 100%; border-top: 1px solid; box-sizing: border-box; + overflow: hidden; .dx-filemanager-dirs-panel { - width: 30%; min-width: 250px; max-width: 300px; padding: 5px 10px; } } - .dx-filemanager-view-separator { - min-width: 1px; + .dx-filemanager-dirs-panel { + height: 100%; + border-right: 1px solid; } - + .dx-filemanager-breadcrumbs { padding: 6px 11px; border-bottom: 1px solid; @@ -205,6 +208,23 @@ overflow: auto; } +.dx-filemanager-dialog-folder-chooser > div { + height: 100%; +} + +.dx-filemanager-dialog-popup > .dx-overlay-content { + min-width: 300px; + max-width: 400px; + min-height: 300px; + max-height: 400px; +} + +.dx-filemanager-dialog-name-editor-popup > .dx-overlay-content { + max-width: 340px; + min-height: 180px; + max-height: 180px; +} + .dx-filemanager-progresspanel { box-sizing: border-box; padding: 5px; diff --git a/styles/widgets/generic/diagram.generic.less b/styles/widgets/generic/diagram.generic.less index f2e02f2cb3ee..588cfb9f6d9f 100644 --- a/styles/widgets/generic/diagram.generic.less +++ b/styles/widgets/generic/diagram.generic.less @@ -8,8 +8,14 @@ .dx-diagram-left-panel, .dx-diagram-right-panel { background: @overlay-content-bg; - .dx-accordion .dx-accordion-item { - border: none; + .dx-accordion { + .dx-accordion-item { + border-left: none; + border-right: none; + } + .dx-accordion-item:first-of-type { + border-top: none; + } } } .dx-diagram-left-panel { diff --git a/styles/widgets/generic/fileManager.generic.less b/styles/widgets/generic/fileManager.generic.less index 8481591fd71e..ce08dbfc2586 100644 --- a/styles/widgets/generic/fileManager.generic.less +++ b/styles/widgets/generic/fileManager.generic.less @@ -44,14 +44,14 @@ } - .dx-filemanager-view-separator { - background: @filemanager-border-color; - } - .dx-filemanager-container { border-top-color: @filemanager-border-color; } - + + .dx-filemanager-dirs-panel { + border-right-color: @filemanager-border-color; + } + .dx-filemanager-breadcrumbs { border-bottom-color: @filemanager-border-color; diff --git a/styles/widgets/material/diagram.material.less b/styles/widgets/material/diagram.material.less index 249b0f3f0cdf..39afbdb14d95 100644 --- a/styles/widgets/material/diagram.material.less +++ b/styles/widgets/material/diagram.material.less @@ -8,8 +8,14 @@ .dx-diagram-left-panel, .dx-diagram-right-panel { background: @overlay-content-bg; - .dx-accordion .dx-accordion-item { - border: none; + .dx-accordion { + .dx-accordion-item { + border-left: none; + border-right: none; + } + .dx-accordion-item:first-of-type { + border-top: none; + } } } diff --git a/styles/widgets/material/fileManager.material.less b/styles/widgets/material/fileManager.material.less index aaf7804e99f6..aec5d88d22f5 100644 --- a/styles/widgets/material/fileManager.material.less +++ b/styles/widgets/material/fileManager.material.less @@ -44,14 +44,14 @@ } - .dx-filemanager-view-separator { - background: @filemanager-border-color; - } - .dx-filemanager-container { border-top-color: @filemanager-border-color; } - + + .dx-filemanager-dirs-panel { + border-right-color: @filemanager-border-color; + } + .dx-filemanager-breadcrumbs { border-bottom-color: @filemanager-border-color; @@ -153,3 +153,7 @@ } } } + +.dx-filemanager-context-menu .dx-menu-item-has-text:not(.dx-menu-item-has-icon) .dx-menu-item-text { + margin-left: @FILEMANAGER_CONTEXTMENU_TEXT_ITEM_LEFT_MARGIN; +} diff --git a/styles/widgets/material/size-schemes/compact.less b/styles/widgets/material/size-schemes/compact.less index ea1232e71ffd..57ba09b151a1 100644 --- a/styles/widgets/material/size-schemes/compact.less +++ b/styles/widgets/material/size-schemes/compact.less @@ -51,6 +51,7 @@ @FILEMANAGER_TOOLBAR_VIEWMODE_WIDTH: 150px; @FILEMANAGER_FILE_ACTIONS_BUTTON_WIDTH: 26px; @FILEMANAGER_FILE_ACTIONS_BUTTON_HEIGHT: 22px; +@FILEMANAGER_CONTEXTMENU_TEXT_ITEM_LEFT_MARGIN: 30px; @import "shared/base"; diff --git a/styles/widgets/material/size-schemes/default.less b/styles/widgets/material/size-schemes/default.less index a10c79a31cd0..9341b0902e80 100644 --- a/styles/widgets/material/size-schemes/default.less +++ b/styles/widgets/material/size-schemes/default.less @@ -50,6 +50,7 @@ @FILEMANAGER_TOOLBAR_VIEWMODE_WIDTH: 190px; @FILEMANAGER_FILE_ACTIONS_BUTTON_WIDTH: 32px; @FILEMANAGER_FILE_ACTIONS_BUTTON_HEIGHT: 28px; +@FILEMANAGER_CONTEXTMENU_TEXT_ITEM_LEFT_MARGIN: 48px; @import "shared/base"; diff --git a/testing/helpers/fileManagerHelpers.js b/testing/helpers/fileManagerHelpers.js index 0c09cd7ce36e..c549d0a25de7 100644 --- a/testing/helpers/fileManagerHelpers.js +++ b/testing/helpers/fileManagerHelpers.js @@ -142,66 +142,66 @@ export const createTestFileSystem = () => { return [ { name: "Folder 1", - isFolder: true, - children: [ + isDirectory: true, + items: [ { name: "Folder 1.1", - isFolder: true, - children: [ + isDirectory: true, + items: [ { name: "File 1-1.txt", - isFolder: false + isDirectory: false }, { name: "File 1-2.txt", - isFolder: false + isDirectory: false }, { name: "File 1-3.png", - isFolder: false + isDirectory: false }, { name: "File 1-4.jpg", - isFolder: false + isDirectory: false }] }, { name: "Folder 1.2", - isFolder: true + isDirectory: true }, { name: "File 1-1.txt", - isFolder: false + isDirectory: false }, { name: "File 1-2.jpg", - isFolder: false + isDirectory: false }] }, { name: "Folder 2", - isFolder: true, - children: [ + isDirectory: true, + items: [ { name: "File 2-1.jpg", - isFolder: false + isDirectory: false }] }, { name: "Folder 3", - isFolder: true + isDirectory: true }, { name: "File 1.txt", - isFolder: false + isDirectory: false }, { name: "File 2.jpg", - isFolder: false + isDirectory: false }, { name: "File 3.xml", - isFolder: false + isDirectory: false } ]; }; diff --git a/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js b/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js index 7e84da3e76bf..2777f8380e47 100644 --- a/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js +++ b/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js @@ -1938,6 +1938,70 @@ QUnit.test("Apply partial dataController with empty data. Update rows", function ]); }); +// T737140 +QUnit.test("Apply partial dataController by rows after column collapsing", function(assert) { + var dataController = new DataController({ + dataSource: { + fields: [ + { area: "row" }, { area: "row" }, + { area: "column" }, { area: "column" }, + { caption: 'Sum', format: 'fixedPoint', area: "data" } + ], + rows: [{ value: 'Vasya', index: 0 }, { value: 'Piter', index: 1 }], + columns: [ + { value: 'A', index: 2, children: [{ value: 'P1', index: 0 }, { value: 'P2', index: 1 }] }, + { value: 'C', index: 3 } + ], + values: [ + [1, 2, 3, 6, 12], + [2, 3, 4, 9, 18], + [3, 5, 7, 15, 30] + ] + } + }); + + dataController.collapseHeaderItem('column', ['A']); + + dataController.applyPartialDataSource('row', ['Piter'], { + columns: [ + { value: 'A', index: 2, children: [{ value: 'P1', index: 0 }, { value: 'P2', index: 1 }] }, + { value: 'C', index: 3 } + ], + rows: [ + { value: "T1", index: 0 } + ], + values: [ + [1, 2, 3, 6, 12] + ] + }); + + assert.deepEqual(prepareLoadedData(dataController.getData().rows), [ + { value: 'Vasya', index: 0 }, + { value: 'Piter', index: 1, children: [{ + value: "T1", + index: 3 + }] } + ]); + + assert.deepEqual(prepareLoadedData(dataController.getData().columns), [ + { value: 'A', index: 2 }, + { value: 'C', index: 3 } + ]); + + var cells = []; + $.each(dataController.getCellsInfo(), function() { + cells.push($.map(this, function(cell) { + return cell && cell.text; + })); + }); + assert.deepEqual(cells, [ + ['3', '6', '12'], + ['3', '6', '12'], + ['4', '9', '18'], + ['7', '15', '30'] + ]); +}); + QUnit.test("Apply partial dataController with empty data. Update Rows", function(assert) { var dataController = new DataController({ dataSource: { diff --git a/testing/tests/DevExpress.ui.widgets.scheduler/appointmentPopup.tests.js b/testing/tests/DevExpress.ui.widgets.scheduler/appointmentPopup.tests.js index 8556ff51857c..0062135d92d8 100644 --- a/testing/tests/DevExpress.ui.widgets.scheduler/appointmentPopup.tests.js +++ b/testing/tests/DevExpress.ui.widgets.scheduler/appointmentPopup.tests.js @@ -8,6 +8,7 @@ import devices from "core/devices"; import SchedulerTimezoneEditor from "ui/scheduler/timezones/ui.scheduler.timezone_editor"; import fx from "animation/fx"; import { DataSource } from "data/data_source/data_source"; +import resizeCallbacks from "core/utils/resize_callbacks"; import "ui/scheduler/ui.scheduler"; import "ui/switch"; @@ -151,13 +152,7 @@ QUnit.test("popup should have right height", function(assert) { var popup = this.instance.getAppointmentPopup(); assert.equal(popup.option("height"), 'auto', "popup has correct height"); - - // NOTE: popup maxHeight depends on device. - if(devices.current().generic) { - assert.equal(popup.option("maxHeight"), $(window).height() * 0.8, "popup has correct max-height"); - } else { - assert.equal(popup.option("maxHeight"), $(window).height(), "popup has correct max-height"); - } + assert.equal(popup.option("maxHeight"), "100%", "popup has correct max-height"); }); QUnit.test("showAppointmentPopup should render a popup content only once", function(assert) { @@ -895,26 +890,6 @@ QUnit.test("focus is called on popup hiding", function(assert) { assert.ok(spy.calledOnce, "focus is called"); }); -QUnit.test("Appointment popup should have correct 'fullScreen' option", function(assert) { - this.instance.showAppointmentPopup({ startDate: new Date(2015, 1, 1), endDate: new Date(2015, 1, 2) }); - - var popup = this.instance.getAppointmentPopup(); - - if(!devices.current().generic) { - assert.ok(popup.option("fullScreen"), "the fullScreen option is OK"); - } else { - assert.notOk(popup.option("fullScreen"), "the fullScreen option is OK"); - } -}); - -QUnit.test("Appointment popup should have correct 'maxWidth' option", function(assert) { - this.instance.showAppointmentPopup({ startDate: new Date(2015, 1, 1), endDate: new Date(2015, 1, 2) }); - - var popup = this.instance.getAppointmentPopup(); - - assert.equal(popup.option("maxWidth"), 610, "the maxWidth option is OK"); -}); - QUnit.test("Popup should has close button in mobile theme when allowUpdating: false", function(assert) { this.instance.option({ editing: { @@ -1075,3 +1050,44 @@ QUnit.test("Appointment form will have right dates on multiple openings (T727713 assert.deepEqual(formData.startDate, appointments[0].startDate, "Second opening appointment form has right startDate"); assert.deepEqual(formData.endDate, appointments[0].endDate, "Second opening appointment form has right endDate"); }); + +QUnit.test("The vertical scroll bar is shown when an appointment popup fill to a small window's height", function(assert) { + const scheduler = createInstance({ + currentDate: new Date(2015, 1, 1), + currentView: "day", + dataSource: [] + }); + + const popup = scheduler.appointmentPopup; + popup.setInitialPopupSize({ height: 300 }); + + scheduler.instance.fire("showAddAppointmentPopup", { + startDate: new Date(2015, 1, 1), + endDate: new Date(2015, 1, 1, 1), + allDay: true + }); + + assert.ok(popup.hasVerticalScroll(), "The popup has the vertical scrolling"); +}); + +QUnit.test("The resize event of appointment popup is triggered the the window is resize", function(assert) { + const scheduler = createInstance({ + currentDate: new Date(2015, 1, 1), + currentView: "day", + dataSource: [] + }); + + scheduler.instance.fire("showAddAppointmentPopup", { + startDate: new Date(2015, 1, 1), + endDate: new Date(2015, 1, 1, 1), + allDay: true + }); + + const $popup = scheduler.appointmentPopup.getPopupInstance().$element(); + let isResizeEventTriggered; + $($popup).on("dxresize", () => { + isResizeEventTriggered = true; + }); + resizeCallbacks.fire(); + assert.ok(isResizeEventTriggered, "The resize event of popup is triggered"); +}); diff --git a/testing/tests/DevExpress.ui.widgets.scheduler/common.tests.js b/testing/tests/DevExpress.ui.widgets.scheduler/common.tests.js index 08127ca49e8e..1d38e42ca2e3 100644 --- a/testing/tests/DevExpress.ui.widgets.scheduler/common.tests.js +++ b/testing/tests/DevExpress.ui.widgets.scheduler/common.tests.js @@ -1855,15 +1855,6 @@ QUnit.testStart(function() { assert.ok(repaintStub.calledOnce, "Sheduler was repainted"); }); - QUnit.test("Appointment popup should have right defaultOptionsRules", function(assert) { - this.createInstance(); - this.instance.showAppointmentPopup({ startDate: new Date(2015, 1, 1), endDate: new Date(2015, 1, 2) }); - - var popupDefaultOptions = this.instance.getAppointmentPopup().option("defaultOptionsRules")[0].options; - - assert.deepEqual(popupDefaultOptions, { fullScreen: true }, "Popup has right default"); - }); - QUnit.test("Filter options should be updated when dataSource is changed", function(assert) { this.createInstance({ currentDate: new Date(2016, 2, 15), @@ -3198,57 +3189,6 @@ QUnit.testStart(function() { assert.equal(args.form, this.instance.getAppointmentDetailsForm(), "Appointment form is OK"); }); - QUnit.test("'_setPopupContentMaxHeight' should be called while opening popup", function(assert) { - this.createInstance({ - currentDate: new Date(2015, 1, 1), - currentView: "day", - dataSource: [] - }); - - var setPopupMaxHeight = sinon.stub(this.instance, "_setPopupContentMaxHeight"); - - this.instance.fire("showAddAppointmentPopup", { - startDate: new Date(2015, 1, 1), - endDate: new Date(2015, 1, 1, 1), - allDay: true - }); - - assert.ok(setPopupMaxHeight.called, "method has been called"); - }); - - QUnit.test("Popup content should have correct height on a small screen", function(assert) { - let realClientHeight = document.documentElement.clientHeight; - - try { - Object.defineProperty(document.documentElement, 'clientHeight', { - get: () => 500, - configurable: true - }); - - this.createInstance({ - currentDate: new Date(2015, 1, 1), - currentView: "day", - dataSource: [] - }); - - this.instance.fire("showAddAppointmentPopup", { - startDate: new Date(2015, 1, 1), - endDate: new Date(2015, 1, 1, 1), - allDay: true - }); - this.clock.tick(300); - - let $popupContent = this.instance._popup.$content(); - let $scrollable = $popupContent.find(".dx-scrollable-content"); - - assert.equal($scrollable.get(0).getBoundingClientRect().height, $popupContent.height(), "Height is correct"); - } finally { - Object.defineProperty(document.documentElement, 'clientHeight', { - get: () => realClientHeight - }); - } - }); - QUnit.test("Option changed", function(assert) { this.createInstance(); diff --git a/testing/tests/DevExpress.ui.widgets.scheduler/helpers.js b/testing/tests/DevExpress.ui.widgets.scheduler/helpers.js index 14b7d1f632df..23e9c6911861 100644 --- a/testing/tests/DevExpress.ui.widgets.scheduler/helpers.js +++ b/testing/tests/DevExpress.ui.widgets.scheduler/helpers.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import { extend } from "core/utils/extend"; export const TOOLBAR_TOP_LOCATION = "top"; export const TOOLBAR_BOTTOM_LOCATION = "bottom"; @@ -77,15 +78,18 @@ export class SchedulerTestWrapper { this.appointmentPopup = { getPopup: () => $(".dx-overlay-wrapper.dx-scheduler-appointment-popup"), + hasVerticalScroll: () => { + const scrollableContainer = this.appointmentPopup.getPopup().find(".dx-scrollable-container").get(0); + return scrollableContainer.scrollHeight > scrollableContainer.clientHeight; + }, getPopupInstance: () => $(".dx-scheduler-appointment-popup.dx-widget").dxPopup("instance"), isVisible: () => this.appointmentPopup.getPopup().length !== 0, hide: () => this.appointmentPopup.getPopup().find(".dx-closebutton.dx-button").trigger("dxclick"), - setInitialWidth: width => { + setInitialPopupSize: (size) => { const popupConfig = this.instance._popupConfig; this.instance._popupConfig = appointmentData => { const config = popupConfig.call(this.instance, appointmentData); - config.width = width; - return config; + return extend(config, size); }; }, setPopupWidth: width => this.appointmentPopup.getPopupInstance().option("width", width), diff --git a/testing/tests/DevExpress.ui.widgets.scheduler/integration.adaptivity.tests.js b/testing/tests/DevExpress.ui.widgets.scheduler/integration.adaptivity.tests.js index 28b1270acd14..aea00dc32be5 100644 --- a/testing/tests/DevExpress.ui.widgets.scheduler/integration.adaptivity.tests.js +++ b/testing/tests/DevExpress.ui.widgets.scheduler/integration.adaptivity.tests.js @@ -44,6 +44,15 @@ const moduleConfig = { } }; +const setWindowWidth = width => { + Object.defineProperty(document.documentElement, "clientWidth", { + get: () => width, + configurable: true + }); +}; + +const resetWindowWidth = () => delete document.documentElement.clientWidth; + module("Mobile tooltip", moduleConfig, () => { test("Tooltip should be render scroll, if count of items in list is a lot", function(assert) { const MAX_TOOLTIP_HEIGHT = 250; @@ -138,98 +147,106 @@ module("Mobile tooltip", moduleConfig, () => { }); }); -if(devices.real().deviceType === "desktop") { - module("Appointment form", moduleConfig, () => { - test("Label location is left when the form's width > 610px on first show", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(700); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); +module("Appointment form", { + beforeEach() { + fx.off = true; + setWindowWidth(800); + }, - const form = scheduler.appointmentForm.getFormInstance(); - assert.equal(form.option("labelLocation"), "left", "label location of Form"); - }); + afterEach() { + fx.off = false; + resetWindowWidth(); + } +}, () => { + test("Label location is left when the form's width > 610px on first show", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 700 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - test("Label location is top when the form's width < 610px on first show", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(600); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + const form = scheduler.appointmentForm.getFormInstance(); + assert.equal(form.option("labelLocation"), "left", "label location of Form"); + }); - const form = scheduler.appointmentForm.getFormInstance(); - assert.equal(form.option("labelLocation"), "top", "label location of Form"); - }); + test("Label location is top when the form's width < 610px on first show", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 600 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - test("Items has layout has one column when the form's width < 460px on first show", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(400); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + const form = scheduler.appointmentForm.getFormInstance(); + assert.equal(form.option("labelLocation"), "top", "label location of Form"); + }); - assert.ok(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has single column"); - }); + test("Items has layout has one column when the form's width < 460px on first show", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 400 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - test("Items has layout has non-one column when the form's width > 460px on first show", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(463); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + assert.ok(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has single column"); + }); - assert.notOk(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has not single column"); - }); + test("Items has layout has non-one column when the form's width > 460px on first show", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 463 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - test("Label location is left when the form's width > 610px on window resizing", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(400); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + assert.notOk(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has not single column"); + }); - scheduler.appointmentPopup.setPopupWidth(620); - resizeCallbacks.fire(); + test("Label location is left when the form's width > 610px on window resizing", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 400 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - const form = scheduler.appointmentForm.getFormInstance(); - assert.equal(form.option("labelLocation"), "left", "label location of Form"); - }); + scheduler.appointmentPopup.setPopupWidth(620); + resizeCallbacks.fire(); - test("Label location is top when the form's width < 610px on window resizing", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(700); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + const form = scheduler.appointmentForm.getFormInstance(); + assert.equal(form.option("labelLocation"), "left", "label location of Form"); + }); - scheduler.appointmentPopup.setPopupWidth(600); - resizeCallbacks.fire(); + test("Label location is top when the form's width < 610px on window resizing", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 700 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - const form = scheduler.appointmentForm.getFormInstance(); + scheduler.appointmentPopup.setPopupWidth(600); + resizeCallbacks.fire(); - assert.equal(form.option("labelLocation"), "top", "label location of Form"); - }); + const form = scheduler.appointmentForm.getFormInstance(); - test("Items has layout has one column when the form's width < 460px on window resizing", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(500); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + assert.equal(form.option("labelLocation"), "top", "label location of Form"); + }); - scheduler.appointmentPopup.setPopupWidth(400); - resizeCallbacks.fire(); + test("Items has layout has one column when the form's width < 460px on window resizing", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 500 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - assert.ok(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has single column"); - }); + scheduler.appointmentPopup.setPopupWidth(400); + resizeCallbacks.fire(); - test("Items has layout has non-one column when the form's width > 460px on window resizing", function(assert) { - const scheduler = createInstance(); - scheduler.appointmentPopup.setInitialWidth(460); - scheduler.appointments.compact.click(); - scheduler.tooltip.clickOnItem(); + assert.ok(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has single column"); + }); - scheduler.appointmentPopup.setPopupWidth(463); - resizeCallbacks.fire(); + test("Items has layout has non-one column when the form's width > 460px on window resizing", function(assert) { + const scheduler = createInstance(); + scheduler.appointmentPopup.setInitialPopupSize({ width: 460 }); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); - assert.notOk(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has not single column"); - }); + scheduler.appointmentPopup.setPopupWidth(463); + resizeCallbacks.fire(); + + assert.notOk(scheduler.appointmentForm.hasFormSingleColumn(), "Appointment form has not single column"); }); -} +}); module("Appointment popup", moduleConfig, () => { const SECTION_AFTER = "after"; @@ -293,4 +310,66 @@ module("Appointment popup", moduleConfig, () => { this.realDeviceMock.restore(); } }); + + test("The fullscreen mode is enabled of popup when window's width < 768px", function(assert) { + setWindowWidth(767); + + const scheduler = createInstance(); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); + const popup = scheduler.appointmentPopup.getPopupInstance(); + + assert.ok(popup.option("fullScreen"), "The fullscreen mode is enabled"); + assert.equal(popup.option("maxWidth"), "100%", "maxWidth"); + + resetWindowWidth(); + }); + + test("The fullscreen mode is disabled of popup when window's width > 768px", function(assert) { + setWindowWidth(769); + + const scheduler = createInstance(); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); + const popup = scheduler.appointmentPopup.getPopupInstance(); + + assert.notOk(popup.option("fullScreen"), "The fullscreen mode is disabled"); + assert.equal(popup.option("maxWidth"), 610, "maxWidth"); + + resetWindowWidth(); + }); + + test("The fullscreen mode is enabled of popup when the window's width < 768px by resizing the window", function(assert) { + setWindowWidth(769); + + const scheduler = createInstance(); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); + const popup = scheduler.appointmentPopup.getPopupInstance(); + + setWindowWidth(767); + resizeCallbacks.fire(); + + assert.ok(popup.option("fullScreen"), "The fullscreen mode is enabled"); + assert.equal(popup.option("maxWidth"), "100%", "maxWidth"); + + resetWindowWidth(); + }); + + test("The fullscreen mode is disabled of popup when the window's width > 768px by resizing the window", function(assert) { + setWindowWidth(767); + + const scheduler = createInstance(); + scheduler.appointments.compact.click(); + scheduler.tooltip.clickOnItem(); + const popup = scheduler.appointmentPopup.getPopupInstance(); + + setWindowWidth(769); + resizeCallbacks.fire(); + + assert.notOk(popup.option("fullScreen"), "The fullscreen mode is disabled"); + assert.equal(popup.option("maxWidth"), 610, "maxWidth"); + + resetWindowWidth(); + }); }); diff --git a/testing/tests/DevExpress.ui.widgets.scheduler/subscribes.tests.js b/testing/tests/DevExpress.ui.widgets.scheduler/subscribes.tests.js index 13af7f7d42db..83372468e921 100644 --- a/testing/tests/DevExpress.ui.widgets.scheduler/subscribes.tests.js +++ b/testing/tests/DevExpress.ui.widgets.scheduler/subscribes.tests.js @@ -551,26 +551,6 @@ QUnit.test("'resizePopup' should trigger dxresize event for appointment popup", assert.ok(resizeHandler.called, "event has been triggered"); }); -QUnit.test("'resizePopup' should trigger setPopupMaxHeight for appointment popup", function(assert) { - this.createInstance({ - currentDate: new Date(2015, 1, 1), - currentView: "day", - dataSource: [] - }); - - var setPopupMaxHeight = sinon.stub(this.instance, "_setPopupContentMaxHeight"); - - this.instance.fire("showAddAppointmentPopup", { - startDate: new Date(2015, 1, 1), - endDate: new Date(2015, 1, 1, 1), - allDay: true - }); - - this.instance.fire("resizePopup"); - - assert.ok(setPopupMaxHeight.called, "event has been triggered"); -}); - QUnit.test("'appointmentFocused' should fire restoreScrollTop", function(assert) { this.createInstance(); diff --git a/testing/tests/DevExpress.ui.widgets/fileManager.tests.js b/testing/tests/DevExpress.ui.widgets/fileManager.tests.js index 721e73479481..a7829e576756 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManager.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManager.tests.js @@ -12,4 +12,8 @@ import "./fileManagerParts/detailsView.tests.js"; import "./fileManagerParts/toolbar.tests.js"; import "./fileManagerParts/navigation.tests.js"; import "./fileManagerParts/editing.tests.js"; +import "./fileManagerParts/adaptivity.tests.js"; + +import "./fileManagerParts/ajaxProvider.tests.js"; +import "./fileManagerParts/arrayProvider.tests.js"; import "./fileManagerParts/webAPIProvider.tests.js"; diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/adaptivity.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/adaptivity.tests.js new file mode 100644 index 000000000000..8406133185d9 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/adaptivity.tests.js @@ -0,0 +1,111 @@ +import $ from "jquery"; +import renderer from "core/renderer"; +import resizeCallbacks from "core/utils/resize_callbacks"; +import "ui/file_manager"; +import fx from "animation/fx"; +import { FileManagerWrapper, createTestFileSystem } from "../../../helpers/fileManagerHelpers.js"; + +const { test } = QUnit; + +const moduleConfig = { + + beforeEach: function() { + this.clock = sinon.useFakeTimers(); + fx.off = true; + + const that = this; + + this.currentWidth = 400; + this.currentHeight = 300; + + this.originalWidth = renderer.fn.width; + this.originalHeight = renderer.fn.height; + + renderer.fn.width = function() { + if(this[0] && this[0] instanceof Window) { + return that.currentWidth; + } + return that.originalWidth.apply(renderer.fn, arguments); + }; + + renderer.fn.height = function() { + if(this[0] && this[0] instanceof Window) { + return that.currentHeight; + } + return that.originalHeight.apply(renderer.fn, arguments); + }; + + this.$element = $("#fileManager") + .css("width", 350) + .dxFileManager({ + fileProvider: createTestFileSystem(), + permissions: { + create: true, + copy: true, + move: true, + remove: true, + rename: true, + upload: true + } + }); + + this.wrapper = new FileManagerWrapper(this.$element); + + this.clock.tick(400); + }, + + afterEach: function() { + this.clock.tick(5000); + + this.clock.restore(); + fx.off = false; + + renderer.fn.width = this.originalWidth; + renderer.fn.height = this.originalHeight; + } + +}; + +QUnit.module("Adaptivity", moduleConfig, () => { + + test("show dirs button visible on small screen", function(assert) { + let $showDirsButton = this.wrapper.getToolbar().find(".dx-icon-menu:visible"); + assert.equal($showDirsButton.length, 1, "show dirs panel button visible"); + + let folders = this.wrapper.getFolderNodes().filter(":visible"); + assert.ok(folders.length > 3, "dirs tree visible"); + + this.currentWidth = 900; + this.currentHeight = 800; + + resizeCallbacks.fire(); + this.clock.tick(400); + + $showDirsButton = this.wrapper.getToolbar().find(".dx-icon-menu:visible"); + assert.equal($showDirsButton.length, 0, "show dirs panel button hidden"); + + folders = this.wrapper.getFolderNodes().filter(":visible"); + assert.ok(folders.length > 3, "dirs tree visible"); + }); + + test("dialog size corrent on different window size", function(assert) { + this.wrapper.getToolbarButton("Copy").trigger("dxclick"); + this.clock.tick(400); + + let $dialog = $(".dx-filemanager-dialog-folder-chooser:visible"); + assert.equal($dialog.length, 1, "dialog is shown"); + + const dialogWidth = $dialog.get(0).offsetWidth; + const dialogHeight = $dialog.get(0).offsetHeight; + + this.currentWidth = 100; + this.currentHeight = 100; + + resizeCallbacks.fire(); + this.clock.tick(400); + + assert.ok($dialog.get(0).offsetWidth <= dialogWidth, "dialog width decreased"); + assert.ok($dialog.get(0).offsetHeight <= dialogHeight, "dialog height decreased"); + }); + +}); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/ajaxProvider.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/ajaxProvider.tests.js new file mode 100644 index 000000000000..9ee3f406eaa6 --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/ajaxProvider.tests.js @@ -0,0 +1,52 @@ +const { test } = QUnit; +import ajaxMock from "../../../helpers/ajaxMock.js"; + +import AjaxFileProvider from "ui/file_manager/file_provider/ajax"; + +const fileItems = [ + { + name: "F1", + isDirectory: true, + children: [ { name: "File1.1.txt" } ] + }, + { + name: "F2", + isDirectory: true + } +]; + +const moduleConfig = { + beforeEach: function() { + this.options = { + url: "url-to-js-file" + }; + this.provider = new AjaxFileProvider(this.options); + }, + + afterEach: function() { + ajaxMock.clear(); + } +}; + +QUnit.module("Ajax File Provider", moduleConfig, () => { + + test("get folders", function(assert) { + const done = assert.async(); + + ajaxMock.setup({ + url: this.options.url, + responseText: fileItems + }); + + this.provider.getFolders("") + .done(folders => { + assert.equal(folders.length, 2); + assert.equal(folders[0].name, "F1"); + assert.ok(folders[0].isDirectory); + assert.equal(folders[1].name, "F2"); + assert.ok(folders[1].isDirectory); + done(); + }); + }); + +}); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/arrayProvider.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/arrayProvider.tests.js new file mode 100644 index 000000000000..e553ad8ae1df --- /dev/null +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/arrayProvider.tests.js @@ -0,0 +1,89 @@ +const { test } = QUnit; + +import "ui/file_manager"; +import ArrayFileProvider from "ui/file_manager/file_provider/array"; + +const moduleConfig = { + + beforeEach: function() { + this.options = { + data: [ + { + name: "F1", + isDirectory: true, + items: [ + { + name: "F1.1", + isDirectory: true + }, + { + name: "F1.2", + isDirectory: true, + items: [ + { + name: "File1.2.txt" + } + ] + }, + { + name: "F1.3", + isDirectory: true, + items: [ + { + name: "F1.3.1", + isDirectory: true + } + ] + } + ] + }, + { + name: "F2", + isDirectory: true + } + ] + }; + + this.provider = new ArrayFileProvider(this.options); + }, +}; + +QUnit.module("Array File Provider", moduleConfig, () => { + + test("get folders", function(assert) { + let folders = this.provider.getFolders(); + assert.equal(folders.length, 2); + assert.equal(folders[0].name, "F1"); + assert.ok(folders[0].hasSubDirs); + assert.equal(folders[1].name, "F2"); + assert.notOk(folders[1].hasSubDirs); + + folders = this.provider.getFolders("F1"); + assert.equal(3, folders.length); + assert.equal(folders[0].name, "F1.1"); + assert.notOk(folders[0].hasSubDirs); + assert.equal(folders[1].name, "F1.2"); + assert.notOk(folders[1].hasSubDirs); + assert.equal(folders[2].name, "F1.3"); + assert.ok(folders[2].hasSubDirs); + }); + + test("get files", function(assert) { + let files = this.provider.getFiles(); + assert.equal(files.length, 0); + + files = this.provider.getFiles("F1/F1.2"); + assert.equal(files.length, 1); + assert.equal(files[0].name, "File1.2.txt"); + }); + + test("move folder", function(assert) { + let folders = this.provider.getFolders(); + this.provider.moveItems([ folders[0] ], folders[1]); + + folders = this.provider.getFolders(); + assert.equal(folders.length, 1); + assert.ok(folders[0].hasSubDirs); + }); + +}); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/contextMenu.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/contextMenu.tests.js index 60e9f9b0219b..406523fd23c2 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/contextMenu.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/contextMenu.tests.js @@ -186,4 +186,34 @@ QUnit.module("Raise context menu", moduleConfig, () => { assert.ok(this.wrapper.getContextMenuItems(true).length > 0, "context menu is shown"); }); + test('root folder context menu has restricted items', function(assert) { + assert.equal(this.wrapper.getFocusedItemText(), "Files", "root folder selected"); + assert.equal(this.wrapper.getContextMenuItems().length, 0, "context menu is hidden"); + + this.wrapper.getFolderNode(0).trigger("dxcontextmenu"); + this.clock.tick(400); + + const $items = this.wrapper.getContextMenuItems(true); + assert.equal($items.length, 3, "context menu is shown"); + + assert.ok($items.eq(0).text().indexOf("New folder") > -1, "create folder item shown"); + assert.ok($items.eq(1).text().indexOf("Upload files") > -1, "upload files item shown"); + assert.ok($items.eq(2).text().indexOf("Refresh") > -1, "refresh item shown"); + }); + + test('root folder action button menu has restricted items', function(assert) { + assert.equal(this.wrapper.getFocusedItemText(), "Files", "root folder selected"); + assert.equal(this.wrapper.getContextMenuItems().length, 0, "context menu is hidden"); + + this.wrapper.getFolderActionButton(0).trigger("dxclick"); + this.clock.tick(400); + + const $items = this.wrapper.getContextMenuItems(true); + assert.equal($items.length, 3, "context menu is shown"); + + assert.ok($items.eq(0).text().indexOf("New folder") > -1, "create folder item shown"); + assert.ok($items.eq(1).text().indexOf("Upload files") > -1, "upload files item shown"); + assert.ok($items.eq(2).text().indexOf("Refresh") > -1, "refresh item shown"); + }); + }); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js index 587ff7bcb80e..35408fbd5f10 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/detailsView.tests.js @@ -18,29 +18,29 @@ const moduleConfig = { fileProvider: [ { name: "Folder 1", - isFolder: true + isDirectory: true }, { name: "1.txt", - isFolder: false, + isDirectory: false, size: 0, owner: "Admin" }, { name: "2.txt", - isFolder: false, + isDirectory: false, size: 200, owner: "Admin" }, { name: "3.txt", - isFolder: false, + isDirectory: false, size: 1024, owner: "Guest" }, { name: "4.txt", - isFolder: false, + isDirectory: false, size: 1300, owner: "Max" } @@ -80,27 +80,27 @@ QUnit.module("Details View", moduleConfig, () => { }); test("Using custom formats of JSON files", function(assert) { - const fileSystem = [ - { - title: "Folder", - noFolder: true, - myDate: "2/2/2000" - }, - { - title: "Title", - noFolder: false, - myDate: "1/1/2000", - count: 55 - } - ]; - const fileManagerInstance = $("#fileManager").dxFileManager("instance"); - fileManagerInstance.option({ - "fileProvider": fileSystem, - "nameExpr": "title", - "isFolderExpr": "noFolder", - "dateModifiedExpr": "myDate", - "sizeExpr": "count" - }); + $("#fileManager") + .dxFileManager("instance") + .option("fileProvider", { + data: [ + { + title: "Folder", + noFolder: true, + myDate: "2/2/2000" + }, + { + title: "Title", + noFolder: false, + myDate: "1/1/2000", + count: 55 + } + ], + nameExpr: "title", + isDirectoryExpr: "noFolder", + dateModifiedExpr: "myDate", + sizeExpr: "count" + }); this.clock.tick(400); assert.ok(getCellValueInDetailsView(this.$element, 1, 2).indexOf("Folder") === 0); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/editing.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/editing.tests.js index ba6d1a1337bf..3d5485bf60f7 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/editing.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/editing.tests.js @@ -123,6 +123,56 @@ QUnit.module("Editing operations", moduleConfig, () => { assert.equal(this.wrapper.getFocusedItemText(), "Files", "root folder selected"); }); + test("create sub-folder for new folder", function(assert) { + this.$element.dxFileManager("option", { + itemView: { + showParentFolder: true, + showFolders: true + } + }); + this.clock.tick(400); + + this.wrapper.getFolderNodes().eq(2).trigger("dxclick"); + let togglesCount = this.wrapper.getFolderToggles().length; + assert.equal(this.wrapper.getFocusedItemText(), "Folder 2", "sub folder selected"); + assert.ok(togglesCount >= 2, "specfied toggles shown"); + + this.wrapper.getToolbarButton("New folder").trigger("dxclick"); + this.clock.tick(400); + + $(`.${Consts.DIALOG_CLASS} .${Consts.TEXT_EDITOR_INPUT_CLASS}`).val("test 111").trigger("change"); + $(`.${Consts.POPUP_BOTTOM_CLASS} .${Consts.BUTTON_CLASS}:contains('Create')`).trigger("dxclick"); + this.clock.tick(400); + + let $cell = this.wrapper.findDetailsItem("test 111"); + assert.equal($cell.length, 1, "new folder created"); + assert.equal(this.wrapper.getFolderToggles().length, ++togglesCount, "new folder toggle shown"); + + $cell.trigger("dxdblclick"); + this.clock.tick(400); + + assert.equal(this.wrapper.getFocusedItemText(), "test 111", "new folder selected"); + assert.equal(this.wrapper.getFolderToggles().length, togglesCount, "toggle count is not changed"); + + this.wrapper.getToolbarButton("New folder").trigger("dxclick"); + this.clock.tick(400); + + $(`.${Consts.DIALOG_CLASS} .${Consts.TEXT_EDITOR_INPUT_CLASS}`).val("test 222").trigger("change"); + $(`.${Consts.POPUP_BOTTOM_CLASS} .${Consts.BUTTON_CLASS}:contains('Create')`).trigger("dxclick"); + this.clock.tick(400); + + $cell = this.wrapper.findDetailsItem("test 222"); + assert.equal($cell.length, 1, "new folder created"); + assert.equal(this.wrapper.getFolderToggles().length, ++togglesCount, "new folder toggle shown"); + + $cell.trigger("dxdblclick"); + this.clock.tick(400); + + assert.equal(this.wrapper.getFocusedItemText(), "test 222", "new folder selected"); + assert.equal(this.wrapper.getBreadcrumbsPath(), "Files/Folder 2/test 111/test 222", "correct path shown"); + assert.equal(this.wrapper.getFolderToggles().length, togglesCount, "toggle count is not changed"); + }); + test("delete folder in folders area", function(assert) { let $folderNodes = this.wrapper.getFolderNodes(); const initialCount = $folderNodes.length; @@ -167,6 +217,34 @@ QUnit.module("Editing operations", moduleConfig, () => { assert.equal(this.wrapper.getFocusedItemText(), "Files", "root folder selected"); }); + test("delete file from subfolder in items area", function(assert) { + this.wrapper.getFolderNodes().eq(1).trigger("dxclick"); + this.clock.tick(400); + + assert.equal(this.wrapper.getFocusedItemText(), "Folder 1", "sub folder selected"); + + let $rows = this.$element.find(`.${Consts.GRID_DATA_ROW_CLASS}`); + const initialCount = $rows.length; + + const $cell = $rows.find("td").eq(1); + assert.equal($cell.get(0).childNodes[0].textContent, "File 1-1.txt", "has target file"); + + $cell.trigger("dxclick"); + this.$element.find(`.${Consts.ITEMS_GRID_VIEW_CLASS}`).trigger("click"); + this.clock.tick(400); + + const $commandButton = this.$element.find(`.${Consts.TOOLBAR_CLASS} .${Consts.BUTTON_CLASS}:contains('Delete')`); + $commandButton.trigger("dxclick"); + this.clock.tick(400); + + $rows = this.$element.find(`.${Consts.GRID_DATA_ROW_CLASS}`); + assert.equal($rows.length, initialCount - 1, "files count decreased"); + assert.ok($rows.eq(0).text().indexOf("File 1-1.txt") === -1, "first folder is not target folder"); + assert.ok($rows.eq(1).text().indexOf("File 1-1.txt") === -1, "second folder is not target folder"); + + assert.equal(this.wrapper.getFocusedItemText(), "Folder 1", "sub folder selected"); + }); + test("move folder in folders area", function(assert) { let $folderNodes = this.wrapper.getFolderNodes(); const initialCount = $folderNodes.length; @@ -195,7 +273,7 @@ QUnit.module("Editing operations", moduleConfig, () => { assert.equal($folderNodes.eq(2).find("span").text(), "Folder 3", "second folder is not target folder"); const $folderToggles = this.wrapper.getFolderToggles(); - $folderToggles.eq(2).trigger("dxclick"); + $folderToggles.eq(1).trigger("dxclick"); this.clock.tick(400); $folderNodes = this.wrapper.getFolderNodes(); @@ -236,7 +314,7 @@ QUnit.module("Editing operations", moduleConfig, () => { assert.equal($folderNodes.eq(2).find("span").text(), "Folder 2", "second folder is not target folder"); const $folderToggles = this.wrapper.getFolderToggles(); - $folderToggles.eq(3).trigger("dxclick"); + $folderToggles.eq(2).trigger("dxclick"); this.clock.tick(400); $folderNodes = this.wrapper.getFolderNodes(); diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/markup.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/markup.tests.js index 5f18768a1e70..73b99b82786d 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/markup.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/markup.tests.js @@ -51,7 +51,7 @@ QUnit.module("Markup rendering", moduleConfig, () => { this.prepareFileManager({ customizeThumbnail: item => { - if(item.isFolder) { + if(item.isDirectory) { return ""; } counter++; diff --git a/testing/tests/DevExpress.ui.widgets/fileManagerParts/webAPIProvider.tests.js b/testing/tests/DevExpress.ui.widgets/fileManagerParts/webAPIProvider.tests.js index fa1ab1882f87..0d245d285c75 100644 --- a/testing/tests/DevExpress.ui.widgets/fileManagerParts/webAPIProvider.tests.js +++ b/testing/tests/DevExpress.ui.widgets/fileManagerParts/webAPIProvider.tests.js @@ -9,21 +9,24 @@ import { deserializeDate } from "core/utils/date_serialization"; const { test } = QUnit; const createFileManagerItem = (parentPath, dataObj) => { - let item = new FileManagerItem(parentPath, dataObj.name, dataObj.isFolder); + let item = new FileManagerItem(parentPath, dataObj.name, dataObj.isDirectory); item.dateModified = deserializeDate(dataObj.dateModified); item.size = dataObj.size; item.dataItem = dataObj; + if(dataObj.isDirectory) { + item.hasSubDirs = true; + } return item; }; const FOLDER_COUNT = 3; const itemData = [ - { id: "Root\\Files\\Documents", name: "Documents", dateModified: "2019-02-14T07:44:15.4265625Z", isFolder: true, size: 0 }, - { id: "Root\\Files\\Images", name: "Images", dateModified: "2019-02-14T07:44:15.4885105Z", isFolder: true, size: 0 }, - { id: "Root\\Files\\Music", name: "Music", dateModified: "2019-02-14T07:44:15.4964648Z", isFolder: true, size: 0 }, - { id: "Root\\Files\\Description.rtf", name: "Description.rtf", dateModified: "2017-02-09T09:38:46.3772529Z", isFolder: false, size: 1 }, - { id: "Root\\Files\\Article.txt", name: "Article.txt", dateModified: "2017-02-09T09:38:46.3772529Z", isFolder: false, size: 1 } + { id: "Root\\Files\\Documents", name: "Documents", dateModified: "2019-02-14T07:44:15.4265625Z", isDirectory: true, size: 0 }, + { id: "Root\\Files\\Images", name: "Images", dateModified: "2019-02-14T07:44:15.4885105Z", isDirectory: true, size: 0 }, + { id: "Root\\Files\\Music", name: "Music", dateModified: "2019-02-14T07:44:15.4964648Z", isDirectory: true, size: 0 }, + { id: "Root\\Files\\Description.rtf", name: "Description.rtf", dateModified: "2017-02-09T09:38:46.3772529Z", isDirectory: false, size: 1 }, + { id: "Root\\Files\\Article.txt", name: "Article.txt", dateModified: "2017-02-09T09:38:46.3772529Z", isDirectory: false, size: 1 } ]; const fileManagerItems = [ @@ -63,7 +66,8 @@ QUnit.module("Web API Provider", moduleConfig, () => { responseText: { result: itemData, success: true - } + }, + callback: request => assert.equal(request.method, "GET") }); this.provider.getFolders("Root/Files") @@ -81,7 +85,8 @@ QUnit.module("Web API Provider", moduleConfig, () => { responseText: { success: true, result: itemData - } + }, + callback: request => assert.equal(request.method, "GET") }); this.provider.getFiles("Root/Files") @@ -98,7 +103,8 @@ QUnit.module("Web API Provider", moduleConfig, () => { url: this.options.endpointUrl + "?command=CreateDir&arguments=%7B%22parentId%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Test%201%22%7D", responseText: { success: true - } + }, + callback: request => assert.equal(request.method, "POST") }); const parentFolder = new FileManagerItem("Root/Files", "Documents"); @@ -116,7 +122,8 @@ QUnit.module("Web API Provider", moduleConfig, () => { url: this.options.endpointUrl + "?command=Rename&arguments=%7B%22id%22%3A%22Root%2FFiles%2FDocuments%22%2C%22name%22%3A%22Test%201%22%7D", responseText: { success: true - } + }, + callback: request => assert.equal(request.method, "POST") }); const item = new FileManagerItem("Root/Files", "Documents"); @@ -134,7 +141,8 @@ QUnit.module("Web API Provider", moduleConfig, () => { url: this.options.endpointUrl + "?command=Remove&arguments=%7B%22id%22%3A%22Root%2FFiles%2FDocuments%22%7D", responseText: { success: true - } + }, + callback: request => assert.equal(request.method, "POST") }); const item = new FileManagerItem("Root/Files", "Documents"); @@ -153,7 +161,8 @@ QUnit.module("Web API Provider", moduleConfig, () => { url: this.options.endpointUrl + "?command=Move&arguments=%7B%22sourceId%22%3A%22Root%2FFiles%2FDocuments%22%2C%22destinationId%22%3A%22Root%2FFiles%2FImages%2FDocuments%22%7D", responseText: { success: true - } + }, + callback: request => assert.equal(request.method, "POST") }); const item = new FileManagerItem("Root/Files", "Documents"); @@ -170,10 +179,11 @@ QUnit.module("Web API Provider", moduleConfig, () => { const done = assert.async(); ajaxMock.setup({ - url: this.options.endpointUrl + "?command=Copy&arguments=%7B%22sourceId%22%3A%22Root%2FFiles%2FDocuments%22%2C%22destinationId%22%3A%22Root%2FFiles%2FImages%22%7D", + url: this.options.endpointUrl + "?command=Copy&arguments=%7B%22sourceId%22%3A%22Root%2FFiles%2FDocuments%22%2C%22destinationId%22%3A%22Root%2FFiles%2FImages%2FDocuments%22%7D", responseText: { success: true - } + }, + callback: request => assert.equal(request.method, "POST") }); const item = new FileManagerItem("Root/Files", "Documents"); diff --git a/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js b/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js index dd0da396bbc6..c84e442259fc 100644 --- a/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js +++ b/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js @@ -2485,6 +2485,31 @@ QUnit.module("keyboard navigation", { assert.ok($firstItem.hasClass("dx-state-focused"), "first item is focused"); }); + QUnit.test("focusing next item after 'down' pressing if selectAll checkbox is focused", assert => { + if(!isDeviceDesktop(assert)) { + return; + } + assert.expect(2); + + const $element = $("#list").dxList({ + showSelectionControls: true, + selectionMode: "all", + focusStateEnabled: true, + items: [0, 1, 2, 3, 4] + }); + + const $selectAllCheckBox = $element.find(".dx-list-select-all-checkbox"); + const keyboard = keyboardMock($selectAllCheckBox); + const $firstItem = $element.find(toSelector(LIST_ITEM_CLASS)).eq(0); + + $selectAllCheckBox.trigger("focusin"); + keyboard.keyDown("down"); + this.clock.tick(); + + assert.notOk($selectAllCheckBox.hasClass("dx-state-focused"), "selectAll checkbox is not focused"); + assert.ok($firstItem.hasClass("dx-state-focused"), "first item is focused"); + }); + QUnit.test("list does not scroll to item after click on it", assert => { assert.expect(2); diff --git a/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js b/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js index 619cd88798d5..d3ae45f24293 100644 --- a/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js +++ b/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js @@ -1540,6 +1540,23 @@ QUnit.test("Logarithmic ticks adjusting", function(assert) { assert.deepEqual(this.axis._majorTicks.map(value), [1e-9, 1e-8, 1e-7]); }); +QUnit.test("Values adjusting", function(assert) { + this.createAxis(); + this.updateOptions({ + argumentType: "numeric", + type: "logarithmic", + logarithmBase: 10, + tickInterval: 2 + }); + + this.axis.setBusinessRange({ min: 0.0001, max: 10 }); + + // act + this.axis.createTicks(canvas(450)); + + assert.strictEqual(this.axis._majorTicks[0].value, 0.0001); +}); + QUnit.module("Logarithmic. Minor ticks", environment); QUnit.test("minorTickInterval as exponent, but ticks not", function(assert) { diff --git a/ts/dx.all.d.ts b/ts/dx.all.d.ts index 7a344583a372..74037522f38e 100644 --- a/ts/dx.all.d.ts +++ b/ts/dx.all.d.ts @@ -560,41 +560,25 @@ interface JQuery { /* #EndJQueryAugmentation */ declare module DevExpress.ui { /** @name AjaxFileProvider.Options */ - export interface AjaxFileProviderOptions { - /** @name AjaxFileProvider.Options.dateModifiedExpr */ - dateModifiedExpr?: string | Function; - /** @name AjaxFileProvider.Options.isFolderExpr */ - isFolderExpr?: string | Function; - /** @name AjaxFileProvider.Options.nameExpr */ - nameExpr?: string | Function; - /** @name AjaxFileProvider.Options.sizeExpr */ - sizeExpr?: string | Function; - /** @name AjaxFileProvider.Options.thumbnailExpr */ - thumbnailExpr?: string | Function; + export interface AjaxFileProviderOptions extends FileProviderOptions { + /** @name AjaxFileProvider.Options.itemsExpr */ + itemsExpr?: string | Function; /** @name AjaxFileProvider.Options.url */ url?: string; } /** @name AjaxFileProvider */ - export class AjaxFileProvider { + export class AjaxFileProvider extends FileProvider { constructor(options?: AjaxFileProviderOptions) } /** @name ArrayFileProvider.Options */ - export interface ArrayFileProviderOptions { + export interface ArrayFileProviderOptions extends FileProviderOptions { /** @name ArrayFileProvider.Options.data */ data?: Array; - /** @name ArrayFileProvider.Options.dateModifiedExpr */ - dateModifiedExpr?: string | Function; - /** @name ArrayFileProvider.Options.isFolderExpr */ - isFolderExpr?: string | Function; - /** @name ArrayFileProvider.Options.nameExpr */ - nameExpr?: string | Function; - /** @name ArrayFileProvider.Options.sizeExpr */ - sizeExpr?: string | Function; - /** @name ArrayFileProvider.Options.thumbnailExpr */ - thumbnailExpr?: string | Function; + /** @name ArrayFileProvider.Options.itemsExpr */ + itemsExpr?: string | Function; } /** @name ArrayFileProvider */ - export class ArrayFileProvider { + export class ArrayFileProvider extends FileProvider { constructor(options?: ArrayFileProviderOptions) } /** @name ColCountResponsible */ @@ -710,21 +694,6 @@ declare module DevExpress.ui { /** @name DataHelperMixin.getDataSource() */ getDataSource(): DevExpress.data.DataSource; } - /** @name DiagramCustomShapeItem */ - export interface DiagramCustomShapeItem { - /** @name DiagramCustomShapeItem.allowHasText */ - allowHasText?: boolean; - /** @name DiagramCustomShapeItem.defaultHeight */ - defaultHeight?: number; - /** @name DiagramCustomShapeItem.defaultWidth */ - defaultWidth?: number; - /** @name DiagramCustomShapeItem.id */ - id?: number; - /** @name DiagramCustomShapeItem.svgUrl */ - svgUrl?: string; - /** @name DiagramCustomShapeItem.title */ - title?: string; - } /** @name DiagramDataSourceParameters */ export interface DiagramDataSourceParameters { /** @name DiagramDataSourceParameters.edges */ @@ -769,8 +738,23 @@ declare module DevExpress.ui { /** @name EmailRule.type */ type?: 'required' | 'numeric' | 'range' | 'stringLength' | 'custom' | 'compare' | 'pattern' | 'email'; } + /** @name FileProvider.Options */ + export interface FileProviderOptions { + /** @name FileProvider.Options.dateModifiedExpr */ + dateModifiedExpr?: string | Function; + /** @name FileProvider.Options.isDirectoryExpr */ + isDirectoryExpr?: string | Function; + /** @name FileProvider.Options.nameExpr */ + nameExpr?: string | Function; + /** @name FileProvider.Options.sizeExpr */ + sizeExpr?: string | Function; + /** @name FileProvider.Options.thumbnailExpr */ + thumbnailExpr?: string | Function; + } /** @name FileProvider */ - export type FileProvider = any; + export class FileProvider { + constructor(options?: FileProviderOptions) + } /** @name GridBase.Options */ export interface GridBaseOptions extends WidgetOptions { /** @name GridBase.Options.allowColumnReordering */ @@ -1275,7 +1259,8 @@ declare module DevExpress.ui { type?: 'required' | 'numeric' | 'range' | 'stringLength' | 'custom' | 'compare' | 'pattern' | 'email'; } /** @name OneDriveFileProvider */ - export type OneDriveFileProvider = any; + export interface OneDriveFileProvider extends FileProvider { + } /** @name PatternRule */ export interface PatternRule { /** @name PatternRule.ignoreEmptyValue */ @@ -1346,22 +1331,14 @@ declare module DevExpress.ui { type?: 'required' | 'numeric' | 'range' | 'stringLength' | 'custom' | 'compare' | 'pattern' | 'email'; } /** @name WebApiFileProvider.Options */ - export interface WebApiFileProviderOptions { - /** @name WebApiFileProvider.Options.dateModifiedExpr */ - dateModifiedExpr?: string | Function; + export interface WebApiFileProviderOptions extends FileProviderOptions { /** @name WebApiFileProvider.Options.endpointUrl */ endpointUrl?: string; - /** @name WebApiFileProvider.Options.isFolderExpr */ - isFolderExpr?: string | Function; - /** @name WebApiFileProvider.Options.nameExpr */ - nameExpr?: string | Function; - /** @name WebApiFileProvider.Options.sizeExpr */ - sizeExpr?: string | Function; - /** @name WebApiFileProvider.Options.thumbnailExpr */ - thumbnailExpr?: string | Function; + /** @name WebApiFileProvider.Options.hasSubDirectoriesExpr */ + hasSubDirectoriesExpr?: string | Function; } /** @name WebApiFileProvider */ - export class WebApiFileProvider { + export class WebApiFileProvider extends FileProvider { constructor(options?: WebApiFileProviderOptions) } /** @name Widget.Options */ @@ -2010,7 +1987,7 @@ declare module DevExpress.ui { /** @name dxDiagram.Options */ export interface dxDiagramOptions extends WidgetOptions { /** @name dxDiagram.Options.customShapes */ - customShapes?: Array; + customShapes?: Array<{ allowHasText?: boolean, defaultHeight?: number, defaultWidth?: number, id?: number, svgUrl?: string, title?: string }>; /** @name dxDiagram.Options.edges */ edges?: { dataSource?: Array | DevExpress.data.DataSource | DevExpress.data.DataSourceOptions, fromExpr?: string | ((data: any) => any), keyExpr?: string | ((data: any) => any), toExpr?: string | ((data: any) => any) }; /** @name dxDiagram.Options.export */ @@ -2032,8 +2009,8 @@ declare module DevExpress.ui { deleteDataSource(key: string): void; /** @name dxDiagram.getData() */ getData(): string; - /** @name dxDiagram.setData(data, keepExistingItems) */ - setData(data: string, keepExistingItems: boolean): void; + /** @name dxDiagram.setData(data, updateExistingItemsOnly) */ + setData(data: string, updateExistingItemsOnly: boolean): void; } /** @name dxDrawer.Options */ export interface dxDrawerOptions extends WidgetOptions { @@ -2167,6 +2144,8 @@ declare module DevExpress.ui { activeStateEnabled?: boolean; /** @name dxDropDownEditor.Options.applyValueMode */ applyValueMode?: 'instantly' | 'useButtons'; + /** @name dxDropDownEditor.Options.buttons */ + buttons?: Array<'clear' | 'dropDown' | dxTextEditorButton>; /** @name dxDropDownEditor.Options.deferRendering */ deferRendering?: boolean; /** @name dxDropDownEditor.Options.dropDownButtonTemplate */ @@ -2285,26 +2264,16 @@ declare module DevExpress.ui { customizeDetailColumns?: ((columns: Array) => Array); /** @name dxFileManager.Options.customizeThumbnail */ customizeThumbnail?: ((fileItem: any) => string); - /** @name dxFileManager.Options.dateModifiedExpr */ - dateModifiedExpr?: string | ((fileItem: any) => any); /** @name dxFileManager.Options.fileProvider */ fileProvider?: any; - /** @name dxFileManager.Options.isFolderExpr */ - isFolderExpr?: string | ((fileItem: any) => any); /** @name dxFileManager.Options.itemView */ itemView?: { mode?: 'details' | 'thumbnails', showFolders?: boolean, showParentFolder?: boolean }; - /** @name dxFileManager.Options.nameExpr */ - nameExpr?: string | ((fileItem: any) => any); /** @name dxFileManager.Options.onSelectedFileOpened */ onSelectedFileOpened?: ((e: { component?: dxFileManager, element?: DevExpress.core.dxElement, model?: any, fileItem?: any }) => any); /** @name dxFileManager.Options.permissions */ permissions?: { copy?: boolean, create?: boolean, move?: boolean, remove?: boolean, rename?: boolean, upload?: boolean }; /** @name dxFileManager.Options.selectionMode */ selectionMode?: 'multiple' | 'single'; - /** @name dxFileManager.Options.sizeExpr */ - sizeExpr?: string | ((fileItem: any) => any); - /** @name dxFileManager.Options.thumbnailExpr */ - thumbnailExpr?: string | ((fileItem: any) => any); } /** @name dxFileManager */ export class dxFileManager extends Widget { @@ -3285,6 +3254,8 @@ declare module DevExpress.ui { } /** @name dxNumberBox.Options */ export interface dxNumberBoxOptions extends dxTextEditorOptions { + /** @name dxNumberBox.Options.buttons */ + buttons?: Array<'clear' | 'spins' | dxTextEditorButton>; /** @name dxNumberBox.Options.format */ format?: format; /** @name dxNumberBox.Options.invalidValueMessage */ @@ -3646,7 +3617,7 @@ declare module DevExpress.ui { /** @name dxPopup.Options.toolbarItems.visible */ visible?: boolean; /** @name dxPopup.Options.toolbarItems.widget */ - widget?: 'dxAutocomplete' | 'dxButton' | 'dxCheckBox' | 'dxDateBox' | 'dxMenu' | 'dxSelectBox' | 'dxTabs' | 'dxTextBox' | 'dxButtonGroup'; + widget?: 'dxAutocomplete' | 'dxButton' | 'dxCheckBox' | 'dxDateBox' | 'dxMenu' | 'dxSelectBox' | 'dxTabs' | 'dxTextBox' | 'dxButtonGroup' | 'dxDropDownButton'; } /** @name dxPopup */ export class dxPopup extends dxOverlay { @@ -4386,7 +4357,7 @@ declare module DevExpress.ui { /** @name dxTextEditor.Options */ export interface dxTextEditorOptions extends EditorOptions { /** @name dxTextEditor.Options.buttons */ - buttons?: Array<'clear' | 'spins' | 'dropDown' | dxTextEditorButton>; + buttons?: Array; /** @name dxTextEditor.Options.focusStateEnabled */ focusStateEnabled?: boolean; /** @name dxTextEditor.Options.hoverStateEnabled */ @@ -4573,7 +4544,7 @@ declare module DevExpress.ui { /** @name dxToolbarItem.showText */ showText?: 'always' | 'inMenu'; /** @name dxToolbarItem.widget */ - widget?: 'dxAutocomplete' | 'dxButton' | 'dxCheckBox' | 'dxDateBox' | 'dxMenu' | 'dxSelectBox' | 'dxTabs' | 'dxTextBox' | 'dxButtonGroup'; + widget?: 'dxAutocomplete' | 'dxButton' | 'dxCheckBox' | 'dxDateBox' | 'dxMenu' | 'dxSelectBox' | 'dxTabs' | 'dxTextBox' | 'dxButtonGroup' | 'dxDropDownButton'; } /** @name dxTooltip.Options */ export interface dxTooltipOptions extends dxPopoverOptions {