| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import {Component, Output, EventEmitter, OnDestroy, Input, OnInit} from '@angular/core'; |
| import {FlatTreeControl} from '@angular/cdk/tree'; |
| import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree'; |
| import {BucketBrowserService, TodoItemFlatNode, TodoItemNode} from '../../../core/services/bucket-browser.service'; |
| import {BucketDataService} from '../bucket-data.service'; |
| import {Subscription} from 'rxjs'; |
| import {FormControl, FormGroupDirective, NgForm, Validators} from '@angular/forms'; |
| import {ErrorStateMatcher} from '@angular/material/core'; |
| import {PATTERNS} from '../../../core/util'; |
| import {ToastrService} from 'ngx-toastr'; |
| |
| export class MyErrorStateMatcher implements ErrorStateMatcher { |
| isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
| const isSubmitted = form && form.submitted; |
| return !!(control && control.invalid && (control.dirty)); |
| } |
| } |
| |
| |
| |
| @Component({ |
| selector: 'datalab-folder-tree', |
| templateUrl: './folder-tree.component.html', |
| styleUrls: ['./folder-tree.component.scss'] |
| }) |
| |
| export class FolderTreeComponent implements OnDestroy { |
| |
| @Output() showFolderContent: EventEmitter<any> = new EventEmitter(); |
| @Output() disableAll: EventEmitter<any> = new EventEmitter(); |
| @Input() folders; |
| @Input() endpoint: string; |
| @Input() cloud: string; |
| |
| private folderTreeSubs; |
| private path = []; |
| public selectedFolder: TodoItemFlatNode; |
| private flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>(); |
| private nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>(); |
| |
| public folderCreating = false; |
| private subscriptions: Subscription = new Subscription(); |
| public treeControl: FlatTreeControl<TodoItemFlatNode>; |
| private treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>; |
| public dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>; |
| |
| constructor( |
| public toastr: ToastrService, |
| private bucketBrowserService: BucketBrowserService, |
| public bucketDataService: BucketDataService, |
| ) { |
| this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); |
| this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable); |
| this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); |
| this.subscriptions.add(this.bucketDataService._bucketData.subscribe(data => { |
| if (data) { |
| this.dataSource.data = data; |
| const subject = this.dataSource._flattenedData; |
| const subjectData = subject.getValue(); |
| if (this.selectedFolder) { |
| if (this.cloud !== 'azure') { |
| this.selectedFolder = subjectData.find(v => v.item === this.selectedFolder.item && |
| v.level === this.selectedFolder.level && v.obj === this.selectedFolder.obj); |
| } else { |
| const selectedFolderPath = this.selectedFolder.obj.slice(0, this.selectedFolder.obj.lastIndexOf('/') + 1); |
| this.selectedFolder = subjectData.find(v => { |
| const objectPath = v.obj.slice(0, v.obj.lastIndexOf('/') + 1); |
| return v.item === this.selectedFolder.item && |
| v.level === this.selectedFolder.level && objectPath === selectedFolderPath; |
| }); |
| } |
| } |
| this.expandAllParents(this.selectedFolder || subjectData[0]); |
| this.showItem(this.selectedFolder || subjectData[0]); |
| if (this.selectedFolder && !this.bucketDataService.emptyFolder) { |
| setTimeout(() => { |
| const element = document.querySelector('.folder-item-line.active-item'); |
| element && element.scrollIntoView({ block: 'start', behavior: 'smooth' }); |
| }, 0); |
| } else if (this.selectedFolder && this.bucketDataService.emptyFolder) { |
| setTimeout(() => { |
| const element = document.querySelector('#folder-form'); |
| element && element.scrollIntoView({ block: 'end', behavior: 'smooth' }); |
| }, 0); |
| } |
| } |
| })); |
| this.dataSource._flattenedData.subscribe(); |
| } |
| |
| getLevel = (node: TodoItemFlatNode) => node.level; |
| |
| isExpandable = (node: TodoItemFlatNode) => node.expandable; |
| |
| getChildren = (node: TodoItemNode): TodoItemNode[] => node.children; |
| |
| hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable; |
| |
| hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '' || _nodeData.item === 'ا'; |
| |
| transformer = (node: TodoItemNode, level: number) => { |
| const existingNode = this.nestedNodeMap.get(node); |
| const flatNode = existingNode && existingNode.item === node.item |
| ? existingNode |
| : new TodoItemFlatNode(); |
| flatNode.item = node.item; |
| flatNode.level = level; |
| flatNode.expandable = !!node.children; |
| if (node.object) { |
| flatNode.obj = node.object.object; |
| } else { |
| flatNode.obj = ''; |
| } |
| this.flatNodeMap.set(flatNode, node); |
| this.nestedNodeMap.set(node, flatNode); |
| return flatNode; |
| } |
| |
| ngOnDestroy() { |
| this.bucketDataService._bucketData.next([]); |
| this.subscriptions.unsubscribe(); |
| this.bucketDataService.emptyFolder = null; |
| } |
| |
| folderFormControl = new FormControl('', [ |
| Validators.required, |
| Validators.pattern(PATTERNS.folderRegex), |
| this.duplicate.bind(this) |
| ]); |
| |
| matcher = new MyErrorStateMatcher(); |
| |
| private duplicate(control) { |
| if (control && control.value) { |
| const isDublicat = this.folders.slice(1).some(folder => folder.item === control.value); |
| return isDublicat ? { isDuplicate: true } : null; |
| } |
| } |
| |
| public showItem(el) { |
| if (el) { |
| this.treeControl.expand(el); |
| this.selectedFolder = el; |
| const path = this.getPath(el); |
| this.path = []; |
| const data = { |
| flatNode: el, |
| element: this.flatNodeMap.get(el), |
| path: path.map(v => v.item).join('/'), |
| pathObject: path |
| }; |
| this.showFolderContent.emit(data); |
| } |
| } |
| |
| private getPath(el) { |
| if (el) { |
| if (this.path.length === 0) { |
| this.path.unshift(el); |
| } |
| if (this.getParentNode(el) !== null) { |
| this.path.unshift(this.getParentNode(el)); |
| this.getPath(this.getParentNode(el)); |
| } |
| return this.path; |
| } |
| } |
| |
| private expandAllParents(el) { |
| if (el) { |
| this.treeControl.expand(el); |
| if (this.getParentNode(el) !== null) { |
| this.expandAllParents(this.getParentNode(el)); |
| } |
| } |
| } |
| |
| private getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null { |
| const currentLevel = this.getLevel(node); |
| if (currentLevel < 1) { |
| return null; |
| } |
| |
| const startIndex = this.treeControl.dataNodes.indexOf(node) - 1; |
| |
| for (let i = startIndex; i >= 0; i--) { |
| const currentNode = this.treeControl.dataNodes[i]; |
| |
| if (this.getLevel(currentNode) < currentLevel) { |
| return currentNode; |
| } |
| } |
| return null; |
| } |
| |
| |
| private addNewItem(node: TodoItemFlatNode, file, isFile) { |
| const currNode = this.flatNodeMap.get(node); |
| if (!currNode.object) { |
| currNode.object = {bucket: currNode.item, object: ''}; |
| } |
| const emptyFolderObject = currNode.object; |
| if (emptyFolderObject.object.lastIndexOf('ا') !== emptyFolderObject.object.length - 1 || emptyFolderObject.object === '') { |
| emptyFolderObject.object += 'ا'; |
| } |
| this.bucketDataService.insertItem(currNode!, file, isFile, emptyFolderObject); |
| this.treeControl.expand(node); |
| setTimeout(() => { |
| const element = document.querySelector('#folder-form'); |
| element && element.scrollIntoView({ block: 'end', behavior: 'smooth' }); |
| }, 0); |
| } |
| |
| public removeItem(node: TodoItemFlatNode) { |
| const parentNode = this.flatNodeMap.get(this.getParentNode(node)); |
| const childNode = this.flatNodeMap.get(node); |
| if (this.cloud === 'azure') { |
| parentNode.object.object = parentNode.object.object.replace(/ا/g, ''); |
| } |
| this.bucketDataService.emptyFolder = null; |
| this.bucketDataService.removeItem(parentNode!, childNode); |
| this.resetForm(); |
| } |
| |
| public createFolder(node: TodoItemFlatNode, itemValue: string) { |
| this.folderCreating = true; |
| const parent = this.getParentNode(node); |
| const flatParent = this.flatNodeMap.get(parent); |
| let flatObject = flatParent.object.object; |
| if (flatObject.indexOf('ا') === flatObject.length - 1) { |
| flatObject = flatObject.substring(0, flatParent.object.object.length - 1); |
| } |
| const path = `${ flatParent.object && flatObject !== '/' ? flatObject : ''}${itemValue}/`; |
| const bucket = flatParent.object ? flatParent.object.bucket : flatParent.item; |
| |
| this.bucketDataService.emptyFolder = null; |
| if (this.cloud !== 'azure') { |
| this.bucketBrowserService.createFolder({ |
| 'bucket': bucket, |
| 'folder': path.replace(/ا/g, ''), |
| 'endpoint': this.endpoint |
| }) |
| .subscribe(_ => { |
| this.bucketDataService.insertItem(flatParent, itemValue, false); |
| this.toastr.success('Folder successfully created!', 'Success!'); |
| this.folderCreating = false; |
| this.removeItem(node); |
| }, error => { |
| this.folderCreating = false; |
| this.toastr.error(error.message || 'Folder creation error!', 'Oops!'); |
| }); |
| } else { |
| flatParent.object.object = flatParent.object.object.replace(/ا/g, ''); |
| parent.obj = parent.obj.replace(/ا/g, ''); |
| this.bucketDataService.insertItem(flatParent, itemValue, false); |
| this.toastr.success('Folder successfully created!', 'Success!'); |
| this.folderCreating = false; |
| this.removeItem(node); |
| } |
| } |
| |
| private resetForm() { |
| this.folderFormControl.setValue(''); |
| this.folderFormControl.updateValueAndValidity(); |
| this.folderFormControl.markAsPristine(); |
| this.disableAll.emit(false); |
| } |
| } |