Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDB-10692 - Change toggle behavior of import checkbox #1690

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
12 changes: 9 additions & 3 deletions src/css/import-resource-tree.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,18 @@
margin-right: .5rem;
}

.import-resource-tree .import-resources-actions .import-resource-status-dropdown .import-resource-status-checkbox {
pointer-events: none;
.import-resource-tree .import-resources-actions .import-resource-status-dropdown {
padding-left: 5px;
gap: 5px;
margin-right: 10px;
}

.import-resource-tree .import-resources-actions .import-resource-status-dropdown #importSelectCheckboxInput {
cursor: pointer;
}

.import-resource-tree .import-resources-actions .import-resource-status-dropdown .dropdown-toggle {
padding-left: 0.5rem;
margin-right: 0;
}

.import-resource-tree .import-resources-actions .import-resource-status-dropdown .dropdown-toggle:not(.selected) {
Expand Down
110 changes: 25 additions & 85 deletions src/js/angular/import/directives/import-resource-tree.directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {SortingType} from "../../models/import/sorting-type";
import {ImportResourceTreeElement} from "../../models/import/import-resource-tree-element";
import {TABS} from "../services/import-context.service";
import {ImportResourceTreeService} from "../services/import-resource-tree.service";
import {convertToBytes} from "../../utils/size-util";

const TYPE_FILTER_OPTIONS = {
'FILE': 'FILE',
Expand Down Expand Up @@ -64,13 +63,12 @@ function importResourceTreeDirective($timeout, ImportContextService) {
// =========================
$scope.resources = new ImportResourceTreeElement();
$scope.displayResources = [];
$scope.checkboxControlModel = undefined;
$scope.TYPE_FILTER_OPTIONS = TYPE_FILTER_OPTIONS;
$scope.filterByType = TYPE_FILTER_OPTIONS.ALL;
$scope.filterByFileName = '';
$scope.STATUS_OPTIONS = STATUS_OPTIONS;
$scope.selectedByStatus = undefined;
$scope.areAllDisplayedImportResourcesSelected = false;
$scope.areAllDisplayedImportResourcesPartialSelected = false;
$scope.selectedByStatus = STATUS_OPTIONS.NONE;
$scope.ImportResourceStatus = ImportResourceStatus;
$scope.canRemoveResource = angular.isDefined(attrs.onRemove);
$scope.canResetSelectedResources = false;
Expand Down Expand Up @@ -104,7 +102,13 @@ function importResourceTreeDirective($timeout, ImportContextService) {
} else if (STATUS_OPTIONS.IMPORTED === $scope.selectedByStatus) {
$scope.resources.selectAllWithStatus([ImportResourceStatus.DONE]);
} else if (STATUS_OPTIONS.NOT_IMPORTED === $scope.selectedByStatus) {
$scope.resources.selectAllWithStatus([ImportResourceStatus.IMPORTING, ImportResourceStatus.NONE, ImportResourceStatus.ERROR, ImportResourceStatus.PENDING, ImportResourceStatus.INTERRUPTING]);
$scope.resources.selectAllWithStatus([
ImportResourceStatus.IMPORTING,
ImportResourceStatus.NONE,
ImportResourceStatus.ERROR,
ImportResourceStatus.PENDING,
ImportResourceStatus.INTERRUPTING
]);
}

updateListedImportResources();
Expand Down Expand Up @@ -180,6 +184,14 @@ function importResourceTreeDirective($timeout, ImportContextService) {
$scope.onEditResource({resource});
};

$scope.toggleSelectAll = () => {
$scope.resources.setSelection(!($scope.resourceListUtil.areAllDisplayedImportResourcesPartialSelected ||
$scope.resourceListUtil.areAllDisplayedImportResourcesSelected));

updateListedImportResources();
setCanResetResourcesFlag();
};

// =========================
// Private functions
// =========================
Expand All @@ -194,11 +206,8 @@ function importResourceTreeDirective($timeout, ImportContextService) {

const updateListedImportResources = () => {
$scope.resources.getRoot().updateSelectionState();
sortResources();
$scope.displayResources = $scope.resources.toList()
.filter(filterByType)
.filter(filterByName);

$scope.resourceListUtil.sortResources($scope.sortedBy, $scope.sortAsc);
$scope.displayResources = $scope.resourceListUtil.getFilteredResources();
updateHasSelection();
updateSelectByStateDropdownModel();
};
Expand All @@ -210,83 +219,11 @@ function importResourceTreeDirective($timeout, ImportContextService) {
};

const updateSelectByStateDropdownModel = () => {
const hasUnselectedDisplayedImportResource = $scope.displayResources.some((resource) => !resource.selected);
const hasSelectedDisplayedImportResource = $scope.displayResources.some((resource) => resource.selected);
$scope.areAllDisplayedImportResourcesSelected = hasSelectedDisplayedImportResource && !hasUnselectedDisplayedImportResource;
$scope.areAllDisplayedImportResourcesPartialSelected = hasSelectedDisplayedImportResource && hasUnselectedDisplayedImportResource;
};

const sortResources = () => {
if (SortingType.NAME === $scope.sortedBy) {
$scope.resources.sort(compareByName($scope.sortAsc));
} else if (SortingType.SIZE === $scope.sortedBy) {
$scope.resources.sort(compareBySize($scope.sortAsc));
} else if (SortingType.MODIFIED === $scope.sortedBy) {
$scope.resources.sort(compareByModified($scope.sortAsc));
} else if (SortingType.IMPORTED === $scope.sortedBy) {
$scope.resources.sort(compareByImportedOn($scope.sortAsc));
} else if (SortingType.CONTEXT === $scope.sortedBy) {
$scope.resources.sort(compareByContext($scope.sortAsc));
}
};

const compareByName = (acs) => (r1, r2) => {
return acs ? r1.importResource.name.localeCompare(r2.importResource.name) : r2.importResource.name.localeCompare(r1.importResource.name);
};

const compareBySize = (acs) => (r1, r2) => {
// The format of size returned by the backend has changed, but we need to keep the old format for backward compatibility.
// Therefore, we convert the size to always be in bytes.
const r1Size = convertToBytes(r1.importResource.size);
const r2Size = convertToBytes(r2.importResource.size);
return acs ? r1Size - r2Size : r2Size - r1Size;
};

const compareByModified = (acs) => (r1, r2) => {
const r1ModifiedOn = r1.importResource.modifiedOn || Number.MAX_VALUE;
const r2ModifiedOn = r2.importResource.modifiedOn || Number.MAX_VALUE;
return acs ? r1ModifiedOn - r2ModifiedOn : r2ModifiedOn - r1ModifiedOn;
};

const compareByImportedOn = (acs) => (r1, r2) => {
const r1ImportedOn = r1.importResource.importedOn || Number.MAX_VALUE;
const r2ImportedOn = r2.importResource.importedOn || Number.MAX_VALUE;
return acs ? r1ImportedOn - r2ImportedOn : r2ImportedOn - r1ImportedOn;
};

const compareByContext = (acs) => (r1, r2) => {
return acs ? r1.importResource.context.localeCompare(r2.importResource.context) : r2.importResource.context.localeCompare(r1.importResource.context);
};

const filterByType = (resource) => {
if (TYPE_FILTER_OPTIONS.ALL === $scope.filterByType) {
return true;
}

if ($scope.filterByType === TYPE_FILTER_OPTIONS.FILE) {
return resource.isFile();
}

if ($scope.filterByType === TYPE_FILTER_OPTIONS.DIRECTORY) {
return resource.isDirectory();
}

return false;
};

const filterByName = (resource) => {
if (!$scope.filterByFileName) {
return true;
}

if ($scope.filterByType === TYPE_FILTER_OPTIONS.DIRECTORY) {
return resource.hasTextInDirectoriesName($scope.filterByFileName);
}
const mainCheckbox = element[0].querySelector('#importSelectCheckboxInput');

if ($scope.filterByType === TYPE_FILTER_OPTIONS.FILE) {
return resource.hasTextInFilesName($scope.filterByFileName);
if (mainCheckbox) {
mainCheckbox.checked = !!($scope.resourceListUtil.areAllDisplayedImportResourcesSelected || $scope.resourceListUtil.areAllDisplayedImportResourcesPartialSelected);
DesiBorisova marked this conversation as resolved.
Show resolved Hide resolved
}
return resource.hasTextInResourcesName($scope.filterByFileName);
};

let debounceTimeout;
Expand Down Expand Up @@ -314,6 +251,9 @@ function importResourceTreeDirective($timeout, ImportContextService) {
} else {
ImportResourceTreeService.mergeResourceTree($scope.resources, newResources, isUserImport);
}
$scope.resourceListUtil = $scope.resources.getResourceListUtil();
$scope.resourceListUtil.setNameFilter($scope.filterByFileName);
DesiBorisova marked this conversation as resolved.
Show resolved Hide resolved
$scope.resourceListUtil.setTypeFilter($scope.filterByType);
DesiBorisova marked this conversation as resolved.
Show resolved Hide resolved
ImportResourceTreeService.calculateElementIndent($scope.resources);
ImportResourceTreeService.setupAfterTreeInitProperties($scope.resources);
updateListedImportResources();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class ImportResourceTreeService {
*/
static mergeResourceTree(importResourceElement, newResources, isUserImport) {
// Removes missing files.
importResourceElement.getRoot().toList().forEach((resource) => {
importResourceElement.getRoot().getResourceListUtil().getResourceList.forEach((resource) => {
const newResource = newResources.find((newResource) => {
return newResource.name === resource.importResource.name
});
Expand Down
9 changes: 4 additions & 5 deletions src/js/angular/import/templates/import-resource-tree.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
<div class="import-resource-tree mt-2" ng-if="!showLoader && resources && !resources.isEmpty()">
<div class="import-resource-tree-header">
<div class="import-resources-actions">
<div class="import-resource-status-dropdown btn-group" uib-dropdown>
<div class="import-resource-status-dropdown btn-group btn-secondary" uib-dropdown>
<input type="checkbox" id="importSelectCheckboxInput"
prop-indeterminate="resourceListUtil.areAllDisplayedImportResourcesPartialSelected"
ng-click="toggleSelectAll()">
<button type="button" uib-dropdown-toggle class="btn btn-secondary dropdown-toggle"
ng-class="{'selected': selectedByStatus}">
<input type="checkbox" prop-indeterminate="areAllDisplayedImportResourcesPartialSelected"
ng-model="areAllDisplayedImportResourcesSelected">
{{selectedByStatus ? (('import.import_resource_tree.status.' + selectedByStatus) | translate) : '
'}}
</button>
<ul class="dropdown-menu" role="menu">
<li>
Expand Down
13 changes: 7 additions & 6 deletions src/js/angular/models/import/import-resource-tree-element.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ImportResourceStatus} from "./import-resource-status";
import {cloneDeep} from 'lodash';
import {ResourceListUtil} from "./resource-list-util";

/**
* Resources have parent-child relations. This class represents one resource element from the parent-child relation tree.
Expand Down Expand Up @@ -178,21 +179,21 @@ export class ImportResourceTreeElement {
}

/**
* Returns flattening view of resources parent-child relation tree.
* Returns a utility wrapper of the flattening view of resources parent-child relation tree.
*
* @return {ImportResourceTreeElement[]} flattening view of resources.
* @return {ResourceListUtil} a utility wrapper of the flattening view of resources.
*/
toList() {
getResourceListUtil() {
const result = [];
if (this.parent) {
// skip the root element.
result.push(this);
}
this.directories.forEach((directory) => {
result.push(...directory.toList());
result.push(...directory.getResourceListUtil().getResourceList);
});
result.push(...this.files);
return result;
return new ResourceListUtil(result);
}

/**
Expand Down Expand Up @@ -381,6 +382,6 @@ export class ImportResourceTreeElement {
}

getSize() {
return this.toList().length;
return this.getResourceListUtil().getResourceList.length;
}
}
131 changes: 131 additions & 0 deletions src/js/angular/models/import/resource-list-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {SortingType} from "./sorting-type";
import {convertToBytes} from "../../utils/size-util";

export class ResourceListUtil {
constructor(resourceList) {
this.resourceList = resourceList || [];
}

get areAllDisplayedImportResourcesSelected() {
const filteredResources = this.getFilteredResources();
return filteredResources.every((resource) => resource.selected);
}

get areAllDisplayedImportResourcesPartialSelected() {
const filteredResources = this.getFilteredResources();
const hasUnselected = filteredResources.some((resource) => !resource.selected);
const hasSelected = filteredResources.some((resource) => resource.selected);
return hasSelected && hasUnselected;
}

get getResourceList() {
return this.resourceList;
}

setTypeFilter(filterByType) {
this.filterByTypeOption = filterByType;
}

setNameFilter(filterByName) {
this.filterByFileName = filterByName;
}

filterByType(resource) {
switch (this.filterByTypeOption) {
case TYPE_FILTER_OPTIONS.ALL:
return true;
case TYPE_FILTER_OPTIONS.FILE:
return resource.isFile();
case TYPE_FILTER_OPTIONS.DIRECTORY:
return resource.isDirectory();
default:
return false;
}
}

filterByName(resource) {
if (!this.filterByFileName) {
return true;
}

if (this.filterByTypeOption === TYPE_FILTER_OPTIONS.DIRECTORY) {
return resource.hasTextInDirectoriesName(this.filterByFileName);
}

if (this.filterByTypeOption === TYPE_FILTER_OPTIONS.FILE) {
return resource.hasTextInFilesName(this.filterByFileName);
}

return resource.hasTextInResourcesName(this.filterByFileName);
}

getFilteredResources() {
return this.resourceList.filter((resource) => this.filterByType(resource) && this.filterByName(resource));
}

/**
* Sorts the resources based on the specified sorting type and order.
* @param {string} sortedBy - The sorting type (e.g., NAME, SIZE, MODIFIED).
* @param {boolean} sortAsc - Determines whether to sort in ascending order.
*/
sortResources(sortedBy, sortAsc) {
if (SortingType.NAME === sortedBy) {
this.resourceList.sort(this.nameComparator(sortAsc));
} else if (SortingType.SIZE === sortedBy) {
this.resourceList.sort(this.sizeComparator(sortAsc));
} else if (SortingType.MODIFIED === sortedBy) {
this.resourceList.sort(this.modifiedOnComparator(sortAsc));
} else if (SortingType.IMPORTED === sortedBy) {
this.resourceList.sort(this.importedOnComparator(sortAsc));
} else if (SortingType.CONTEXT === sortedBy) {
this.resourceList.sort(this.contextComparator(sortAsc));
}
}

nameComparator(asc) {
return (r1, r2) => {
return asc
? r1.importResource.name.localeCompare(r2.importResource.name)
: r2.importResource.name.localeCompare(r1.importResource.name);
};
}

sizeComparator(asc) {
return (r1, r2) => {
// The format of size returned by the backend has changed, but we need to keep the old format for backward compatibility.
// Therefore, we convert the size to always be in bytes.
const r1Size = convertToBytes(r1.importResource.size);
const r2Size = convertToBytes(r2.importResource.size);
return asc ? r1Size - r2Size : r2Size - r1Size;
};
}

modifiedOnComparator(asc) {
return (r1, r2) => {
const r1ModifiedOn = r1.importResource.modifiedOn || Number.MAX_VALUE;
const r2ModifiedOn = r2.importResource.modifiedOn || Number.MAX_VALUE;
return asc ? r1ModifiedOn - r2ModifiedOn : r2ModifiedOn - r1ModifiedOn;
};
}

importedOnComparator(asc) {
return (r1, r2) => {
const r1ImportedOn = r1.importResource.importedOn || Number.MAX_VALUE;
const r2ImportedOn = r2.importResource.importedOn || Number.MAX_VALUE;
return asc ? r1ImportedOn - r2ImportedOn : r2ImportedOn - r1ImportedOn;
};
}

contextComparator(asc) {
return (r1, r2) => {
return asc
? r1.importResource.context.localeCompare(r2.importResource.context)
: r2.importResource.context.localeCompare(r1.importResource.context);
};
}
}
const TYPE_FILTER_OPTIONS = {
'FILE': 'FILE',
'DIRECTORY': 'DIRECTORY',
'ALL': 'ALL'
};
Loading