ACS-11224 Migrate upload components to Angular control flow (#11691)

This commit is contained in:
Denys Vuika
2026-02-26 14:54:25 +00:00
committed by GitHub
parent 68d174c3e6
commit 3535fa3d89
44 changed files with 1072 additions and 1557 deletions
+3 -3
View File
@@ -38,9 +38,9 @@ runs:
node_modules node_modules
key: .npm-${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} key: .npm-${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
node_modules-${{ runner.os }}-build-${{ env.cache-name }}- .npm-${{ runner.os }}-build-${{ env.cache-name }}-
node_modules-${{ runner.os }}-build- .npm-${{ runner.os }}-build-
node_modules-${{ runner.os }}- .npm-${{ runner.os }}-
- name: load "NPM TAG" - name: load "NPM TAG"
id: set-npm-tag id: set-npm-tag
uses: ./.github/actions/set-npm-tag uses: ./.github/actions/set-npm-tag
+1 -1
View File
@@ -1 +1 @@
24.11.1 24.14.0
+2 -4
View File
@@ -20,10 +20,8 @@
"stylelint.enable": true, "stylelint.enable": true,
"css.validate": false, "css.validate": false,
"scss.validate": false, "scss.validate": false,
"stylelint.config": { "stylelint.configFile": "${workspaceFolder}/.stylelintrc.json",
"extends": "./.stylelintrc.json" "stylelint.configBasedir": "${workspaceFolder}",
},
"stylelint.configFile": ".stylelintrc.json",
"stylelint.validate": [ "stylelint.validate": [
"css", "css",
"less", "less",
-7
View File
@@ -323,7 +323,6 @@ for more information about installing and using the source code.
| [Node Favorite directive](content-services/directives/node-favorite.directive.md) | Selectively toggles nodes as favorites. | [Source](../lib/content-services/src/lib/directives/node-favorite.directive.ts) | | [Node Favorite directive](content-services/directives/node-favorite.directive.md) | Selectively toggles nodes as favorites. | [Source](../lib/content-services/src/lib/directives/node-favorite.directive.ts) |
| [Node Lock directive](content-services/directives/node-lock.directive.md) | Locks or unlocks a node. | [Source](../lib/content-services/src/lib/directives/node-lock.directive.ts) | | [Node Lock directive](content-services/directives/node-lock.directive.md) | Locks or unlocks a node. | [Source](../lib/content-services/src/lib/directives/node-lock.directive.ts) |
| [Node Restore directive](content-services/directives/node-restore.directive.md) | Restores deleted nodes to their original location. | [Source](../lib/content-services/src/lib/directives/node-restore.directive.ts) | | [Node Restore directive](content-services/directives/node-restore.directive.md) | Restores deleted nodes to their original location. | [Source](../lib/content-services/src/lib/directives/node-restore.directive.ts) |
| [Toggle Icon directive](content-services/directives/toggle-icon.directive.md) | Toggle icon on mouse or keyboard event for a selectable element. | [Source](../lib/content-services/src/lib/upload/directives/toggle-icon.directive.ts) |
| [Version Compatibility Directive](content-services/directives/version-compatibility.directive.md) | Enables/disables components based on ACS version in use. | [Source](../lib/content-services/src/lib/version-compatibility/version-compatibility.directive.ts) | | [Version Compatibility Directive](content-services/directives/version-compatibility.directive.md) | Enables/disables components based on ACS version in use. | [Source](../lib/content-services/src/lib/version-compatibility/version-compatibility.directive.ts) |
### Dialogs ### Dialogs
@@ -348,12 +347,6 @@ for more information about installing and using the source code.
| [Permission Style model](content-services/models/permissions-style.model.md) | Sets custom CSS styles for rows of a Document List according to the item's permissions. | [Source](../lib/content-services/src/lib/document-list/models/permissions-style.model.ts) | | [Permission Style model](content-services/models/permissions-style.model.md) | Sets custom CSS styles for rows of a Document List according to the item's permissions. | [Source](../lib/content-services/src/lib/document-list/models/permissions-style.model.ts) |
| [Row Filter Model](content-services/models/row-filter.model.md) | Defines the Row Filter function used by the Document List Component. | [Source](../lib/content-services/document-list/data/row-filter.model.ts) | | [Row Filter Model](content-services/models/row-filter.model.md) | Defines the Row Filter function used by the Document List Component. | [Source](../lib/content-services/document-list/data/row-filter.model.ts) |
### Pipes
| Name | Description | Source link |
| ---- | ----------- | ----------- |
| [File upload error pipe](content-services/pipes/file-upload-error.pipe.md) | Converts an upload error code to an error message. | [Source](../lib/content-services/src/lib/upload/pipes/file-upload-error.pipe.ts) |
### Services ### Services
| Name | Description | Source link | | Name | Description | Source link |
@@ -40,4 +40,3 @@ This component should be used in combination with the
- [Upload button component](upload-button.component.md) - [Upload button component](upload-button.component.md)
- [Upload drag area component](upload-drag-area.component.md) - [Upload drag area component](upload-drag-area.component.md)
- [File upload error pipe](../pipes/file-upload-error.pipe.md)
@@ -86,4 +86,3 @@ export class MyComponent {
## See also ## See also
- [Upload Version Button component](upload-version-button.component.md) - [Upload Version Button component](upload-version-button.component.md)
- [File upload error pipe](../pipes/file-upload-error.pipe.md)
@@ -141,4 +141,3 @@ This could be either a selection of 2 or more files, or a folder with multiple e
- [File uploading dialog component](file-uploading-dialog.component.md) - [File uploading dialog component](file-uploading-dialog.component.md)
- [Upload button component](upload-button.component.md) - [Upload button component](upload-button.component.md)
- [File upload error pipe](../pipes/file-upload-error.pipe.md)
@@ -1,27 +0,0 @@
---
Title: Toggle Icon directive
Added: v2.0.0
Status: Active
Last reviewed: 2019-04-09
---
# [Toggle Icon directive](../../../lib/content-services/src/lib/upload/directives/toggle-icon.directive.ts "Defined in toggle-icon.directive.ts")
Toggle icon on mouse or keyboard event for a selectable element.
## Example Usage
```html
<button mat-icon-button adf-toggle-icon #toggle="toggleIcon">
<mat-icon *ngIf="!toggle.isToggled">
check_circle
</mat-icon>
<mat-icon *ngIf="toggle.isToggled">
remove_circle
</mat-icon>
<button></button>
</button>
```
## Class members
@@ -1,34 +0,0 @@
---
Title: File upload error pipe
Added: v3.0.0
Status: Active
Last reviewed: 2019-01-17
---
# [File upload error pipe](../../../lib/content-services/src/lib/upload/pipes/file-upload-error.pipe.ts "Defined in file-upload-error.pipe.ts")
Converts an upload error code to an error message.
## Basic Usage
<!-- {% raw %} -->
```HTML
<div>
Upload failed: {{ errorCode | adfFileUploadError }}
</div>
```
<!-- {% endraw %} -->
## Details
The pipe takes an upload error code (eg, from the `error` event of one of the
upload components) and converts it to a human-readable message. The message is
automatically translated to the user's chosen language.
## See also
- [File uploading dialog component](../components/file-uploading-dialog.component.md)
- [Upload drag area component](../components/upload-drag-area.component.md)
- [Upload button component](../components/upload-button.component.md)
+4 -4
View File
@@ -5,7 +5,7 @@ Status: Active
Last reviewed: 2019-08-05 Last reviewed: 2019-08-05
--- ---
# [Decimal Number Pipe](../../../lib/core/src/lib/pipes/decimal-number.pipe.ts "Defined in decimal-number.pipe.ts") # Decimal Number Pipe
Transforms a number to have a certain amount of digits in its integer part and also in its decimal part. Transforms a number to have a certain amount of digits in its integer part and also in its decimal part.
@@ -15,7 +15,7 @@ Transforms a number to have a certain amount of digits in its integer part and a
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| digitsInfo | [`DecimalNumberModel`](../../../lib/core/src/lib/models/decimal-number.model.ts) | | A format to apply to the date value. [Date Pipe Formats.](https://angular.io/api/common/DatePipe#custom-format-options) | | digitsInfo | `DecimalNumberModel` | | A format to apply to the date value. [Date Pipe Formats.](https://angular.io/api/common/DatePipe#custom-format-options) |
| locale | string | 'en-US' | A locale id for the locale format rules to use. | | locale | string | 'en-US' | A locale id for the locale format rules to use. |
## Details ## Details
@@ -30,11 +30,11 @@ This pipe transforms a given number so it follows the set configuration for the
} }
``` ```
You can also overwrite this config by passing a [`DecimalNumberModel`](../../../lib/core/src/lib/models/decimal-number.model.ts) as an argument for this pipe. You can also overwrite this config by passing a `DecimalNumberModel` as an argument for this pipe.
The number can be also localized so it applies commas and dots in the right place depending on the locale id in use. The number can be also localized so it applies commas and dots in the right place depending on the locale id in use.
#### Result ### Result
```ts ```ts
decimalNumberPipe.transform(1234.567); decimalNumberPipe.transform(1234.567);
-2
View File
@@ -354,7 +354,6 @@ backend services have been tested with each released version of ADF.
- [Edit process filter cloud component](process-services-cloud/components/edit-process-filter-cloud.component.md) - [Edit process filter cloud component](process-services-cloud/components/edit-process-filter-cloud.component.md)
- [Edit task filter cloud component](process-services-cloud/components/edit-task-filter-cloud.component.md) - [Edit task filter cloud component](process-services-cloud/components/edit-task-filter-cloud.component.md)
- [Extension service](extensions/services/extension.service.md) - [Extension service](extensions/services/extension.service.md)
- [File upload error pipe](content-services/pipes/file-upload-error.pipe.md)
- [Format space pipe](core/pipes/format-space.pipe.md) - [Format space pipe](core/pipes/format-space.pipe.md)
- [Full name pipe](core/pipes/full-name.pipe.md) - [Full name pipe](core/pipes/full-name.pipe.md)
- [Group cloud component](process-services-cloud/components/group-cloud.component.md) - [Group cloud component](process-services-cloud/components/group-cloud.component.md)
@@ -663,7 +662,6 @@ backend services have been tested with each released version of ADF.
- [Text mask component](core/components/text-mask.component.md) - [Text mask component](core/components/text-mask.component.md)
- [Thumbnail service](core/services/thumbnail.service.md) - [Thumbnail service](core/services/thumbnail.service.md)
- [Time ago pipe](core/pipes/time-ago.pipe.md) - [Time ago pipe](core/pipes/time-ago.pipe.md)
- [Toggle icon directive](content-services/directives/toggle-icon.directive.md)
- [Toolbar divider component](core/components/toolbar-divider.component.md) - [Toolbar divider component](core/components/toolbar-divider.component.md)
- [Toolbar title component](core/components/toolbar-title.component.md) - [Toolbar title component](core/components/toolbar-title.component.md)
- [Toolbar component](core/components/toolbar.component.md) - [Toolbar component](core/components/toolbar.component.md)
@@ -1,74 +1,79 @@
<nav @if (folderNode) {
*ngIf="folderNode" <nav
data-automation-id="breadcrumb" data-automation-id="breadcrumb"
class="adf-breadcrumb-container" class="adf-breadcrumb-container"
role="navigation" role="navigation"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.BREADCRUMB' | translate" [attr.aria-label]="'BREADCRUMB.ARIA-LABEL.BREADCRUMB' | translate"
>
<button
*ngIf="hasPreviousNodes()"
tabindex="0"
class="adf-breadcrumb-dropdown-trigger"
(click)="open()"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.DROPDOWN' | translate"
> >
<div class="adf-breadcrumb-dropdown-trigger-icon"> @if (hasPreviousNodes()) {
<mat-icon [class.adf-isRoot]="!hasPreviousNodes()" adf-icon="folder" /> <button
<mat-icon [class.adf-isRoot]="!hasPreviousNodes()" class="adf-breadcrumb-dropdown-trigger-arrow" adf-icon="arrow_drop_down" /> tabindex="0"
</div> class="adf-breadcrumb-dropdown-trigger"
</button> (click)="open()"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.DROPDOWN' | translate"
>
<div class="adf-breadcrumb-dropdown-trigger-icon">
<mat-icon [class.adf-isRoot]="!hasPreviousNodes()" adf-icon="folder" />
<mat-icon [class.adf-isRoot]="!hasPreviousNodes()" class="adf-breadcrumb-dropdown-trigger-arrow" adf-icon="arrow_drop_down" />
</div>
</button>
<mat-select
#dropdown
class="adf-breadcrumb-dropdown-path"
tabindex="-1"
role="button"
>
@for (node of previousNodes; track node.id) {
<mat-option
(click)="onRoutePathClick(node, $event)"
(onSelectionChange)="onRouteClick(node)"
class="adf-breadcrumb-path-option"
[disabled]="readOnly"
>
{{ node.name ?? '' | translate }}
</mat-option>
}
</mat-select>
}
<mat-select @for (item of lastNodes; track item.id; let last = $last) {
#dropdown <div
*ngIf="hasPreviousNodes()" [class.adf-active]="last"
class="adf-breadcrumb-dropdown-path" title="{{ item.name ?? '' | translate }}"
tabindex="-1" class="adf-breadcrumb-item">
role="button" @if (breadcrumbItemIsAnchor(last)) {
> <a
<mat-option href="#"
*ngFor="let node of previousNodes" [attr.data-automation-id]="'breadcrumb_' + item.name"
(click)="onRoutePathClick(node, $event)" class="adf-breadcrumb-item-anchor"
(onSelectionChange)="onRouteClick(node)" (click)="onRoutePathClick(item, $event)"
class="adf-breadcrumb-path-option" >
[disabled]="readOnly" {{ item.name ?? '' | translate }}
</a>
} @else {
<div class="adf-breadcrumb-item-current" aria-current="location">
{{ (selectedRowItemsCount < 1 ? item.name ?? '' : 'BREADCRUMB.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
</div>
}
@if (!last) {
<mat-icon class="adf-breadcrumb-item-chevron" adf-icon="chevron_right" />
}
</div>
}
</nav>
@if (!folderNode && hasRoot) {
<nav
data-automation-id="breadcrumb"
role="navigation"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.BREADCRUMB' | translate"
> >
{{ node.name | translate }} <div class="adf-breadcrumb-item adf-active" role="group">
</mat-option> <div class="adf-breadcrumb-item-current">
</mat-select> {{ root ?? '' | translate }}
</div>
<div </div>
*ngFor="let item of lastNodes; let last = last" </nav>
[class.adf-active]="last" }
[ngSwitch]="breadcrumbItemIsAnchor(last)" }
title="{{ item.name | translate }}"
class="adf-breadcrumb-item">
<a
*ngSwitchCase="true"
href="#"
[attr.data-automation-id]="'breadcrumb_' + item.name"
class="adf-breadcrumb-item-anchor"
(click)="onRoutePathClick(item, $event)"
>
{{ item.name | translate }}
</a>
<div *ngSwitchDefault class="adf-breadcrumb-item-current" aria-current="location">
{{ (selectedRowItemsCount < 1 ? item.name : 'BREADCRUMB.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
</div>
<mat-icon class="adf-breadcrumb-item-chevron" *ngIf="!last" adf-icon="chevron_right" />
</div>
</nav>
<nav
*ngIf="!folderNode && hasRoot"
data-automation-id="breadcrumb"
role="navigation"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.BREADCRUMB' | translate"
>
<div class="adf-breadcrumb-item adf-active" role="group">
<div class="adf-breadcrumb-item-current">
{{ root | translate }}
</div>
</div>
</nav>
@@ -31,7 +31,6 @@ import {
import { MatSelect, MatSelectModule } from '@angular/material/select'; import { MatSelect, MatSelectModule } from '@angular/material/select';
import { Node, PathElement } from '@alfresco/js-api'; import { Node, PathElement } from '@alfresco/js-api';
import { DocumentListComponent } from '../document-list/components/document-list.component'; import { DocumentListComponent } from '../document-list/components/document-list.component';
import { CommonModule } from '@angular/common';
import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { IconModule } from '@alfresco/adf-core'; import { IconModule } from '@alfresco/adf-core';
@@ -39,7 +38,7 @@ import { LiveAnnouncer } from '@angular/cdk/a11y';
@Component({ @Component({
selector: 'adf-breadcrumb', selector: 'adf-breadcrumb',
imports: [CommonModule, IconModule, TranslatePipe, MatSelectModule], imports: [IconModule, TranslatePipe, MatSelectModule],
templateUrl: './breadcrumb.component.html', templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss'], styleUrls: ['./breadcrumb.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
@@ -1,29 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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 { NgModule } from '@angular/core';
import { BreadcrumbComponent } from './breadcrumb.component';
import { DropdownBreadcrumbComponent } from './dropdown-breadcrumb.component';
export const BREADCRUMB_DIRECTIVES = [BreadcrumbComponent, DropdownBreadcrumbComponent] as const;
/** @deprecated use `...BREADCRUMB_DIRECTIVES` instead */
@NgModule({
imports: [...BREADCRUMB_DIRECTIVES],
exports: [...BREADCRUMB_DIRECTIVES]
})
export class BreadcrumbModule {}
@@ -1,48 +1,47 @@
<nav *ngIf="route.length > 0" class="adf-dropdown-breadcrumb-container" @if (route.length > 0) {
role="navigation" <nav class="adf-dropdown-breadcrumb-container"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.BREADCRUMB' | translate"> role="navigation"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.BREADCRUMB' | translate">
<button <button
id="dropdown-breadcrumb-button" id="dropdown-breadcrumb-button"
[tabindex]="hasPreviousNodes() ? 0 : -1" [tabindex]="hasPreviousNodes() ? 0 : -1"
class="adf-dropdown-breadcrumb-trigger" class="adf-dropdown-breadcrumb-trigger"
(click)="open()" (click)="open()"
[attr.aria-label]="'BREADCRUMB.ARIA-LABEL.DROPDOWN' | translate:{ folderName: currentNode?.name }" [attr.aria-label]="'BREADCRUMB.ARIA-LABEL.DROPDOWN' | translate:{ folderName: currentNode?.name }"
[attr.aria-haspopup]="hasPreviousNodes() ? 'true' : null" [attr.aria-haspopup]="hasPreviousNodes() ? 'true' : null"
[attr.aria-expanded]="dropdown?.panelOpen || false" [attr.aria-expanded]="dropdown?.panelOpen || false"
data-automation-id="dropdown-breadcrumb-trigger"> data-automation-id="dropdown-breadcrumb-trigger">
<mat-icon class="adf-dropdown-breadcrumb-icon" [class.adf-isRoot]="!hasPreviousNodes()" adf-icon="folder" /> <mat-icon class="adf-dropdown-breadcrumb-icon" [class.adf-isRoot]="!hasPreviousNodes()" adf-icon="folder" />
</button> </button>
<mat-icon class="adf-dropdown-breadcrumb-icon adf-dropdown-breadcrumb-item-chevron" adf-icon="chevron_right" /> <mat-icon class="adf-dropdown-breadcrumb-icon adf-dropdown-breadcrumb-item-chevron" adf-icon="chevron_right" />
<div class="adf-dropdown-breadcrumb-path"> <div class="adf-dropdown-breadcrumb-path">
@if (hasPreviousNodes()) {
<mat-select <mat-select
#dropdown #dropdown
*ngIf="hasPreviousNodes()" panelClass="adf-breadcrumb-dropdown-panel"
panelClass="adf-breadcrumb-dropdown-panel" class="adf-dropdown-breadcrumb-path-select"
class="adf-dropdown-breadcrumb-path-select" tabindex="-1"
tabindex="-1" role="button"
role="button" data-automation-id="dropdown-breadcrumb-path"
data-automation-id="dropdown-breadcrumb-path" aria-labelledby="dropdown-breadcrumb-button">
aria-labelledby="dropdown-breadcrumb-button"> @for (node of previousNodes; track node.id) {
<mat-option <mat-option
*ngFor="let node of previousNodes;" (click)="onRoutePathClick(node, $event)"
(click)="onRoutePathClick(node, $event)" (onSelectionChange)="onRouteClick(node)"
(onSelectionChange)="onRouteClick(node)" class="adf-dropdown-breadcrumb-path-option adf-dropdown-breadcrumb-path-option-node"
class="adf-dropdown-breadcrumb-path-option adf-dropdown-breadcrumb-path-option-node" data-automation-class="dropdown-breadcrumb-path-option"
data-automation-class="dropdown-breadcrumb-path-option" [disabled]="readOnly">
[disabled]="readOnly"> {{ node.name ?? '' | translate }}
{{ node.name | translate }}
</mat-option> </mat-option>
}
</mat-select> </mat-select>
}
</div> </div>
<span <span
class="adf-current-folder" class="adf-current-folder"
[class.adf-isRoot]="!hasPreviousNodes()" [class.adf-isRoot]="!hasPreviousNodes()"
data-automation-id="current-folder" data-automation-id="current-folder"
aria-current="location">{{ currentNode.name }} aria-current="location">{{ currentNode.name }}
</span> </span>
</nav> </nav>
}
@@ -19,13 +19,13 @@ import { Component, OnChanges, ViewChild, ViewEncapsulation } from '@angular/cor
import { MatSelect, MatSelectModule } from '@angular/material/select'; import { MatSelect, MatSelectModule } from '@angular/material/select';
import { PathElement, Node } from '@alfresco/js-api'; import { PathElement, Node } from '@alfresco/js-api';
import { BreadcrumbComponent } from './breadcrumb.component'; import { BreadcrumbComponent } from './breadcrumb.component';
import { CommonModule } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { IconModule } from '@alfresco/adf-core'; import { IconModule } from '@alfresco/adf-core';
@Component({ @Component({
selector: 'adf-dropdown-breadcrumb', selector: 'adf-dropdown-breadcrumb',
imports: [CommonModule, IconModule, MatSelectModule, TranslatePipe], imports: [IconModule, MatSelectModule, TranslatePipe],
templateUrl: './dropdown-breadcrumb.component.html', templateUrl: './dropdown-breadcrumb.component.html',
styleUrls: ['./dropdown-breadcrumb.component.scss'], styleUrls: ['./dropdown-breadcrumb.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
@@ -17,5 +17,3 @@
export * from './breadcrumb.component'; export * from './breadcrumb.component';
export * from './dropdown-breadcrumb.component'; export * from './dropdown-breadcrumb.component';
export * from './breadcrumb.module';
@@ -1,39 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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 { Person } from '@alfresco/js-api';
export const fakeEcmUser: Person = {
id: 'fake-id',
firstName: 'fake-ecm-first-name',
lastName: 'fake-ecm-last-name',
description: 'i am a fake user for test',
avatarId: 'fake-avatar-id',
email: 'fakeEcm@ecmUser.com',
skypeId: 'fake-skype-id',
googleId: 'fake-googleId-id',
instantMessageId: 'fake-instantMessageId-id',
company: null,
jobTitle: 'job-ecm-test',
location: 'fake location',
mobile: '000000000',
telephone: '11111111',
statusUpdatedAt: new Date(),
userStatus: 'active',
enabled: true,
emailNotificationsEnabled: true
};
@@ -41,4 +41,3 @@ export * from './models/allowable-operations.enum';
export * from './interfaces/search-configuration.interface'; export * from './interfaces/search-configuration.interface';
export * from './interfaces/saved-search.interface'; export * from './interfaces/saved-search.interface';
export * from './interfaces/saved-searches-strategy.interface'; export * from './interfaces/saved-searches-strategy.interface';
export * from './mocks/ecm-user.service.mock';
@@ -15,17 +15,37 @@
* limitations under the License. * limitations under the License.
*/ */
import { fakeEcmUser } from '../mocks/ecm-user.service.mock';
import { RedirectAuthService } from '@alfresco/adf-core'; import { RedirectAuthService } from '@alfresco/adf-core';
import { PeopleContentQueryRequestModel, PeopleContentService } from './people-content.service'; import { PeopleContentQueryRequestModel, PeopleContentService } from './people-content.service';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { PersonPaging } from '@alfresco/js-api'; import { Person, PersonPaging } from '@alfresco/js-api';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClientTesting } from '@angular/common/http/testing';
import { EMPTY, firstValueFrom, of } from 'rxjs'; import { EMPTY, firstValueFrom, of } from 'rxjs';
import { AlfrescoApiService } from '../../services'; import { AlfrescoApiService } from '../../services';
import { AlfrescoApiServiceMock } from '../../mock'; import { AlfrescoApiServiceMock } from '../../mock';
const fakeEcmUser: Person = {
id: 'fake-id',
firstName: 'fake-ecm-first-name',
lastName: 'fake-ecm-last-name',
description: 'i am a fake user for test',
avatarId: 'fake-avatar-id',
email: 'fakeEcm@ecmUser.com',
skypeId: 'fake-skype-id',
googleId: 'fake-googleId-id',
instantMessageId: 'fake-instantMessageId-id',
company: null,
jobTitle: 'job-ecm-test',
location: 'fake location',
mobile: '000000000',
telephone: '11111111',
statusUpdatedAt: new Date(),
userStatus: 'active',
enabled: true,
emailNotificationsEnabled: true
};
export const fakeEcmUser2 = { export const fakeEcmUser2 = {
id: 'another-fake-id', id: 'another-fake-id',
firstName: 'another-fake-first-name', firstName: 'another-fake-first-name',
@@ -76,7 +76,7 @@
<div> <div>
<ng-container *ngIf="isUploadEnabled()"> <ng-container *ngIf="isUploadEnabled()">
<adf-upload-button <adf-upload-button
[staticTitle]="'FORM.FIELD.UPLOAD' | translate " [staticTitle]="'FORM.FIELD.UPLOAD' | translate"
[multipleFiles]="isMultipleSelection()" [multipleFiles]="isMultipleSelection()"
[rootFolderId]="currentDirectoryId" [rootFolderId]="currentDirectoryId"
[disabled]="isNotAllowedToUpload()" [disabled]="isNotAllowedToUpload()"
+2 -32
View File
@@ -21,7 +21,6 @@ import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimep
import { CONTENT_TAG_DIRECTIVES } from './tag/tag.module'; import { CONTENT_TAG_DIRECTIVES } from './tag/tag.module';
import { DOCUMENT_LIST_DIRECTIVES } from './document-list/document-list.module'; import { DOCUMENT_LIST_DIRECTIVES } from './document-list/document-list.module';
import { CONTENT_SEARCH_DIRECTIVES } from './search/search.module'; import { CONTENT_SEARCH_DIRECTIVES } from './search/search.module';
import { BREADCRUMB_DIRECTIVES } from './breadcrumb/breadcrumb.module';
import { CONTENT_VERSION_DIRECTIVES } from './version-manager/version-manager.module'; import { CONTENT_VERSION_DIRECTIVES } from './version-manager/version-manager.module';
import { CONTENT_NODE_SELECTOR_DIRECTIVES } from './content-node-selector/content-node-selector.module'; import { CONTENT_NODE_SELECTOR_DIRECTIVES } from './content-node-selector/content-node-selector.module';
import { CONTENT_NODE_SHARE_DIRECTIVES } from './content-node-share/content-node-share.module'; import { CONTENT_NODE_SHARE_DIRECTIVES } from './content-node-share/content-node-share.module';
@@ -34,17 +33,8 @@ import { versionCompatibilityFactory } from './version-compatibility/version-com
import { VersionCompatibilityService } from './version-compatibility/version-compatibility.service'; import { VersionCompatibilityService } from './version-compatibility/version-compatibility.service';
import { contentAuthLoaderFactory } from './auth-loader/content-auth-loader-factory'; import { contentAuthLoaderFactory } from './auth-loader/content-auth-loader-factory';
import { ContentAuthLoaderService } from './auth-loader/content-auth-loader.service'; import { ContentAuthLoaderService } from './auth-loader/content-auth-loader.service';
import { CategoriesManagementComponent } from './category';
import { TreeComponent } from './tree';
import { NewVersionUploaderDialogComponent } from './new-version-uploader';
import { VersionCompatibilityDirective } from './version-compatibility';
import { CONTENT_UPLOAD_DIRECTIVES } from './upload'; import { CONTENT_UPLOAD_DIRECTIVES } from './upload';
import { TreeViewComponent } from './tree-view';
import { NodeCommentsComponent } from './node-comments';
import { AlfrescoViewerComponent } from './viewer';
import { ContentTypeDialogComponent } from './content-type';
import { MaterialModule } from './material.module'; import { MaterialModule } from './material.module';
import { AlfrescoIconComponent } from './alfresco-icon/alfresco-icon.component';
import { AlfrescoApiService } from './services/alfresco-api.service'; import { AlfrescoApiService } from './services/alfresco-api.service';
import { AlfrescoApiNoAuthService } from './api-factories/alfresco-api-no-auth.service'; import { AlfrescoApiNoAuthService } from './api-factories/alfresco-api-no-auth.service';
import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-factories/alfresco-api-v2-loader.service'; import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-factories/alfresco-api-v2-loader.service';
@@ -59,23 +49,13 @@ import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-facto
...CONTENT_SEARCH_DIRECTIVES, ...CONTENT_SEARCH_DIRECTIVES,
...DOCUMENT_LIST_DIRECTIVES, ...DOCUMENT_LIST_DIRECTIVES,
...CONTENT_UPLOAD_DIRECTIVES, ...CONTENT_UPLOAD_DIRECTIVES,
...BREADCRUMB_DIRECTIVES,
...CONTENT_NODE_SELECTOR_DIRECTIVES, ...CONTENT_NODE_SELECTOR_DIRECTIVES,
...CONTENT_NODE_SHARE_DIRECTIVES, ...CONTENT_NODE_SHARE_DIRECTIVES,
...CONTENT_METADATA_DIRECTIVES, ...CONTENT_METADATA_DIRECTIVES,
...CONTENT_DIRECTIVES, ...CONTENT_DIRECTIVES,
...CONTENT_PERMISSION_MANAGER_DIRECTIVES, ...CONTENT_PERMISSION_MANAGER_DIRECTIVES,
...CONTENT_VERSION_DIRECTIVES, ...CONTENT_VERSION_DIRECTIVES,
TreeViewComponent, ...ASPECT_LIST_DIRECTIVES
ContentTypeDialogComponent,
...ASPECT_LIST_DIRECTIVES,
VersionCompatibilityDirective,
NodeCommentsComponent,
TreeComponent,
AlfrescoViewerComponent,
CategoriesManagementComponent,
NewVersionUploaderDialogComponent,
AlfrescoIconComponent
], ],
providers: [provideTranslations('adf-content-services', 'assets/adf-content-services')], providers: [provideTranslations('adf-content-services', 'assets/adf-content-services')],
exports: [ exports: [
@@ -84,7 +64,6 @@ import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-facto
...DOCUMENT_LIST_DIRECTIVES, ...DOCUMENT_LIST_DIRECTIVES,
...CONTENT_UPLOAD_DIRECTIVES, ...CONTENT_UPLOAD_DIRECTIVES,
...CONTENT_SEARCH_DIRECTIVES, ...CONTENT_SEARCH_DIRECTIVES,
...BREADCRUMB_DIRECTIVES,
...CONTENT_NODE_SELECTOR_DIRECTIVES, ...CONTENT_NODE_SELECTOR_DIRECTIVES,
...CONTENT_NODE_SHARE_DIRECTIVES, ...CONTENT_NODE_SHARE_DIRECTIVES,
...CONTENT_METADATA_DIRECTIVES, ...CONTENT_METADATA_DIRECTIVES,
@@ -92,16 +71,7 @@ import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-facto
...CONTENT_DIRECTIVES, ...CONTENT_DIRECTIVES,
...CONTENT_PERMISSION_MANAGER_DIRECTIVES, ...CONTENT_PERMISSION_MANAGER_DIRECTIVES,
...CONTENT_VERSION_DIRECTIVES, ...CONTENT_VERSION_DIRECTIVES,
TreeViewComponent, ...ASPECT_LIST_DIRECTIVES
...ASPECT_LIST_DIRECTIVES,
ContentTypeDialogComponent,
VersionCompatibilityDirective,
NodeCommentsComponent,
TreeComponent,
AlfrescoViewerComponent,
CategoriesManagementComponent,
NewVersionUploaderDialogComponent,
AlfrescoIconComponent
] ]
}) })
export class ContentModule { export class ContentModule {
@@ -1,116 +1,117 @@
<div *ngIf="canShowDialog()" @if (canShowDialog()) {
role="dialog" <div
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG'| translate" #uploadDialog
tabindex="0" role="dialog"
class="adf-upload-dialog" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG'| translate"
id="upload-dialog" tabindex="0"
[class.adf-upload-dialog--minimized]="isDialogMinimized"> class="adf-upload-dialog"
id="upload-dialog"
[class.adf-upload-dialog--minimized]="isDialogMinimized">
<header class="adf-upload-dialog__header"> <header class="adf-upload-dialog__header">
<button <button
mat-icon-button mat-icon-button
class="adf-upload-dialog__header-button" class="adf-upload-dialog__header-button"
[attr.data-automation-id]="'adf-upload-dialog__toggle-minimize'" [attr.data-automation-id]="'adf-upload-dialog__toggle-minimize'"
[attr.aria-label]="(isDialogMinimized ? [attr.aria-label]="(isDialogMinimized ?
'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_MAXIMIZE': 'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_MAXIMIZE':
'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_MINIMIZE') | translate" 'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_MINIMIZE') | translate"
[disabled]="isConfirmation" [disabled]="isConfirmation"
[attr.aria-expanded]="!isDialogMinimized" [attr.aria-expanded]="!isDialogMinimized"
(click)="toggleMinimized()"> (click)="toggleMinimized()">
<mat-icon class="adf-upload-dialog__header-button-icon" [adf-icon]="isDialogMinimized ? 'keyboard_arrow_up' : 'keyboard_arrow_down'" /> <mat-icon class="adf-upload-dialog__header-button-icon" [adf-icon]="isDialogMinimized ? 'keyboard_arrow_up' : 'keyboard_arrow_down'" />
</button> </button>
<span @if (uploadList.isUploadCancelled()) {
class="adf-upload-dialog__title" <span class="adf-upload-dialog__title">
*ngIf="!uploadList.isUploadCancelled()"> {{ 'FILE_UPLOAD.MESSAGES.UPLOAD_CANCELED' | translate }}
</span>
} @else {
<span class="adf-upload-dialog__title">
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_PROGRESS' {{ 'FILE_UPLOAD.MESSAGES.UPLOAD_PROGRESS'
| translate: { | translate: {
completed: totalCompleted, completed: totalCompleted,
total: filesUploadingList.length total: filesUploadingList.length
} }
}} }}
</span> </span>
}
<span </header>
class="adf-upload-dialog__title" @if (totalErrors) {
*ngIf="uploadList.isUploadCancelled()"> <section class="adf-upload-dialog__info">
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_CANCELED' | translate }} {{
</span> (totalErrors > 1
</header> ? 'FILE_UPLOAD.MESSAGES.UPLOAD_ERRORS'
: 'FILE_UPLOAD.MESSAGES.UPLOAD_ERROR')
<section class="adf-upload-dialog__info" | translate: { total: totalErrors }
*ngIf="totalErrors"> }}
{{
(totalErrors > 1
? 'FILE_UPLOAD.MESSAGES.UPLOAD_ERRORS'
: 'FILE_UPLOAD.MESSAGES.UPLOAD_ERROR')
| translate: { total: totalErrors }
}}
</section> </section>
}
<section class="adf-upload-dialog__content" <section class="adf-upload-dialog__content"
[class.adf-upload-dialog--padding]="isConfirmation" aria-live="polite"> [class.adf-upload-dialog--padding]="isConfirmation" aria-live="polite">
<adf-file-uploading-list <adf-file-uploading-list
class="adf-file-uploading-list" class="adf-file-uploading-list"
[class.adf-upload-dialog--hide]="isConfirmation" [class.adf-upload-dialog--hide]="isConfirmation"
#uploadList #uploadList
[files]="filesUploadingList"> [files]="filesUploadingList">
<ng-template let-file="$implicit"> <ng-template let-file="$implicit">
<adf-file-uploading-list-row <adf-file-uploading-list-row
[file]="file" [file]="file"
(cancel)="uploadList.cancelFile(file)" /> (cancel)="uploadList.cancelFile(file)" />
</ng-template> </ng-template>
</adf-file-uploading-list> </adf-file-uploading-list>
<div
<div aria-live="polite"
aria-live="polite" aria-labelledby="confirmationTitle"
aria-labelledby="confirmationTitle" aria-describedby="confirmationDescription"
aria-describedby="confirmationDescription" class="adf-upload-dialog__confirmation"
class="adf-upload-dialog__confirmation" [class.adf-upload-dialog--hide]="!isConfirmation">
[class.adf-upload-dialog--hide]="!isConfirmation"> <p role="heading" aria-level="2" id="confirmationTitle" class="adf-upload-dialog__confirmation--title">
<p role="heading" aria-level="2" id="confirmationTitle" class="adf-upload-dialog__confirmation--title"> {{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TITLE' | translate }}
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TITLE' | translate }} </p>
</p> <p id="confirmationDescription" class="adf-upload-dialog__confirmation--text">
<p id="confirmationDescription" class="adf-upload-dialog__confirmation--text"> {{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TEXT' | translate }}
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TEXT' | translate }} </p>
</p> </div>
</div> </section>
</section> @if (!isConfirmation) {
<footer class="adf-upload-dialog__actions">
<footer class="adf-upload-dialog__actions" *ngIf="!isConfirmation"> @if (canShowCancelAll()) {
<button <button
id="adf-upload-dialog-cancel-all" id="adf-upload-dialog-cancel-all"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.STOP_ALL' | translate" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.STOP_ALL' | translate"
color="primary" color="primary"
mat-button mat-button
*ngIf="canShowCancelAll()" (click)="toggleConfirmation()"
(click)="toggleConfirmation()"
>{{ 'ADF_FILE_UPLOAD.BUTTON.STOP_ALL' | translate }}</button> >{{ 'ADF_FILE_UPLOAD.BUTTON.STOP_ALL' | translate }}</button>
}
@if (canCloseDialog()) {
<button <button
id="adf-upload-dialog-close" id="adf-upload-dialog-close"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_CLOSE' | translate" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_CLOSE' | translate"
*ngIf="canCloseDialog()" mat-button
mat-button color="primary"
color="primary" (click)="close()"
(click)="close()"
>{{ 'ADF_FILE_UPLOAD.BUTTON.CLOSE' | translate }}</button> >{{ 'ADF_FILE_UPLOAD.BUTTON.CLOSE' | translate }}</button>
}
</footer> </footer>
}
@if (isConfirmation) {
<footer class="adf-upload-dialog__actions" <footer class="adf-upload-dialog__actions"
*ngIf="isConfirmation" cdkTrapFocus cdkTrapFocusAutoCapture> cdkTrapFocus cdkTrapFocusAutoCapture>
<button <button
id="adf-upload-dialog-cancel" id="adf-upload-dialog-cancel"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.CONFIRMATION.CANCEL' | translate" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.CONFIRMATION.CANCEL' | translate"
mat-button mat-button
(click)="cancelAllUploads()" (click)="cancelAllUploads()"
>{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CANCEL' | translate }}</button> >{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CANCEL' | translate }}</button>
<button
<button id="adf-upload-dialog-confirm"
id="adf-upload-dialog-confirm" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.CONFIRMATION.CONTINUE' | translate"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.CONFIRMATION.CONTINUE' | translate" mat-button
mat-button color="primary"
color="primary" (click)="toggleConfirmation()"
(click)="toggleConfirmation()" >{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CONTINUE' | translate }}</button>
>{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CONTINUE' | translate }}</button>
</footer> </footer>
}
</div> </div>
}
@@ -38,7 +38,7 @@ import { delay } from 'rxjs/operators';
import { UploadService } from '../../common/services/upload.service'; import { UploadService } from '../../common/services/upload.service';
import { FileModel, FileUploadStatus } from '../../common/models/file.model'; import { FileModel, FileUploadStatus } from '../../common/models/file.model';
import { FileUploadCompleteEvent, FileUploadDeleteEvent } from '../../common/events/file.event'; import { FileUploadCompleteEvent, FileUploadDeleteEvent } from '../../common/events/file.event';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { FileUploadingListRowComponent } from './file-uploading-list-row.component'; import { FileUploadingListRowComponent } from './file-uploading-list-row.component';
@@ -47,7 +47,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'adf-file-uploading-dialog', selector: 'adf-file-uploading-dialog',
imports: [CommonModule, MatButtonModule, TranslatePipe, IconModule, FileUploadingListComponent, FileUploadingListRowComponent, A11yModule], imports: [MatButtonModule, TranslatePipe, IconModule, FileUploadingListComponent, FileUploadingListRowComponent, A11yModule],
templateUrl: './file-uploading-dialog.component.html', templateUrl: './file-uploading-dialog.component.html',
styleUrls: ['./file-uploading-dialog.component.scss'], styleUrls: ['./file-uploading-dialog.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
@@ -56,7 +56,9 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
private readonly uploadService = inject(UploadService); private readonly uploadService = inject(UploadService);
private readonly changeDetector = inject(ChangeDetectorRef); private readonly changeDetector = inject(ChangeDetectorRef);
private readonly userPreferencesService = inject(UserPreferencesService); private readonly userPreferencesService = inject(UserPreferencesService);
private readonly elementRef = inject(ElementRef);
@ViewChild('uploadDialog', { read: ElementRef })
private readonly uploadDialogRef: ElementRef<HTMLElement> | undefined;
/** Dialog direction. Can be 'ltr' or 'rtl. */ /** Dialog direction. Can be 'ltr' or 'rtl. */
private direction: Direction = 'ltr'; private direction: Direction = 'ltr';
@@ -70,11 +72,11 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
/** Makes the dialog always visible even when there are no uploads. */ /** Makes the dialog always visible even when there are no uploads. */
@Input() @Input()
alwaysVisible: boolean = false; alwaysVisible = false;
/** Emitted when a file in the list has an error. */ /** Emitted when a file in the list has an error. */
@Output() @Output()
error: EventEmitter<any> = new EventEmitter(); error = new EventEmitter();
@HostBinding('attr.adfUploadDialogRight') @HostBinding('attr.adfUploadDialogRight')
public get isPositionRight(): boolean { public get isPositionRight(): boolean {
@@ -98,10 +100,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.dialogActive.pipe(delay(100), takeUntilDestroyed(this.destroyRef)).subscribe(() => { this.dialogActive.pipe(delay(100), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
const element: any = this.elementRef.nativeElement.querySelector('#upload-dialog'); this.uploadDialogRef?.nativeElement?.focus();
if (element) {
element.focus();
}
}); });
this.uploadService.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((fileList) => { this.uploadService.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((fileList) => {
@@ -1,92 +1,113 @@
<div class="adf-file-uploading-row" [attr.aria-label]="file.name + ' ' + file.status"> <div class="adf-file-uploading-row" [attr.aria-label]="file.name + ' ' + file.status">
<mat-icon *ngIf="mimeType === 'default'" matListItemIcon class="adf-file-uploading-row__type" adf-icon="insert_drive_file" /> @if (mimeType === 'default') {
<mat-icon matListItemIcon class="adf-file-uploading-row__type" adf-icon="insert_drive_file" />
} @else {
<mat-icon svgIcon="adf:{{ mimeType }}" aria-hidden="true" />
}
<mat-icon *ngIf="mimeType !== 'default'" svgIcon="adf:{{ mimeType }}" aria-hidden="true" /> <span
class="adf-file-uploading-row__name"
title="{{ file.name }}">
{{ file.name }}
</span>
<span @if (isUploadVersion()) {
class="adf-file-uploading-row__name" <span class="adf-file-uploading-row__version" tabindex="0" >
title="{{ file.name }}"> <mat-chip color="primary"
{{ file.name }} [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.VERSION' | translate: { version: versionNumber }"
</span> [title]="'version' + versionNumber"
>{{ versionNumber }}</mat-chip>
<span *ngIf="isUploadVersion()" class="adf-file-uploading-row__version" tabindex="0" >
<mat-chip color="primary"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.VERSION' | translate: { version: versionNumber }"
[title]="'version' + versionNumber"
>{{ versionNumber }}</mat-chip>
</span> </span>
}
@if (isUploading()) {
<div <div
tabindex="0" tabindex="0"
role="button" role="button"
#toggleIcon="toggleIcon" (mouseenter)="onToggleMouseEnter(toggleIconState)"
adf-toggle-icon (mouseleave)="onToggleMouseLeave(toggleIconState)"
(keyup.enter)="onCancel(file)" (focus)="onToggleFocus(toggleIconState)"
(click)="onCancel(file)" (blur)="onToggleBlur(toggleIconState)"
data-automation-id="cancel-upload-progress" (keyup.enter)="onCancel(file)"
*ngIf="isUploading()" (click)="onCancel(file)"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.STOP_FILE_UPLOAD' | translate: { file: file.name }" data-automation-id="cancel-upload-progress"
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.STOP_FILE_UPLOAD' | translate: { file: file.name }"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.STOP_FILE' | translate }}"> class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.STOP_FILE' | translate }}">
<span class="adf-file-uploading-row__status" *ngIf="!toggleIcon.isToggled"> @if (toggleIconState.isToggled) {
<mat-icon class="adf-file-uploading-row__action adf-file-uploading-row__action--cancel" adf-icon="clear" />
} @else {
<span class="adf-file-uploading-row__status">
{{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }} {{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }}
</span> </span>
}
<mat-icon *ngIf="toggleIcon.isToggled" class="adf-file-uploading-row__action adf-file-uploading-row__action--cancel" adf-icon="clear" />
</div> </div>
}
@if (isUploadComplete()) {
<button mat-icon-button <button mat-icon-button
adf-toggle-icon (mouseenter)="onToggleMouseEnter(toggleIconState)"
#toggleIcon="toggleIcon" (mouseleave)="onToggleMouseLeave(toggleIconState)"
*ngIf="isUploadComplete()" (focus)="onToggleFocus(toggleIconState)"
class="adf-file-uploading-row__group" (blur)="onToggleBlur(toggleIconState)"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.UPLOAD_SUCCESSFUL' | translate: { file: file.name }" class="adf-file-uploading-row__group"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.UPLOAD_SUCCESSFUL' | translate }}"> [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.UPLOAD_SUCCESSFUL' | translate: { file: file.name }"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.UPLOAD_SUCCESSFUL' | translate }}">
<mat-icon class="adf-file-uploading-row__status adf-file-uploading-row__status--done" adf-icon="check_circle" /> <mat-icon class="adf-file-uploading-row__status adf-file-uploading-row__status--done" adf-icon="check_circle" />
</button> </button>
}
@if (isUploadVersionComplete()) {
<div <div
*ngIf="isUploadVersionComplete()" class="adf-file-uploading-row__file-version"
class="adf-file-uploading-row__file-version" [attr.aria-label]="'ADF_FILE_UPLOAD.STATUS.FILE_DONE_STATUS' | translate"
[attr.aria-label]="'ADF_FILE_UPLOAD.STATUS.FILE_DONE_STATUS' | translate" >
> <mat-icon class="adf-file-uploading-row__status--done" adf-icon="check_circle" matListItemIcon />
<mat-icon class="adf-file-uploading-row__status--done" adf-icon="check_circle" matListItemIcon />
</div> </div>
}
@if (canCancelUpload()) {
<button <button
adf-toggle-icon (mouseenter)="onToggleMouseEnter(toggleIconCancelState)"
#toggleIconCancel="toggleIcon" (mouseleave)="onToggleMouseLeave(toggleIconCancelState)"
mat-icon-button (focus)="onToggleFocus(toggleIconCancelState)"
*ngIf="canCancelUpload()" (blur)="onToggleBlur(toggleIconCancelState)"
(click)="onCancel(file)" mat-icon-button
data-automation-id="cancel-upload-queue" (click)="onCancel(file)"
class="adf-file-uploading-row__group" data-automation-id="cancel-upload-queue"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.STOP_FILE' | translate }}" class="adf-file-uploading-row__group"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.STOP_FILE' | translate: { file: file.name }"> title="{{ 'ADF_FILE_UPLOAD.BUTTON.STOP_FILE' | translate }}"
<mat-icon *ngIf="!toggleIconCancel.isToggled" class="adf-file-uploading-row__status adf-file-uploading-row__status--pending" adf-icon="schedule" /> [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.STOP_FILE' | translate: { file: file.name }">
<mat-icon *ngIf="toggleIconCancel.isToggled" class="adf-file-uploading-row__action adf-file-uploading-row__action--remove" adf-icon="remove_circle" /> @if (toggleIconCancelState.isToggled) {
<mat-icon class="adf-file-uploading-row__action adf-file-uploading-row__action--remove" adf-icon="remove_circle" />
} @else {
<mat-icon class="adf-file-uploading-row__status adf-file-uploading-row__status--pending" adf-icon="schedule" />
}
</button> </button>
}
@if (isUploadError()) {
@let uploadErrorLabel = getFileUploadErrorKey(file.errorCode) | translate;
<div <div
role="status" role="status"
*ngIf="isUploadError()" class="adf-file-uploading-row__block adf-file-uploading-row__status--error">
class="adf-file-uploading-row__block adf-file-uploading-row__status--error"> <mat-icon
<mat-icon [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.UPLOAD_FILE_ERROR' | translate: { error: uploadErrorLabel }"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.UPLOAD_FILE_ERROR' | translate: { error: file.errorCode | adfFileUploadError }" [title]="uploadErrorLabel"
[title]="file.errorCode | adfFileUploadError" adf-icon="report_problem"
adf-icon="report_problem" matListItemIcon
matListItemIcon
/> />
</div> </div>
}
@if (showCancelledStatus()) {
<div <div
[attr.aria-label]="'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate" [attr.aria-label]="'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate"
role="status" role="status"
*ngIf="showCancelledStatus()" class="adf-file-uploading-row__block adf-file-uploading-row__status--cancelled">
class="adf-file-uploading-row__block adf-file-uploading-row__status--cancelled"> {{ 'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate }}
{{ 'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate }}
</div> </div>
}
</div> </div>
@@ -106,4 +106,118 @@ describe('FileUploadingListRowComponent', () => {
const cancelButton = getCancelButton(); const cancelButton = getCancelButton();
expect(cancelButton.title).toBe('ADF_FILE_UPLOAD.BUTTON.STOP_FILE'); expect(cancelButton.title).toBe('ADF_FILE_UPLOAD.BUTTON.STOP_FILE');
}); });
describe('getFileUploadErrorKey', () => {
it('should return correct key format for error code 500', () => {
const result = component.getFileUploadErrorKey(500);
expect(result).toBe('FILE_UPLOAD.ERRORS.500');
});
it('should return correct key format for error code 404', () => {
const result = component.getFileUploadErrorKey(404);
expect(result).toBe('FILE_UPLOAD.ERRORS.404');
});
it('should return correct key format for error code 403', () => {
const result = component.getFileUploadErrorKey(403);
expect(result).toBe('FILE_UPLOAD.ERRORS.403');
});
it('should return GENERIC key when errorCode is null', () => {
const result = component.getFileUploadErrorKey(null);
expect(result).toBe('FILE_UPLOAD.ERRORS.GENERIC');
});
it('should return GENERIC key when errorCode is undefined', () => {
const result = component.getFileUploadErrorKey(undefined);
expect(result).toBe('FILE_UPLOAD.ERRORS.GENERIC');
});
it('should return GENERIC key when errorCode is 0', () => {
const result = component.getFileUploadErrorKey(0);
expect(result).toBe('FILE_UPLOAD.ERRORS.GENERIC');
});
});
describe('Toggle Icon State', () => {
describe('onToggleMouseEnter', () => {
it('should set isToggled to true when not focused', () => {
component.toggleIconState = { isFocused: false, isToggled: false };
component.onToggleMouseEnter(component.toggleIconState);
expect(component.toggleIconState.isToggled).toBe(true);
});
it('should not change isToggled when already focused', () => {
component.toggleIconState = { isFocused: true, isToggled: false };
component.onToggleMouseEnter(component.toggleIconState);
expect(component.toggleIconState.isToggled).toBe(false);
});
});
describe('onToggleMouseLeave', () => {
it('should set isToggled to false when not focused', () => {
component.toggleIconState = { isFocused: false, isToggled: true };
component.onToggleMouseLeave(component.toggleIconState);
expect(component.toggleIconState.isToggled).toBe(false);
});
it('should not change state when focused but not toggled', () => {
component.toggleIconState = { isFocused: true, isToggled: false };
component.onToggleMouseLeave(component.toggleIconState);
expect(component.toggleIconState.isFocused).toBe(true);
expect(component.toggleIconState.isToggled).toBe(false);
});
it('should reset both isFocused and isToggled when both are true', () => {
component.toggleIconState = { isFocused: true, isToggled: true };
component.onToggleMouseLeave(component.toggleIconState);
expect(component.toggleIconState.isFocused).toBe(false);
expect(component.toggleIconState.isToggled).toBe(false);
});
});
describe('onToggleFocus', () => {
it('should set both isFocused and isToggled to true', () => {
component.toggleIconState = { isFocused: false, isToggled: false };
component.onToggleFocus(component.toggleIconState);
expect(component.toggleIconState.isFocused).toBe(true);
expect(component.toggleIconState.isToggled).toBe(true);
});
});
describe('onToggleBlur', () => {
it('should set both isFocused and isToggled to false', () => {
component.toggleIconState = { isFocused: true, isToggled: true };
component.onToggleBlur(component.toggleIconState);
expect(component.toggleIconState.isFocused).toBe(false);
expect(component.toggleIconState.isToggled).toBe(false);
});
});
describe('Multiple toggle states', () => {
it('should maintain independent state for toggleIconState and toggleIconCancelState', () => {
component.onToggleFocus(component.toggleIconState);
component.onToggleMouseEnter(component.toggleIconCancelState);
expect(component.toggleIconState.isFocused).toBe(true);
expect(component.toggleIconState.isToggled).toBe(true);
expect(component.toggleIconCancelState.isFocused).toBe(false);
expect(component.toggleIconCancelState.isToggled).toBe(true);
});
});
});
}); });
@@ -17,28 +17,21 @@
import { FileModel, FileUploadStatus } from '../../common/models/file.model'; import { FileModel, FileUploadStatus } from '../../common/models/file.model';
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatListModule } from '@angular/material/list'; import { MatListModule } from '@angular/material/list';
import { FileSizePipe, IconModule } from '@alfresco/adf-core'; import { FileSizePipe, IconModule } from '@alfresco/adf-core';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { ToggleIconDirective } from '../directives/toggle-icon.directive';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { FileUploadErrorPipe } from '../pipes/file-upload-error.pipe';
interface ToggleIconState {
isFocused: boolean;
isToggled: boolean;
}
@Component({ @Component({
selector: 'adf-file-uploading-list-row', selector: 'adf-file-uploading-list-row',
imports: [ imports: [IconModule, MatListModule, MatChipsModule, TranslatePipe, FileSizePipe, MatButtonModule],
CommonModule,
IconModule,
MatListModule,
MatChipsModule,
TranslatePipe,
ToggleIconDirective,
FileSizePipe,
MatButtonModule,
FileUploadErrorPipe
],
templateUrl: './file-uploading-list-row.component.html', templateUrl: './file-uploading-list-row.component.html',
styleUrls: ['./file-uploading-list-row.component.scss'], styleUrls: ['./file-uploading-list-row.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
@@ -50,10 +43,40 @@ export class FileUploadingListRowComponent {
@Output() @Output()
cancel = new EventEmitter<FileModel>(); cancel = new EventEmitter<FileModel>();
toggleIconState: ToggleIconState = { isFocused: false, isToggled: false };
toggleIconCancelState: ToggleIconState = { isFocused: false, isToggled: false };
onCancel(file: FileModel): void { onCancel(file: FileModel): void {
this.cancel.emit(file); this.cancel.emit(file);
} }
onToggleMouseEnter(state: ToggleIconState): void {
if (!state.isFocused) {
state.isToggled = true;
}
}
onToggleMouseLeave(state: ToggleIconState): void {
if (!state.isFocused) {
state.isToggled = false;
}
if (state.isFocused && state.isToggled) {
state.isFocused = false;
state.isToggled = false;
}
}
onToggleFocus(state: ToggleIconState): void {
state.isFocused = true;
state.isToggled = true;
}
onToggleBlur(state: ToggleIconState): void {
state.isFocused = false;
state.isToggled = false;
}
showCancelledStatus(): boolean { showCancelledStatus(): boolean {
return ( return (
this.file.status === FileUploadStatus.Cancelled || this.file.status === FileUploadStatus.Cancelled ||
@@ -93,4 +116,8 @@ export class FileUploadingListRowComponent {
isUploadVersionComplete(): boolean { isUploadVersionComplete(): boolean {
return this.file?.status === FileUploadStatus.Complete && this.isUploadVersion(); return this.file?.status === FileUploadStatus.Complete && this.isUploadVersion();
} }
getFileUploadErrorKey(errorCode?: number | null): string {
return `FILE_UPLOAD.ERRORS.${errorCode || 'GENERIC'}`;
}
} }
@@ -1,6 +1,7 @@
<div class="upload-list"> <div class="upload-list">
@for (file of files; track file.id) {
<ng-template <ng-template
ngFor [ngTemplateOutlet]="template"
[ngForOf]="files" [ngTemplateOutletContext]="{ $implicit: file }" />
[ngForTemplate]="template" /> }
</div> </div>
@@ -20,11 +20,11 @@ import { UploadService } from '../../common/services/upload.service';
import { FileModel, FileUploadStatus } from '../../common/models/file.model'; import { FileModel, FileUploadStatus } from '../../common/models/file.model';
import { Component, ContentChild, Input, Output, TemplateRef, EventEmitter, inject } from '@angular/core'; import { Component, ContentChild, Input, Output, TemplateRef, EventEmitter, inject } from '@angular/core';
import { CommonModule } from '@angular/common'; import { NgTemplateOutlet } from '@angular/common';
@Component({ @Component({
selector: 'adf-file-uploading-list', selector: 'adf-file-uploading-list',
imports: [CommonModule], imports: [NgTemplateOutlet],
templateUrl: './file-uploading-list.component.html', templateUrl: './file-uploading-list.component.html',
styleUrls: ['./file-uploading-list.component.scss'] styleUrls: ['./file-uploading-list.component.scss']
}) })
@@ -1,70 +1,74 @@
<form class="adf-upload-button-file-container"> <form class="adf-upload-button-file-container">
<!--Files Upload--> <!--Files Upload-->
<div *ngIf="!uploadFolders"> @if (!uploadFolders) {
<div>
<!--Single Files Upload--> <!--Single Files Upload-->
<ng-container *ngIf="!multipleFiles"> @if (!multipleFiles) {
<input
class="adf-upload-button-file-container-upload-single-file"
id="upload-single-file"
data-automation-id="upload-single-file"
[type]="file ? 'button' : 'file'"
name="uploadFiles"
accept="{{acceptedFilesType}}"
[attr.disabled]="isButtonDisabled()"
[title]="tooltip"
(change)="onFilesAdded($event)"
(click)="onClickUploadButton()"/>
<label tabindex="0" (keydown.enter)="onClickUploadButton()" for="upload-single-file" class="adf-upload-button-label">
<mat-icon class="adf-upload-button-icon" adf-icon="file_upload" />
<span id="upload-single-file-label" *ngIf="!staticTitle">
{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' | translate }}</span>
<span id="upload-single-file-label-static" *ngIf="staticTitle">
{{ staticTitle }}</span>
</label>
</ng-container>
<!--Multiple Files Upload-->
<ng-container *ngIf="multipleFiles">
<input
id="upload-multiple-files"
data-automation-id="upload-multiple-files"
type="file"
name="uploadFiles"
multiple="multiple"
accept="{{acceptedFilesType}}"
[attr.disabled]="isButtonDisabled()"
[title]="tooltip"
(change)="onFilesAdded($event)"/>
<label for="upload-multiple-files" class="adf-upload-button-label">
<mat-icon class="adf-upload-button-icon" adf-icon="file_upload" />
<span id="upload-multiple-file-label" *ngIf="!staticTitle">
{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' | translate }}</span>
<span id="upload-multiple-file-label-static" *ngIf="staticTitle">
{{ staticTitle }}</span>
</label>
</ng-container>
</div>
<!--Folders Upload-->
<ng-container *ngIf="uploadFolders">
<input <input
id="uploadFolder" class="adf-upload-button-file-container-upload-single-file"
data-automation-id="uploadFolder" id="upload-single-file"
type="file" data-automation-id="upload-single-file"
name="uploadFiles" [type]="file ? 'button' : 'file'"
multiple="multiple" name="uploadFiles"
accept="{{acceptedFilesType}}" accept="{{acceptedFilesType}}"
webkitdirectory directory [attr.disabled]="isButtonDisabled()"
[attr.disabled]="isButtonDisabled()" [title]="tooltip"
[title]="tooltip" (change)="onFilesAdded($event)"
(change)="onDirectoryAdded($event)"/> (click)="onClickUploadButton()"/>
<label for="uploadFolder" data-automation-id="uploadFolder" class="adf-upload-button-label"> <label tabindex="0" (keydown.enter)="onClickUploadButton()" for="upload-single-file" class="adf-upload-button-label">
<mat-icon class="adf-upload-button-icon" adf-icon="file_upload" /> <mat-icon class="adf-upload-button-icon" adf-icon="file_upload" />
<span id="uploadFolder-label" *ngIf="!staticTitle">
{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' | translate }}</span> @if (staticTitle) {
<span id="uploadFolder-label-static" *ngIf="staticTitle"> <span id="upload-single-file-label-static"> {{ staticTitle }}</span>
{{ staticTitle }}</span> } @else {
<span id="upload-single-file-label">{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' | translate }}</span>
}
</label> </label>
</ng-container> }
<!--Multiple Files Upload-->
@if (multipleFiles) {
<input
id="upload-multiple-files"
data-automation-id="upload-multiple-files"
type="file"
name="uploadFiles"
multiple="multiple"
accept="{{acceptedFilesType}}"
[attr.disabled]="isButtonDisabled()"
[title]="tooltip"
(change)="onFilesAdded($event)"/>
<label for="upload-multiple-files" class="adf-upload-button-label">
<mat-icon class="adf-upload-button-icon" adf-icon="file_upload" />
@if (staticTitle) {
<span id="upload-multiple-file-label-static"> {{ staticTitle }}</span>
} @else {
<span id="upload-multiple-file-label">{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' | translate }}</span>
}
</label>
}
</div>
}
<!--Folders Upload-->
@if (uploadFolders) {
<input
id="uploadFolder"
data-automation-id="uploadFolder"
type="file"
name="uploadFiles"
multiple="multiple"
accept="{{acceptedFilesType}}"
webkitdirectory directory
[attr.disabled]="isButtonDisabled()"
[title]="tooltip"
(change)="onDirectoryAdded($event)"/>
<label for="uploadFolder" data-automation-id="uploadFolder" class="adf-upload-button-label">
<mat-icon class="adf-upload-button-icon" adf-icon="file_upload" />
@if (staticTitle) {
<span id="uploadFolder-label-static"> {{ staticTitle }}</span>
} @else {
<span id="uploadFolder-label">{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' | translate }}</span>
}
</label>
}
</form> </form>
@@ -24,13 +24,13 @@ import { Node } from '@alfresco/js-api';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { PermissionModel } from '../../document-list/models/permissions.model'; import { PermissionModel } from '../../document-list/models/permissions.model';
import { UploadBase } from './base-upload/upload-base'; import { UploadBase } from './base-upload/upload-base';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
@Component({ @Component({
selector: 'adf-upload-button', selector: 'adf-upload-button',
imports: [CommonModule, MatButtonModule, TranslatePipe, IconModule], imports: [MatButtonModule, TranslatePipe, IconModule],
templateUrl: './upload-button.component.html', templateUrl: './upload-button.component.html',
styleUrls: ['./upload-button.component.scss'], styleUrls: ['./upload-button.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
@@ -1,86 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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, ViewChild } from '@angular/core';
import { ToggleIconDirective } from './toggle-icon.directive';
import { ComponentFixture, TestBed } from '@angular/core/testing';
@Component({
selector: 'adf-test-component',
imports: [ToggleIconDirective],
template: ` <button id="testButton" adf-toggle-icon>test</button> `
})
class TestComponent {
@ViewChild(ToggleIconDirective)
directive: ToggleIconDirective;
}
describe('ToggleIconDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestComponent]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should set toggle to true on mouseenter', () => {
const button: HTMLElement = fixture.nativeElement.querySelector('#testButton');
button.dispatchEvent(new MouseEvent('mouseenter'));
expect(component.directive.isToggled).toBe(true);
});
it('should set toggle to false on mouseleave if element is not focused', () => {
const button: HTMLElement = fixture.nativeElement.querySelector('#testButton');
button.dispatchEvent(new MouseEvent('mouseleave'));
expect(component.directive.isToggled).toBe(false);
});
it('should set toggle and focus to false on mouseleave when element is focused', () => {
const button: HTMLElement = fixture.nativeElement.querySelector('#testButton');
button.dispatchEvent(new Event('focus'));
expect(component.directive.isToggled).toBe(true);
button.dispatchEvent(new MouseEvent('mouseleave'));
expect(component.directive.isToggled).toBe(false);
expect(component.directive.isFocused).toBe(false);
});
it('should set toggle and focus to true when element is focused', () => {
const button: HTMLElement = fixture.nativeElement.querySelector('#testButton');
button.dispatchEvent(new Event('focus'));
expect(component.directive.isToggled).toBe(true);
expect(component.directive.isFocused).toBe(true);
});
it('should set toggle and focus to true when element blur event', () => {
const button: HTMLElement = fixture.nativeElement.querySelector('#testButton');
button.dispatchEvent(new Event('focus'));
button.dispatchEvent(new Event('blur'));
expect(component.directive.isToggled).toBe(false);
expect(component.directive.isFocused).toBe(false);
});
});
@@ -1,62 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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 { Directive, HostListener } from '@angular/core';
@Directive({
selector: '[adf-toggle-icon]',
exportAs: 'toggleIcon'
})
export class ToggleIconDirective {
private isFocus: boolean = false;
private toggle: boolean = false;
@HostListener('mouseenter') onMouseEnter() {
if (!this.isFocus) {
this.toggle = true;
}
}
@HostListener('mouseleave') onMouseLeave() {
if (!this.isFocus) {
this.toggle = false;
}
if (this.isFocus && this.toggle) {
this.isFocus = false;
this.toggle = false;
}
}
@HostListener('focus') onFocus() {
this.isFocus = true;
this.toggle = true;
}
@HostListener('blur') onBlur() {
this.isFocus = false;
this.toggle = false;
}
get isToggled(): boolean {
return this.toggle;
}
get isFocused(): boolean {
return this.isFocus;
}
}
@@ -1,52 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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 { NoopTranslateModule } from '@alfresco/adf-core';
import { FileUploadErrorPipe } from './file-upload-error.pipe';
import { TestBed } from '@angular/core/testing';
describe('FileUploadErrorPipe', () => {
let pipe: FileUploadErrorPipe;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NoopTranslateModule],
providers: [FileUploadErrorPipe]
});
pipe = TestBed.inject(FileUploadErrorPipe);
});
it('should return generic message when error code is null', () => {
expect(pipe.transform(null)).toBe('FILE_UPLOAD.ERRORS.GENERIC');
});
it('should return 500 message', () => {
expect(pipe.transform(500)).toBe('FILE_UPLOAD.ERRORS.500');
});
it('should return 504 message', () => {
expect(pipe.transform(504)).toBe('FILE_UPLOAD.ERRORS.504');
});
it('should return 403 message', () => {
expect(pipe.transform(403)).toBe('FILE_UPLOAD.ERRORS.403');
});
it('should return 404 message', () => {
expect(pipe.transform(404)).toBe('FILE_UPLOAD.ERRORS.404');
});
});
@@ -1,31 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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 { Pipe, PipeTransform, inject } from '@angular/core';
import { TranslationService } from '@alfresco/adf-core';
@Pipe({
name: 'adfFileUploadError',
pure: true
})
export class FileUploadErrorPipe implements PipeTransform {
private readonly translation = inject(TranslationService);
transform(errorCode: number): string {
return this.translation.instant(`FILE_UPLOAD.ERRORS.${errorCode || 'GENERIC'}`);
}
}
@@ -24,9 +24,6 @@ export * from './components/file-uploading-list-row.component';
export * from './components/upload-files.event'; export * from './components/upload-files.event';
export * from './directives/file-draggable.directive'; export * from './directives/file-draggable.directive';
export * from './directives/toggle-icon.directive';
export * from './pipes/file-upload-error.pipe';
export * from './components/base-upload/upload-base'; export * from './components/base-upload/upload-base';
@@ -22,14 +22,10 @@ import { FileUploadingListComponent } from './components/file-uploading-list.com
import { UploadButtonComponent } from './components/upload-button.component'; import { UploadButtonComponent } from './components/upload-button.component';
import { UploadVersionButtonComponent } from './components/upload-version-button.component'; import { UploadVersionButtonComponent } from './components/upload-version-button.component';
import { UploadDragAreaComponent } from './components/upload-drag-area.component'; import { UploadDragAreaComponent } from './components/upload-drag-area.component';
import { FileUploadErrorPipe } from './pipes/file-upload-error.pipe';
import { FileDraggableDirective } from './directives/file-draggable.directive'; import { FileDraggableDirective } from './directives/file-draggable.directive';
import { ToggleIconDirective } from './directives/toggle-icon.directive';
export const CONTENT_UPLOAD_DIRECTIVES = [ export const CONTENT_UPLOAD_DIRECTIVES = [
FileUploadErrorPipe,
FileDraggableDirective, FileDraggableDirective,
ToggleIconDirective,
UploadDragAreaComponent, UploadDragAreaComponent,
UploadButtonComponent, UploadButtonComponent,
UploadVersionButtonComponent, UploadVersionButtonComponent,
@@ -48,6 +48,7 @@ import { AlfrescoViewerComponent } from './alfresco-viewer.component';
import { RenditionService } from '../../common/services/rendition.service'; import { RenditionService } from '../../common/services/rendition.service';
import { NodeActionsService } from '../../document-list/services/node-actions.service'; import { NodeActionsService } from '../../document-list/services/node-actions.service';
import { provideApiTesting } from '../../testing/providers'; import { provideApiTesting } from '../../testing/providers';
import { MatIconTestingModule } from '@angular/material/icon/testing';
@Component({ @Component({
selector: 'adf-viewer-container-toolbar', selector: 'adf-viewer-container-toolbar',
@@ -179,7 +180,7 @@ describe('AlfrescoViewerComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopAuthModule, NoopTranslateModule, MatDialogModule, AlfrescoViewerComponent], imports: [NoopAuthModule, MatIconTestingModule, NoopTranslateModule, MatDialogModule, AlfrescoViewerComponent],
providers: [ providers: [
provideApiTesting(), provideApiTesting(),
{ {
@@ -1,30 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed 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.
*/
export class DecimalNumberModel {
minIntegerDigits: number;
minFractionDigits: number;
maxFractionDigits: number;
constructor(obj?: any) {
if (obj) {
this.minIntegerDigits = obj.minIntegerDigits;
this.minFractionDigits = obj.minFractionDigits;
this.maxFractionDigits = obj.maxFractionDigits;
}
}
}
-1
View File
@@ -18,6 +18,5 @@
export * from './comment.model'; export * from './comment.model';
export * from './pagination.model'; export * from './pagination.model';
export * from './request-pagination.model'; export * from './request-pagination.model';
export * from './decimal-number.model';
export * from './general-user.model'; export * from './general-user.model';
export * from './path.model'; export * from './path.model';
@@ -19,7 +19,12 @@ import { DecimalPipe } from '@angular/common';
import { Pipe, PipeTransform, inject } from '@angular/core'; import { Pipe, PipeTransform, inject } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService } from '../common/services/user-preferences.service'; import { UserPreferencesService } from '../common/services/user-preferences.service';
import { DecimalNumberModel } from '../models/decimal-number.model';
export interface DecimalNumberModel {
minIntegerDigits?: number;
minFractionDigits?: number;
maxFractionDigits?: number;
}
@Pipe({ @Pipe({
name: 'adfDecimalNumber', name: 'adfDecimalNumber',
@@ -33,6 +33,7 @@ import { ViewerWithCustomToolbarActionsComponent } from './mock/adf-viewer-conta
import { ViewerWithCustomToolbarComponent } from './mock/adf-viewer-container-toolbar.component.mock'; import { ViewerWithCustomToolbarComponent } from './mock/adf-viewer-container-toolbar.component.mock';
import { ViewerComponent } from './viewer.component'; import { ViewerComponent } from './viewer.component';
import { ThumbnailService } from '../../common/services/thumbnail.service'; import { ThumbnailService } from '../../common/services/thumbnail.service';
import { MatIconTestingModule } from '@angular/material/icon/testing';
@Component({ @Component({
selector: 'adf-dialog-dummy', selector: 'adf-dialog-dummy',
@@ -71,6 +72,7 @@ describe('ViewerComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
MatIconTestingModule,
ViewerWithCustomToolbarComponent, ViewerWithCustomToolbarComponent,
ViewerWithCustomSidebarComponent, ViewerWithCustomSidebarComponent,
ViewerWithCustomOpenWithComponent, ViewerWithCustomOpenWithComponent,
+468 -708
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -147,6 +147,8 @@
"nock": "13.5.5", "nock": "13.5.5",
"nx": "22.4.1", "nx": "22.4.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"rimraf": "^6.1.2", "rimraf": "^6.1.2",
"sass-loader": "16.0.7", "sass-loader": "16.0.7",
"spdx-license-list": "^6.11.0", "spdx-license-list": "^6.11.0",
@@ -155,11 +157,11 @@
"stylelint-config-standard-scss": "^13.1.0", "stylelint-config-standard-scss": "^13.1.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "5.8.3", "typescript": "5.8.3",
"webpack": "5.104.1" "webpack": "5.105.3"
}, },
"overrides": { "overrides": {
"@storybook/angular": { "@storybook/angular": {
"webpack": "5.101.3" "webpack": "5.105.3"
} }
}, },
"license": "Apache-2.0", "license": "Apache-2.0",