UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

672 lines (671 loc) • 27.9 kB
/** * DevExtreme (esm/ui/file_manager/file_items_controller.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import FileSystemProviderBase from "../../file_management/provider_base"; import FileSystemItem from "../../file_management/file_system_item"; import ObjectFileSystemProvider from "../../file_management/object_provider"; import RemoteFileSystemProvider from "../../file_management/remote_provider"; import CustomFileSystemProvider from "../../file_management/custom_provider"; import FileSystemError from "../../file_management/error"; import ErrorCode from "../../file_management/error_codes"; import { pathCombine, getEscapedFileName, getPathParts, getFileExtension } from "../../file_management/utils"; import { whenSome } from "./ui.file_manager.common"; import { Deferred, when } from "../../core/utils/deferred"; import { find } from "../../core/utils/array"; import { extend } from "../../core/utils/extend"; import { equalByValue } from "../../core/utils/common"; import { isDefined } from "../../core/utils/type"; var DEFAULT_ROOT_FILE_SYSTEM_ITEM_NAME = "Files"; export default class FileItemsController { constructor(options) { options = options || {}; this._options = extend({}, options); this._isInitialized = false; this._dataLoading = false; this._dataLoadingDeferred = null; this._rootDirectoryInfo = this._createRootDirectoryInfo(options.rootText); this._currentDirectoryInfo = this._rootDirectoryInfo; this._defaultIconMap = this._createDefaultIconMap(); this._setSecurityController(); this._setProvider(options.fileProvider); this._initialize() } _setSecurityController() { this._securityController = new FileSecurityController({ allowedFileExtensions: this._options.allowedFileExtensions, maxFileSize: this._options.uploadMaxFileSize }); this._resetState() } setAllowedFileExtensions(allowedFileExtensions) { if (isDefined(allowedFileExtensions)) { this._options.allowedFileExtensions = allowedFileExtensions } this._setSecurityController(); this.refresh() } setUploadOptions(_ref) { var { maxFileSize: maxFileSize, chunkSize: chunkSize } = _ref; if (isDefined(chunkSize)) { this._options.uploadChunkSize = chunkSize } if (isDefined(maxFileSize)) { this._options.uploadMaxFileSize = maxFileSize; this._setSecurityController(); this.refresh() } } _setProvider(fileProvider) { this._fileProvider = this._createFileProvider(fileProvider); this._resetState() } updateProvider(fileProvider, currentPath) { this._resetCurrentDirectory(); this._setProvider(fileProvider); return this.refresh().then(() => this.setCurrentPath(currentPath)) } _createFileProvider(fileProvider) { if (!fileProvider) { fileProvider = [] } if (Array.isArray(fileProvider)) { return new ObjectFileSystemProvider({ data: fileProvider }) } if (fileProvider instanceof FileSystemProviderBase) { return fileProvider } switch (fileProvider.type) { case "remote": return new RemoteFileSystemProvider(fileProvider); case "custom": return new CustomFileSystemProvider(fileProvider) } return new ObjectFileSystemProvider(fileProvider) } setCurrentPath(path) { var pathParts = getPathParts(path); var rawPath = pathCombine(...pathParts); if (this.getCurrentDirectory().fileItem.relativeName === rawPath) { return (new Deferred).resolve().promise() } return this._setCurrentDirectoryByPathParts(pathParts) } setCurrentPathByKeys(pathKeys) { if (equalByValue(this.getCurrentDirectory().fileItem.pathKeys, pathKeys, 0, true)) { return } return this._setCurrentDirectoryByPathParts(pathKeys, true) } getCurrentPath() { var currentPath = ""; var directory = this.getCurrentDirectory(); while (directory && !directory.fileItem.isRoot()) { var escapedName = getEscapedFileName(directory.fileItem.name); currentPath = pathCombine(escapedName, currentPath); directory = directory.parentDirectory } return currentPath } getCurrentDirectory() { return this._currentDirectoryInfo } setCurrentDirectory(directoryInfo, checkActuality) { if (!directoryInfo) { return } if (checkActuality) { directoryInfo = this._getActualDirectoryInfo(directoryInfo) } if (this._currentDirectoryInfo && this._currentDirectoryInfo === directoryInfo) { return } var requireRaiseSelectedDirectory = this._currentDirectoryInfo.fileItem.key !== directoryInfo.fileItem.key; this._currentDirectoryInfo = directoryInfo; if (requireRaiseSelectedDirectory && this._isInitialized) { if (!this._dataLoading) { this._raiseDataLoading("navigation") } this._raiseSelectedDirectoryChanged(directoryInfo) } } _resetCurrentDirectory() { this._currentDirectoryInfo = this._rootDirectoryInfo } getCurrentItems(onlyFiles) { return this._dataLoadingDeferred ? this._dataLoadingDeferred.then(() => this._getCurrentItemsInternal(onlyFiles)) : this._getCurrentItemsInternal(onlyFiles) } _getCurrentItemsInternal(onlyFiles) { var currentDirectory = this.getCurrentDirectory(); var getItemsPromise = this.getDirectoryContents(currentDirectory); return getItemsPromise.then(items => { var separatedItems = this._separateItemsByType(items); currentDirectory.fileItem.hasSubDirectories = !!separatedItems.folders.length; return onlyFiles ? separatedItems.files : items }) } getDirectories(parentDirectoryInfo, skipNavigationOnError) { return this.getDirectoryContents(parentDirectoryInfo, skipNavigationOnError).then(itemInfos => itemInfos.filter(info => info.fileItem.isDirectory)) } _separateItemsByType(itemInfos) { var folders = []; var files = []; itemInfos.forEach(info => info.fileItem.isDirectory ? folders.push(info) : files.push(info)); return { folders: folders, files: files } } getDirectoryContents(parentDirectoryInfo, skipNavigationOnError) { if (!parentDirectoryInfo) { return (new Deferred).resolve([this._rootDirectoryInfo]).promise() } if (parentDirectoryInfo.itemsLoaded) { return (new Deferred).resolve(parentDirectoryInfo.items).promise() } var dirKey = parentDirectoryInfo.getInternalKey(); var loadItemsDeferred = this._loadedItems[dirKey]; if (loadItemsDeferred) { return loadItemsDeferred } loadItemsDeferred = this._getFileItems(parentDirectoryInfo, skipNavigationOnError).then(fileItems => { fileItems = fileItems || []; parentDirectoryInfo.items = fileItems.map(fileItem => fileItem.isDirectory && this._createDirectoryInfo(fileItem, parentDirectoryInfo) || this._createFileInfo(fileItem, parentDirectoryInfo)); parentDirectoryInfo.itemsLoaded = true; return parentDirectoryInfo.items }); this._loadedItems[dirKey] = loadItemsDeferred; loadItemsDeferred.always(() => { delete this._loadedItems[dirKey] }); return loadItemsDeferred } _getFileItems(parentDirectoryInfo, skipNavigationOnError) { var loadItemsDeferred = null; try { loadItemsDeferred = this._fileProvider.getItems(parentDirectoryInfo.fileItem) } catch (error) { return this._handleItemLoadError(parentDirectoryInfo, error, skipNavigationOnError) } return when(loadItemsDeferred).then(fileItems => this._securityController.getAllowedItems(fileItems), errorInfo => this._handleItemLoadError(parentDirectoryInfo, errorInfo, skipNavigationOnError)) } createDirectory(parentDirectoryInfo, name) { var tempDirInfo = this._createDirInfoByName(name, parentDirectoryInfo); var actionInfo = this._createEditActionInfo("create", tempDirInfo, parentDirectoryInfo); return this._processEditAction(actionInfo, () => this._fileProvider.createDirectory(parentDirectoryInfo.fileItem, name).done(info => { if (!parentDirectoryInfo.fileItem.isRoot()) { parentDirectoryInfo.fileItem.hasSubDirectories = true } return info }), () => this._resetDirectoryState(parentDirectoryInfo, true)) } renameItem(fileItemInfo, name) { var actionInfo = this._createEditActionInfo("rename", fileItemInfo, fileItemInfo.parentDirectory, { itemNewName: name }); return this._processEditAction(actionInfo, () => { if (!fileItemInfo.fileItem.isDirectory) { this._securityController.validateExtension(name) } return this._fileProvider.renameItem(fileItemInfo.fileItem, name) }, () => { var parentDirectory = this._getActualDirectoryInfo(fileItemInfo.parentDirectory); this._resetDirectoryState(parentDirectory); this.setCurrentDirectory(parentDirectory) }) } moveItems(itemInfos, destinationDirectory) { var items = itemInfos.map(i => i.fileItem); var actionInfo = this._createEditActionInfo("move", itemInfos, destinationDirectory); return this._processEditAction(actionInfo, () => this._fileProvider.moveItems(items, destinationDirectory.fileItem), () => { destinationDirectory = this._getActualDirectoryInfo(destinationDirectory); itemInfos.forEach(itemInfo => this._resetDirectoryState(itemInfo.parentDirectory, true)); this._resetDirectoryState(destinationDirectory); this.setCurrentDirectory(destinationDirectory); destinationDirectory.expanded = true }) } copyItems(itemInfos, destinationDirectory) { var items = itemInfos.map(i => i.fileItem); var actionInfo = this._createEditActionInfo("copy", itemInfos, destinationDirectory); return this._processEditAction(actionInfo, () => this._fileProvider.copyItems(items, destinationDirectory.fileItem), () => { destinationDirectory = this._getActualDirectoryInfo(destinationDirectory); this._resetDirectoryState(destinationDirectory); this.setCurrentDirectory(destinationDirectory); destinationDirectory.expanded = true }) } deleteItems(itemInfos) { var items = itemInfos.map(i => i.fileItem); var directory = itemInfos.length > 0 ? itemInfos[0].parentDirectory : null; var actionInfo = this._createEditActionInfo("delete", itemInfos, directory); return this._processEditAction(actionInfo, () => this._fileProvider.deleteItems(items), () => { itemInfos.forEach(itemInfo => { var parentDir = this._getActualDirectoryInfo(itemInfo.parentDirectory); this._resetDirectoryState(parentDir); this.setCurrentDirectory(parentDir) }) }) } processUploadSession(sessionInfo, uploadDirectoryInfo) { var itemInfos = this._getItemInfosForUploaderFiles(sessionInfo.files, uploadDirectoryInfo); var actionInfo = this._createEditActionInfo("upload", itemInfos, uploadDirectoryInfo, { sessionInfo: sessionInfo }); return this._processEditAction(actionInfo, () => sessionInfo.deferreds, () => this._resetDirectoryState(uploadDirectoryInfo, true)) } uploadFileChunk(fileData, chunksInfo, destinationDirectory) { this._securityController.validateMaxFileSize(fileData.size); this._securityController.validateExtension(fileData.name); return when(this._fileProvider.uploadFileChunk(fileData, chunksInfo, destinationDirectory)) } abortFileUpload(fileData, chunksInfo, destinationDirectory) { return when(this._fileProvider.abortFileUpload(fileData, chunksInfo, destinationDirectory)) } getFileUploadChunkSize() { var chunkSize = this._options.uploadChunkSize; if (chunkSize && chunkSize > 0) { return chunkSize } return this._fileProvider.getFileUploadChunkSize() } downloadItems(itemInfos) { var items = itemInfos.map(i => i.fileItem); this._fileProvider.downloadItems(items) } getItemContent(itemInfos) { var items = itemInfos.map(i => i.fileItem); return when(this._fileProvider.getItemsContent(items)) } _handleItemLoadError(parentDirectoryInfo, errorInfo, skipNavigationOnError) { parentDirectoryInfo = this._getActualDirectoryInfo(parentDirectoryInfo); var actionInfo = this._createEditActionInfo("getItems", parentDirectoryInfo, parentDirectoryInfo); this._raiseEditActionStarting(actionInfo); this._raiseEditActionResultAcquired(actionInfo); this._raiseEditActionError(actionInfo, { errorCode: errorInfo.errorCode, errorText: errorInfo.errorText, fileItem: parentDirectoryInfo.fileItem, index: 0 }); this._resetDirectoryState(parentDirectoryInfo); parentDirectoryInfo.expanded = false; if (!skipNavigationOnError) { this.setCurrentDirectory(parentDirectoryInfo.parentDirectory) } return (new Deferred).reject().promise() } _processEditAction(actionInfo, action, completeAction) { var actionResult = null; this._raiseEditActionStarting(actionInfo); try { actionResult = action() } catch (errorInfo) { this._raiseEditActionError(actionInfo, errorInfo); return (new Deferred).reject().promise() } if (!Array.isArray(actionResult)) { actionResult = [actionResult] } else if (actionResult.length > 1) { actionInfo.singleRequest = false } this._raiseEditActionResultAcquired(actionInfo); return whenSome(actionResult, info => this._raiseCompleteEditActionItem(actionInfo, info), errorInfo => this._raiseEditActionItemError(actionInfo, errorInfo)).then(() => { completeAction(); this._raiseCompleteEditAction(actionInfo) }) } _createEditActionInfo(name, targetItemInfos, directory, customData) { targetItemInfos = Array.isArray(targetItemInfos) ? targetItemInfos : [targetItemInfos]; customData = customData || {}; var items = targetItemInfos.map(itemInfo => itemInfo.fileItem); return { name: name, itemInfos: targetItemInfos, items: items, directory: directory, customData: customData, singleRequest: true } } _getItemInfosForUploaderFiles(files, parentDirectoryInfo) { var pathInfo = this._getPathInfo(parentDirectoryInfo); var result = []; for (var i = 0; i < files.length; i++) { var file = files[i]; var item = new FileSystemItem(pathInfo, file.name, false); var itemInfo = this._createFileInfo(item, parentDirectoryInfo); result.push(itemInfo) } return result } refresh() { if (this._lockRefresh) { return this._refreshDeferred } this._lockRefresh = true; return this._executeDataLoad(() => this._refreshDeferred = this._refreshInternal(), "refresh") } _refreshInternal() { var cachedRootInfo = { items: this._rootDirectoryInfo.items }; var selectedKeyParts = this._getDirectoryPathKeyParts(this.getCurrentDirectory()); this._resetDirectoryState(this._rootDirectoryInfo); return this._loadItemsRecursive(this._rootDirectoryInfo, cachedRootInfo).then(() => { var dirInfo = this._findDirectoryByPathKeyParts(selectedKeyParts); this.setCurrentDirectory(dirInfo); delete this._lockRefresh }) } _loadItemsRecursive(directoryInfo, cachedDirectoryInfo) { var _this = this; return this.getDirectories(directoryInfo).then(dirInfos => { var itemDeferreds = []; var _loop = function(i) { var cachedItem = find(cachedDirectoryInfo.items, cache => dirInfos[i].fileItem.key === cache.fileItem.key); if (!cachedItem) { return "continue" } dirInfos[i].expanded = cachedItem.expanded; if (dirInfos[i].expanded) { itemDeferreds.push(_this._loadItemsRecursive(dirInfos[i], cachedItem)) } }; for (var i = 0; i < dirInfos.length; i++) { var _ret = _loop(i); if ("continue" === _ret) { continue } } return whenSome(itemDeferreds) }, () => null) } _initialize() { var result = this._options.currentPathKeys && this._options.currentPathKeys.length ? this.setCurrentPathByKeys(this._options.currentPathKeys) : this.setCurrentPath(this._options.currentPath); var completeInitialization = () => { this._isInitialized = true; this._raiseInitialized() }; if (result) { when(result).always(completeInitialization) } else { completeInitialization() } } _setCurrentDirectoryByPathParts(pathParts, useKeys) { return this._executeDataLoad(() => this._setCurrentDirectoryByPathPartsInternal(pathParts, useKeys), "navigation") } _setCurrentDirectoryByPathPartsInternal(pathParts, useKeys) { return this._getDirectoryByPathParts(this._rootDirectoryInfo, pathParts, useKeys).then(directoryInfo => { for (var info = directoryInfo.parentDirectory; info; info = info.parentDirectory) { info.expanded = true } this.setCurrentDirectory(directoryInfo) }) } _executeDataLoad(action, operation) { if (this._dataLoadingDeferred) { return this._dataLoadingDeferred.then(() => this._executeDataLoad(action, operation)) } this._dataLoading = true; this._dataLoadingDeferred = new Deferred; if (this._isInitialized) { this._raiseDataLoading(operation) } return action().always(() => { var tempDeferred = this._dataLoadingDeferred; this._dataLoadingDeferred = null; this._dataLoading = false; tempDeferred.resolve() }) } _getDirectoryByPathParts(parentDirectoryInfo, pathParts, useKeys) { if (pathParts.length < 1) { return (new Deferred).resolve(parentDirectoryInfo).promise() } var fieldName = useKeys ? "key" : "name"; return this.getDirectories(parentDirectoryInfo).then(dirInfos => { var subDirInfo = find(dirInfos, d => d.fileItem[fieldName] === pathParts[0]); if (!subDirInfo) { return (new Deferred).reject().promise() } var restPathParts = [...pathParts].splice(1); return this._getDirectoryByPathParts(subDirInfo, restPathParts, useKeys) }) } _getDirectoryPathKeyParts(directoryInfo) { var pathParts = []; while (directoryInfo && directoryInfo.parentDirectory) { pathParts.unshift(directoryInfo.fileItem.key); directoryInfo = directoryInfo.parentDirectory } return pathParts } _findDirectoryByPathKeyParts(keyParts) { var selectedDirInfo = this._rootDirectoryInfo; if (0 === keyParts.length) { return selectedDirInfo } var i = 0; var newSelectedDir = selectedDirInfo; while (newSelectedDir && i < keyParts.length) { newSelectedDir = find(selectedDirInfo.items, info => info.fileItem.key === keyParts[i]); if (newSelectedDir) { selectedDirInfo = newSelectedDir } i++ } return selectedDirInfo } _getActualDirectoryInfo(directoryInfo) { var keys = this._getDirectoryPathKeyParts(directoryInfo); return this._findDirectoryByPathKeyParts(keys) } _createDirInfoByName(name, parentDirectoryInfo) { var dirPathInfo = this._getPathInfo(parentDirectoryInfo); var fileItem = new FileSystemItem(dirPathInfo, name, true); return this._createDirectoryInfo(fileItem, parentDirectoryInfo) } _createDirectoryInfo(fileItem, parentDirectoryInfo) { return extend(this._createFileInfo(fileItem, parentDirectoryInfo), { icon: "folder", expanded: fileItem.isRoot(), items: [] }) } _createFileInfo(fileItem, parentDirectoryInfo) { return { fileItem: fileItem, parentDirectory: parentDirectoryInfo, icon: this._getFileItemDefaultIcon(fileItem), getInternalKey() { return "FIK_".concat(this.fileItem.key) }, getDisplayName() { return this.displayName || this.fileItem.name } } } _resetDirectoryState(directoryInfo, isActualDirectoryRequired) { if (isActualDirectoryRequired) { directoryInfo = this._getActualDirectoryInfo(directoryInfo) } directoryInfo.itemsLoaded = false; directoryInfo.items = [] } _getFileItemDefaultIcon(fileItem) { if (fileItem.isDirectory) { return "folder" } var extension = fileItem.getFileExtension(); var icon = this._defaultIconMap[extension]; return icon || "doc" } _createDefaultIconMap() { var result = { ".txt": "txtfile", ".rtf": "rtffile", ".doc": "docfile", ".docx": "docxfile", ".xls": "xlsfile", ".xlsx": "xlsxfile", ".ppt": "pptfile", ".pptx": "pptxfile", ".pdf": "pdffile" }; [".png", ".gif", ".jpg", ".jpeg", ".ico", ".bmp"].forEach(extension => { result[extension] = "image" }); return result } _createRootDirectoryInfo(text) { var rootDirectory = new FileSystemItem(null, "", true); var result = this._createDirectoryInfo(rootDirectory, null); result.displayName = text || DEFAULT_ROOT_FILE_SYSTEM_ITEM_NAME; return result } setRootText(rootText) { this._rootDirectoryInfo.displayName = rootText || DEFAULT_ROOT_FILE_SYSTEM_ITEM_NAME } _raiseInitialized() { var e = { controller: this }; if (this._options.onInitialized) { this._options.onInitialized(e) } } _raiseDataLoading(operation) { if (this._options.onDataLoading) { this._options.onDataLoading({ operation: operation }) } } _raiseSelectedDirectoryChanged(directoryInfo) { var e = { selectedDirectoryInfo: directoryInfo }; if (this._options.onSelectedDirectoryChanged) { this._options.onSelectedDirectoryChanged(e) } } _raiseEditActionStarting(actionInfo) { if (this._options.onEditActionStarting) { this._options.onEditActionStarting(actionInfo) } } _raiseEditActionResultAcquired(actionInfo) { if (this._options.onEditActionResultAcquired) { this._options.onEditActionResultAcquired(actionInfo) } } _raiseEditActionError(actionInfo, errorInfo) { if (this._options.onEditActionError) { this._options.onEditActionError(actionInfo, errorInfo) } } _raiseEditActionItemError(actionInfo, errorInfo) { if (this._options.onEditActionItemError) { this._options.onEditActionItemError(actionInfo, errorInfo) } } _raiseCompleteEditActionItem(actionInfo, info) { if (this._options.onCompleteEditActionItem) { this._options.onCompleteEditActionItem(actionInfo, info) } } _raiseCompleteEditAction(actionInfo) { if (this._options.onCompleteEditAction) { this._options.onCompleteEditAction(actionInfo) } } _resetState() { this._selectedDirectory = null; this._rootDirectoryInfo.items = []; this._loadedItems = {} } _getPathInfo(directoryInfo) { var pathInfo = []; for (var dirInfo = directoryInfo; dirInfo && !dirInfo.fileItem.isRoot(); dirInfo = dirInfo.parentDirectory) { pathInfo.unshift({ key: dirInfo.fileItem.key, name: dirInfo.fileItem.name }) } return pathInfo } on(eventName, eventHandler) { var finalEventName = "on".concat(eventName); this._options[finalEventName] = eventHandler } } class FileSecurityController { constructor(options) { this._options = extend({ allowedFileExtensions: [], maxFileSize: 0 }, options); this._extensionsMap = {}; this._allowedFileExtensions.forEach(extension => { this._extensionsMap[extension.toUpperCase()] = true }) } getAllowedItems(items) { if (0 === this._allowedFileExtensions.length) { return items } return items.filter(item => item.isDirectory || this._isValidExtension(item.name)) } validateExtension(name) { if (!this._isValidExtension(name)) { throw new FileSystemError(ErrorCode.WrongFileExtension, null) } } validateMaxFileSize(size) { if (this._maxFileSize && size > this._maxFileSize) { throw new FileSystemError(ErrorCode.MaxFileSizeExceeded, null) } } _isValidExtension(name) { if (0 === this._allowedFileExtensions.length) { return true } var extension = getFileExtension(name).toUpperCase(); return this._extensionsMap[extension] } get _allowedFileExtensions() { return this._options.allowedFileExtensions } get _maxFileSize() { return this._options.maxFileSize } }