Compare commits

..

No commits in common. "develop" and "8.1.0-14755969498" have entirely different histories.

92 changed files with 6633 additions and 9644 deletions

View File

@ -267,9 +267,9 @@
}
},
"node_modules/undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"version": "5.28.5",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
"integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
@ -495,9 +495,9 @@
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"version": "5.28.5",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
"integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
"requires": {
"@fastify/busboy": "^2.0.0"
}

View File

@ -26,7 +26,7 @@ runs:
cache-dependency-path: package-lock.json
- name: get latest tag sha
id: tag-sha
uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@95f68dc050fba62b3331d9b47bc659526cdfb343 # v8.21.1
uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@247f59bac145315d38078f8954c763e0db57d5f1 # v8.18.3
# CACHE
- name: Node Modules cache
id: node-modules-cache

View File

@ -30,7 +30,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
# Override language selection by uncommenting this and choosing your languages
with:
languages: javascript
@ -39,7 +39,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -53,4 +53,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16

View File

@ -64,7 +64,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Ensure SHA pinned actions
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@fc87bb5b5a97953d987372e74478de634726b3e5 # v3.0.25
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@4830be28ce81da52ec70d65c552a7403821d98d4 # v3.0.23
- name: Check package-lock.json version
run: |
@ -84,10 +84,10 @@ jobs:
fetch-depth: 0
- name: Get branch name
uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@95f68dc050fba62b3331d9b47bc659526cdfb343 # v8.21.1
uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@09293790e3d482b6376a602f607e009ef1025698 # v8.19.0
- name: Save commit message
uses: Alfresco/alfresco-build-tools/.github/actions/get-commit-message@95f68dc050fba62b3331d9b47bc659526cdfb343 # v8.21.1
uses: Alfresco/alfresco-build-tools/.github/actions/get-commit-message@09293790e3d482b6376a602f607e009ef1025698 # v8.19.0
- name: ci:force flag parser
shell: bash
@ -277,7 +277,7 @@ jobs:
uses: ./.github/actions/slack-group-area
with:
affected: ${{ steps.e2e-result.outputs.result }}
- uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
- uses: slackapi/slack-github-action@485a9d42d3a73031f12ec201c457e2162c45d02d # v2.0.0
name: Nofify QA failure
if: ${{ github.event_name == 'schedule' && contains(needs.*.result, 'failure') }}
env:

View File

@ -229,7 +229,7 @@ jobs:
needs: [release-storybook, release-npm, npm-check-bundle]
steps:
- uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
- uses: slackapi/slack-github-action@485a9d42d3a73031f12ec201c457e2162c45d02d # v2.0.0
name: Nofify FE eng-guild-front-end workflow failed
if: ${{ contains(toJson(needs.*.result), 'failure') }}
env:

2
.nvmrc
View File

@ -1 +1 @@
22.14.0
20.18.1

View File

@ -8,6 +8,6 @@
"source": "/**/**/i18n/en.json",
"translation": "/%original_path%/%two_letters_code%.%file_extension%",
"export_only_approved": "true",
"update_option": "update_without_changes"
"update_option": "update_as_unapproved"
}
]

View File

@ -23,10 +23,10 @@ Manages tags in Content Services.
- _nodeId:_ `string` - Id of node to which tags should be assigned.
- _tags:_ `TagBody[]` - List of tags to create and assign or just assign if they already exist.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagPaging.md)`|`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`>` - Just linked tags to node or single tag if linked only one tag.
- **createTags**(tags: `TagBody[]`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagEntry`](../../../lib/js-api/src/api/content-rest-api/docs/TagsApi.md#TagEntry) `|` [`TagPaging`](../../../lib/js-api/src/api/content-rest-api/docs/TagsApi.md#TagPaging)`>`<br/>
- **createTags**(tags: `TagBody[]`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`[]>`<br/>
Creates tags.
- _tags:_ `TagBody[]` - list of tags to create.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagEntry`](../../../lib/js-api/src/api/content-rest-api/docs/TagsApi.md#TagEntry) `|` [`TagPaging`](../../../lib/js-api/src/api/content-rest-api/docs/TagsApi.md#TagPaging)`>` - Created tags.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`[]>` - Created tags.
- **deleteTag**(tagId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<void>`<br/>
Deletes a tag with tagId. This will cause the tag to be removed from all nodes. You must have admin rights to delete a tag.
- _tagId:_ `string` - of the tag to be deleted

View File

@ -4,18 +4,16 @@ Added: v4.1.0
---
## Form Extensibility for APA Form Widget
This page describes how you can customize ADF forms to your own specification.
## Contents
There are two ways to customize the form
- [Replace default form widgets with custom components](#replace-default-form-widgets-with-apa-form-widgets)
- [Replace custom form widget with custom components](#replace-custom-form-widgets-with-custom-components)
## Replace default form widgets with APA form widgets
This is an example of replacing the standard `Text` with a custom component for all APA forms rendered within the `<adf-form>` component.
This is an example of replacing the standard `Text` [widget](../../lib/testing/src/lib/core/pages/form/widgets/widget.ts) with a custom component for all APA forms rendered within the `<adf-form>` component.
1. Create a simple form with some `Text` widgets:
@ -26,10 +24,8 @@ This is an example of replacing the standard `Text` with a custom component for
```ts
import { Component } from '@angular/core';
import { WidgetComponent } from '@alfresco/adf-core';
@Component({
selector: 'custom-editor',
standalone: true,
template: `
<div style="color: red">Look, I'm a APA custom editor!</div>
`
@ -37,14 +33,40 @@ This is an example of replacing the standard `Text` with a custom component for
export class CustomEditorComponent extends WidgetComponent {}
```
2. Import the [`FormRenderingService`](../core/services/form-rendering.service.md) in the feature module, or application module (recommended: `ProcessServicesExtensionModule`), and override the default mapping:
2. Add it to the application module or any custom module that is imported into the application one:
```ts
import { NgModule } from '@angular/core';
import { CustomEditorComponent } from './custom-editor.component';
import { FormRenderingService } from '@alfresco/adf-core';
@NgModule({...})
export class ProcessServicesExtensionModule {
@NgModule({
declarations: [ CustomEditorComponent ],
exports: [ CustomEditorComponent ]
})
export class CustomEditorsModule {}
```
3. Every custom widget component should be added into the the collections `declarations` and `exports`. If you decided to store custom widgets in a separate dedicated module (and optionally as a separate re-distributable library) don't forget to import it into the main application:
```ts
@NgModule({
imports: [
// ...
CustomEditorsModule
// ...
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {}
```
4. Import the [`FormRenderingService`](../core/services/form-rendering.service.md) into any of your Views and override the default mapping, for example:
```ts
import { Component } from '@angular/core';
import { CustomEditorComponent } from './custom-editor.component';
@Component({...})
export class MyView {
constructor(formRenderingService: FormRenderingService) {
this.formRenderingService.register({
'text': () => CustomEditorComponent
@ -53,12 +75,10 @@ This is an example of replacing the standard `Text` with a custom component for
}
```
> [!IMPORTANT]
> The widget should be registered outside the custom widget component, otherwise the widget will not be registered correctly.
5. At runtime the form should look similar to the following:
At runtime the form should look similar to the following:
![custom text widget](../docassets/images/apa-simple-override-form.png)
![custom text widget](../docassets/images/apa-simple-override-form.png)
## Replace custom form widgets with custom components
@ -85,30 +105,54 @@ When displayed in a task, the field will look similar to the following:
To render the missing content:
1. Create a standalone Angular component:
1. Create an Angular component:
```ts
import { Component } from '@angular/core';
import { WidgetComponent } from '@alfresco/adf-core';
@Component({
selector: 'app-demo-widget',
standalone: true,
template: `<div style="color: green">ADF version of custom form widget</div>`
})
export class DemoWidgetComponent extends WidgetComponent {}
```
2. Import the [`FormRenderingService`](../core/services/form-rendering.service.md) in the feature module, or application module (recommended: `ProcessServicesExtensionModule`), and override the default mapping:
2. Place it inside the custom module:
```ts
import { NgModule } from '@angular/core';
import { DemoWidgetComponent } from './demo-widget.component';
import { FormRenderingService } from '@alfresco/adf-core';
@NgModule({/*...*/})
export class ProcessServicesExtensionModule {
@NgModule({
declarations: [ DemoWidgetComponent ],
exports: [ DemoWidgetComponent ]
})
export class CustomWidgetsModule {}
```
3. Import it into your Application Module:
```ts
@NgModule({
imports: [
// ...
CustomWidgetsModule
// ...
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {}
```
4. Import the [`FormRenderingService`](../core/services/form-rendering.service.md) in any of your Views and provide the new mapping:
```ts
import { Component } from '@angular/core';
import { DemoWidgetComponent } from './demo-widget.component';
@Component({...})
export class MyView {
constructor(formRenderingService: FormRenderingService) {
formRenderingService.register({
this.formRenderingService.register({
'custom-editor': () => DemoWidgetComponent
});
}
@ -119,9 +163,6 @@ At runtime you should now see your custom Angular component rendered in place of
![adf form widget runtime](../docassets/images/apa-resolved-widget.png)
> [!IMPORTANT]
> The widget should be registered outside the custom widget component, otherwise the widget will not be registered correctly.
## See Also
- [Extensibility](./extensibility.md)

View File

@ -9,7 +9,7 @@
"version": "8.0.0",
"license": "Apache-2.0",
"dependencies": {
"@alfresco/js-api": ">=8.0.0-alpha.7",
"@alfresco/js-api": ">=8.0.0-alpha.7-0",
"commander": "^6.2.1",
"ejs": "^3.1.9",
"license-checker": "^25.0.1",
@ -39,27 +39,6 @@
"tslib": "^2.6.1"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@paralleldrive/cuid2": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@types/ejs": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz",
@ -456,18 +435,14 @@
}
},
"node_modules/formidable": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
"license": "MIT",
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz",
"integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"dezalgo": "^1.0.4",
"hexoid": "^1.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
@ -601,6 +576,14 @@
"node": ">= 0.4"
}
},
"node_modules/hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
"engines": {
"node": ">=8"
}
},
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",

View File

@ -711,10 +711,10 @@
},
"NODE_FAVORITE_DIRECTIVE": {
"MESSAGES": {
"NODE_ADDED": "Añadido {{ name }} a favoritos",
"NODES_ADDED": "Añadidos {{ number }} elementos a favoritos",
"NODE_REMOVED": "Eliminado {{ name }} de favoritos",
"NODES_REMOVED": "Eliminado {{ number }} elementos de favoritos"
"NODE_ADDED": "Added {{ name }} to favorites",
"NODES_ADDED": "Added {{ number }} items to favorites",
"NODE_REMOVED": "Removed {{ name }} from favorites",
"NODES_REMOVED": "Removed {{ number }} items from favorites"
}
}
}

View File

@ -711,10 +711,10 @@
},
"NODE_FAVORITE_DIRECTIVE": {
"MESSAGES": {
"NODE_ADDED": "Ajout de {{ name }} aux favoris",
"NODES_ADDED": "Ajout des éléments {{ number }} aux favoris",
"NODE_REMOVED": "Suppression de {{ name }} des favoris",
"NODES_REMOVED": "Les éléments {{ number }} supprimés des favoris"
"NODE_ADDED": "Added {{ name }} to favorites",
"NODES_ADDED": "Added {{ number }} items to favorites",
"NODE_REMOVED": "Removed {{ name }} from favorites",
"NODES_REMOVED": "Removed {{ number }} items from favorites"
}
}
}

View File

@ -711,10 +711,10 @@
},
"NODE_FAVORITE_DIRECTIVE": {
"MESSAGES": {
"NODE_ADDED": "Aggiunto {{ name }} ai preferiti",
"NODES_ADDED": "Aggiunti {{ number }} elementi ai preferiti",
"NODE_REMOVED": "Rimosso {{ name }} dai preferiti",
"NODES_REMOVED": "Rimosso {{ number }} elementi dai preferiti"
"NODE_ADDED": "Added {{ name }} to favorites",
"NODES_ADDED": "Added {{ number }} items to favorites",
"NODE_REMOVED": "Removed {{ name }} from favorites",
"NODES_REMOVED": "Removed {{ number }} items from favorites"
}
}
}

View File

@ -711,10 +711,10 @@
},
"NODE_FAVORITE_DIRECTIVE": {
"MESSAGES": {
"NODE_ADDED": "Dodano {{ name }} do ulubionych",
"NODES_ADDED": "Dodano {{ number }} elementy do ulubionych",
"NODE_REMOVED": "Usunięto {{ name }} z ulubionych",
"NODES_REMOVED": "Usunięto {{ number }} elementy z ulubionych"
"NODE_ADDED": "Added {{ name }} to favorites",
"NODES_ADDED": "Added {{ number }} items to favorites",
"NODE_REMOVED": "Removed {{ name }} from favorites",
"NODES_REMOVED": "Removed {{ number }} items from favorites"
}
}
}

View File

@ -711,10 +711,10 @@
},
"NODE_FAVORITE_DIRECTIVE": {
"MESSAGES": {
"NODE_ADDED": "Adicionado {{ name }} aos favoritos",
"NODES_ADDED": "Adicionados {{ number }} itens aos favoritos",
"NODE_REMOVED": "Removido {{ name }} dos favoritos",
"NODES_REMOVED": "Removido {{ number }} itens dos favoritos"
"NODE_ADDED": "Added {{ name }} to favorites",
"NODES_ADDED": "Added {{ number }} items to favorites",
"NODE_REMOVED": "Removed {{ name }} from favorites",
"NODES_REMOVED": "Removed {{ number }} items from favorites"
}
}
}

View File

@ -89,7 +89,7 @@ describe('TagService', () => {
describe('createTags', () => {
it('should call createTags on tagsApi', () => {
spyOn(service.tagsApi, 'createTags').and.returnValue(Promise.resolve({}));
spyOn(service.tagsApi, 'createTags').and.returnValue(Promise.resolve([]));
const tag1 = new TagBody();
tag1.tag = 'Some tag 1';
const tag2 = new TagBody();
@ -101,17 +101,19 @@ describe('TagService', () => {
});
it('should emit refresh when tags creation is success', async () => {
const tag: TagEntry = {
entry: {
id: 'Some id 1',
tag: 'Some tag 1'
const tags: TagEntry[] = [
{
entry: {
id: 'Some id 1',
tag: 'Some tag 1'
}
}
};
];
spyOn(service.refresh, 'emit');
spyOn(service.tagsApi, 'createTags').and.returnValue(Promise.resolve(tag));
spyOn(service.tagsApi, 'createTags').and.returnValue(Promise.resolve(tags));
await service.createTags([]).toPromise();
expect(service.refresh.emit).toHaveBeenCalledWith(tag);
expect(service.refresh.emit).toHaveBeenCalledWith(tags);
});
});

View File

@ -99,7 +99,7 @@ export class TagService {
* @param tags list of tags to create.
* @returns Created tags.
*/
createTags(tags: TagBody[]): Observable<TagEntry | TagPaging> {
createTags(tags: TagBody[]): Observable<TagEntry[]> {
return from(this.tagsApi.createTags(tags)).pipe(tap((tagEntries) => this.refresh.emit(tagEntries)));
}

View File

@ -2,15 +2,15 @@
@use '@angular/material' as mat;
@mixin adf-breadcrumb-theme($theme) {
$config: mat.m2-get-color-config($theme);
$config: mat.get-color-config($theme);
$foreground-palette: map.get($config, foreground);
$primary-palette: map.get($config, primary);
$text-color: mat.m2-get-color-from-palette($foreground-palette, text);
$primary: mat.m2-get-color-from-palette($primary-palette, text);
$text-color: mat.get-color-from-palette($foreground-palette, text);
$primary: mat.get-color-from-palette($primary-palette, text);
adf-breadcrumb {
.adf-breadcrumb__show-all-button-icon--rotate {
color: mat.m2-get-color-from-palette($primary-palette, 500);
color: mat.get-color-from-palette($primary-palette, 500);
}
.adf-breadcrumb__item-wrapper {

View File

@ -1,3 +0,0 @@
# DEPRECATION WARNING
The custom theme is deprecated and will be removed in the future. To generate a custom theme, please refer to the Angular Material [Custom Theme section](https://v18.material.angular.dev/guide/theming#custom-theme).

View File

@ -2,19 +2,22 @@
@use '@angular/material' as mat;
@import './theme/theme-data';
$custom-theme: mat.m2-define-light-theme(
$custom-theme: mat.define-light-theme(
(
color: (
primary: map.get($palettes, primary),
accent: map.get($palettes, accent),
warn: map.get($palettes, warning)
warn: map.get($palettes, warning),
),
typography: $app-typography
typography: $app-typography,
)
);
@if $background-color {
$custom-theme: get-custom-background-color($background-color, $custom-theme);
$custom-theme: get-custom-background-color(
$background-color,
$custom-theme
);
}
@if $text-color {

View File

@ -1,27 +1,31 @@
@use '@angular/material' as mat;
@import './default-colors';
@import './custom-palette-creator';
@import './default-colors.scss';
@import './custom-palette-creator.scss';
@function get-mat-palettes($primary-color, $accent-color) {
$mat-primary-palette: null;
@if ($primary-color) {
$custom-theme-primary-palette: create-color-palette($primary-color, 'primary');
$mat-primary-palette: mat.m2-define-palette($custom-theme-primary-palette, 500);
$mat-primary-palette: mat.define-palette($custom-theme-primary-palette, 500);
} @else {
$mat-primary-palette: mat.m2-define-palette($default-primary, A100);
$mat-primary-palette: mat.define-palette($default-primary, A100);
}
$mat-accent-palette: null;
@if ($accent-color) {
$custom-theme-accent-palette: create-color-palette($accent-color, 'accent');
$mat-accent-palette: mat.m2-define-palette($custom-theme-accent-palette, 500);
$mat-accent-palette: mat.define-palette($custom-theme-accent-palette, 500);
} @else {
$mat-accent-palette: mat.m2-define-palette($default-accent);
$mat-accent-palette: mat.define-palette($default-accent);
}
$mat-warn-palette: mat.m2-define-palette($default-warn, A100);
$mat-warn-palette: mat.define-palette($default-warn, A100);
@return (primary: $mat-primary-palette, accent: $mat-accent-palette, warning: $mat-warn-palette);
@return (
primary: $mat-primary-palette,
accent: $mat-accent-palette,
warning: $mat-warn-palette,
);
}

View File

@ -3,37 +3,37 @@
@import '../variables/font-family';
@function get-mat-typography($base-font-size, $font-family) {
$custom-typography: mat.m2-define-typography-config(
$custom-typography: mat.define-typography-config(
$font-family: 'Muli, Roboto, "Helvetica Neue", sans-serif',
$headline-1: mat.m2-define-typography-level(112px, 112px, 300),
$headline-2: mat.m2-define-typography-level(56px, 56px, 400),
$headline-3: mat.m2-define-typography-level(45px, 48px, 400),
$headline-4: mat.m2-define-typography-level(34px, 40px, 400),
$headline-5: mat.m2-define-typography-level(24px, 32px, 400),
$headline-6: mat.m2-define-typography-level(20px, 32px, 500),
$subtitle-1: mat.m2-define-typography-level(16px, 28px, 400),
$body-1: mat.m2-define-typography-level(15px, 24px, 400),
$subtitle-2: mat.m2-define-typography-level(14px, 24px, 500),
$body-2: mat.m2-define-typography-level(14px, 20px, 400),
$caption: mat.m2-define-typography-level(12px, 20px, 400),
$button: mat.m2-define-typography-level(14px, 14px, 500),
$headline-1: mat.define-typography-level(112px, 112px, 300),
$headline-2: mat.define-typography-level(56px, 56px, 400),
$headline-3: mat.define-typography-level(45px, 48px, 400),
$headline-4: mat.define-typography-level(34px, 40px, 400),
$headline-5: mat.define-typography-level(24px, 32px, 400),
$headline-6: mat.define-typography-level(20px, 32px, 500),
$subtitle-1: mat.define-typography-level(16px, 28px, 400),
$body-1: mat.define-typography-level(15px, 24px, 400),
$subtitle-2: mat.define-typography-level(14px, 24px, 500),
$body-2: mat.define-typography-level(14px, 20px, 400),
$caption: mat.define-typography-level(12px, 20px, 400),
$button: mat.define-typography-level(14px, 14px, 500),
// Line-height must be unit-less fraction of the font-size.
);
@if $base-font-size {
$custom-typography: mat.m2-define-typography-config(
$headline-1: mat.m2-define-typography-level(8rem, 8rem, 300),
$headline-2: mat.m2-define-typography-level(4rem, 4rem, 400),
$headline-3: mat.m2-define-typography-level(3.21rem, 3.21rem, 400),
$headline-4: mat.m2-define-typography-level(2.42rem, 2.85rem, 400),
$headline-5: mat.m2-define-typography-level(1.71rem, 2.28rem, 400),
$headline-6: mat.m2-define-typography-level(1.42rem, 2.28rem, 500),
$subtitle-1: mat.m2-define-typography-level(1.14rem, 2rem, 400),
$body-1: mat.m2-define-typography-level(1.07rem, 1.71rem, 400),
$subtitle-2: mat.m2-define-typography-level(1rem, 1.71rem, 500),
$body-2: mat.m2-define-typography-level(1rem, 1.42rem, 400),
$caption: mat.m2-define-typography-level(0.86rem, 1.42rem, 400),
$button: mat.m2-define-typography-level(1rem, 1rem, 500),
$custom-typography: mat.define-typography-config(
$headline-1: mat.define-typography-level(8rem, 8rem, 300),
$headline-2: mat.define-typography-level(4rem, 4rem, 400),
$headline-3: mat.define-typography-level(3.21rem, 3.21rem, 400),
$headline-4: mat.define-typography-level(2.42rem, 2.85rem, 400),
$headline-5: mat.define-typography-level(1.71rem, 2.28rem, 400),
$headline-6: mat.define-typography-level(1.42rem, 2.28rem, 500),
$subtitle-1: mat.define-typography-level(1.14rem, 2rem, 400),
$body-1: mat.define-typography-level(1.07rem, 1.71rem, 400),
$subtitle-2: mat.define-typography-level(1rem, 1.71rem, 500),
$body-2: mat.define-typography-level(1rem, 1.42rem, 400),
$caption: mat.define-typography-level(0.86rem, 1.42rem, 400),
$button: mat.define-typography-level(1rem, 1rem, 500),
$font-family: $default-font-family
);
}

View File

@ -17,37 +17,37 @@
import { TestBed } from '@angular/core/testing';
import { DebugFeaturesService } from './debug-features.service';
import { OverridableFeaturesServiceToken, WritableFeaturesServiceConfigToken, WritableFeaturesServiceToken } from '../interfaces/features.interface';
import { StorageService } from '../../../../src/lib/common/services/storage.service';
import { OverridableFeaturesServiceToken, WritableFeaturesServiceToken } from '../interfaces/features.interface';
import { DummyFeaturesService } from './dummy-features.service';
import { StorageFeaturesService } from './storage-features.service';
import { take } from 'rxjs/operators';
describe('DebugFeaturesService', () => {
let service: DebugFeaturesService;
let mockStorageKey: string;
let mockStorage;
const mockStorage = {
getItem: () =>
JSON.stringify({
feature1: {
current: true
},
feature2: {
current: false,
fictive: true
}
}),
setItem: () => {}
};
beforeEach(() => {
mockStorageKey = 'storage-key-test';
mockStorage = { [mockStorageKey]: true };
TestBed.configureTestingModule({
providers: [
DebugFeaturesService,
{
provide: WritableFeaturesServiceConfigToken,
useValue: { storageKey: mockStorageKey }
},
{ provide: StorageService, useValue: mockStorage },
{ provide: WritableFeaturesServiceToken, useClass: StorageFeaturesService },
{ provide: OverridableFeaturesServiceToken, useClass: DummyFeaturesService }
]
});
spyOn(sessionStorage, 'getItem').and.callFake((key) => JSON.stringify(mockStorage[key]));
spyOn(sessionStorage, 'setItem').and.callFake((key, value) => {
mockStorage[key] = value;
});
service = TestBed.inject(DebugFeaturesService);
});

View File

@ -16,8 +16,8 @@
*/
import { Inject, Injectable, Optional } from '@angular/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
import { BehaviorSubject, Observable } from 'rxjs';
import { skip, switchMap } from 'rxjs/operators';
import {
IDebugFeaturesService,
IFeaturesService,
@ -29,12 +29,12 @@ import {
FlagSet,
IWritableFeaturesService
} from '../interfaces/features.interface';
import { StorageService } from '@alfresco/adf-core';
@Injectable()
export class DebugFeaturesService implements IDebugFeaturesService {
private readonly isInDebugModeSubject = new BehaviorSubject<boolean>(false);
private readonly isInDebugMode$ = this.isInDebugModeSubject.asObservable();
private readonly initSubject = new BehaviorSubject<boolean>(false);
private isInDebugMode: BehaviorSubject<boolean>;
private isInDebugMode$: Observable<boolean>;
get storageKey(): string {
return `${this.config?.storageKey || 'feature-flags'}-override`;
@ -43,15 +43,14 @@ export class DebugFeaturesService implements IDebugFeaturesService {
constructor(
@Inject(OverridableFeaturesServiceToken) private overriddenFeaturesService: IFeaturesService,
@Inject(WritableFeaturesServiceToken) private writableFeaturesService: IFeaturesService & IWritableFeaturesService,
private storageService: StorageService,
@Optional() @Inject(WritableFeaturesServiceConfigToken) private config?: WritableFeaturesServiceConfig
) {
this.init();
this.isInDebugMode = new BehaviorSubject<boolean>(JSON.parse(this.storageService.getItem(this.storageKey) || 'false'));
this.isInDebugMode$ = this.isInDebugMode.asObservable();
combineLatest({
debugMode: this.isInDebugModeSubject,
init: this.waitForInitializationToFinish()
}).subscribe(({ debugMode }) => {
sessionStorage.setItem(this.storageKey, JSON.stringify(debugMode));
this.isInDebugMode.pipe(skip(1)).subscribe((debugMode) => {
this.storageService.setItem(this.storageKey, JSON.stringify(debugMode));
});
}
@ -88,20 +87,10 @@ export class DebugFeaturesService implements IDebugFeaturesService {
}
enable(on: boolean): void {
this.isInDebugModeSubject.next(on);
this.isInDebugMode.next(on);
}
isEnabled$(): Observable<boolean> {
return this.isInDebugMode$;
}
private init() {
const storedOverride = JSON.parse(sessionStorage.getItem(this.storageKey) || 'false');
this.isInDebugModeSubject.next(storedOverride);
this.initSubject.next(true);
}
private waitForInitializationToFinish(): Observable<boolean> {
return this.initSubject.pipe(filter((initialized) => !!initialized));
}
}

View File

@ -17,20 +17,17 @@
import { TestBed } from '@angular/core/testing';
import { StorageFeaturesService } from './storage-features.service';
import { StorageService } from '../../../../src/public-api';
import { FlagSet, WritableFeaturesServiceConfigToken } from '../interfaces/features.interface';
import { skip, take } from 'rxjs/operators';
describe('StorageFeaturesService', () => {
let storageFeaturesService: StorageFeaturesService;
describe('if flags are present in sessionStorage', () => {
let mockStorageKey: string;
let mockStorage;
beforeEach(() => {
mockStorageKey = 'storage-key-test';
mockStorage = {
[mockStorageKey]: {
describe('if flags are present in LocalStorage', () => {
const mockStorage = {
getItem: () =>
JSON.stringify({
feature1: {
current: true
},
@ -38,23 +35,23 @@ describe('StorageFeaturesService', () => {
current: false,
fictive: true
}
}
};
}),
setItem: () => {}
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: StorageService, useValue: mockStorage },
{
provide: WritableFeaturesServiceConfigToken,
useValue: { storageKey: mockStorageKey }
useValue: {
storageKey: 'storage-key-test'
}
}
]
});
spyOn(sessionStorage, 'getItem').and.callFake((key) => JSON.stringify(mockStorage[key]));
spyOn(sessionStorage, 'setItem').and.callFake((key, value) => {
mockStorage[key] = value;
});
storageFeaturesService = TestBed.inject(StorageFeaturesService);
storageFeaturesService.init();
});

View File

@ -16,8 +16,8 @@
*/
import { Inject, Injectable, Optional } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, skip } from 'rxjs/operators';
import {
FlagChangeset,
IFeaturesService,
@ -28,21 +28,21 @@ import {
WritableFeaturesServiceConfig
} from '../interfaces/features.interface';
import { FlagSetParser } from './flagset.parser';
import { StorageService } from '@alfresco/adf-core';
@Injectable({ providedIn: 'root' })
export class StorageFeaturesService implements IFeaturesService, IWritableFeaturesService {
private currentFlagState: WritableFlagChangeset = {};
private readonly flags = new BehaviorSubject<WritableFlagChangeset>({});
private readonly flags$ = this.flags.asObservable();
private readonly initSubject = new BehaviorSubject<boolean>(false);
private flags = new BehaviorSubject<WritableFlagChangeset>({});
private flags$ = this.flags.asObservable();
constructor(@Optional() @Inject(WritableFeaturesServiceConfigToken) private config?: WritableFeaturesServiceConfig) {
combineLatest({
flags: this.flags,
init: this.waitForInitializationToFinish()
}).subscribe(({ flags }) => {
constructor(
private storageService: StorageService,
@Optional() @Inject(WritableFeaturesServiceConfigToken) private config?: WritableFeaturesServiceConfig
) {
this.flags.pipe(skip(1)).subscribe((flags) => {
this.currentFlagState = flags;
sessionStorage.setItem(this.storageKey, JSON.stringify(FlagSetParser.serialize(flags)));
this.storageService.setItem(this.storageKey, JSON.stringify(FlagSetParser.serialize(flags)));
});
}
@ -51,10 +51,9 @@ export class StorageFeaturesService implements IFeaturesService, IWritableFeatur
}
init(): Observable<WritableFlagChangeset> {
const storedFlags = JSON.parse(sessionStorage.getItem(this.storageKey) || '{}');
const storedFlags = JSON.parse(this.storageService.getItem(this.storageKey) || '{}');
const initialFlagChangeSet = FlagSetParser.deserialize(storedFlags);
this.flags.next(initialFlagChangeSet);
this.initSubject.next(true);
return of(initialFlagChangeSet);
}
@ -134,8 +133,4 @@ export class StorageFeaturesService implements IFeaturesService, IWritableFeatur
this.flags.next(mergedFlags);
}
private waitForInitializationToFinish(): Observable<boolean> {
return this.initSubject.pipe(filter((initialized) => !!initialized));
}
}

View File

@ -1,123 +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 { AdfHttpClient } from '@alfresco/adf-core/api';
import { StorageService } from '../common';
import { StoragePrefixFactory } from './app-config-storage-prefix.factory';
import { loadAppConfig } from './app-config.loader';
import { AppConfigService, AppConfigValues } from './app-config.service';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { HttpClientModule } from '@angular/common/http';
import { of } from 'rxjs';
describe('loadAppConfig', () => {
let appConfigServiceSpy: jasmine.SpyObj<AppConfigService>;
let storageServiceSpy: jasmine.SpyObj<StorageService>;
let adfHttpClientSpy: jasmine.SpyObj<AdfHttpClient>;
let storagePrefixFactorySpy: jasmine.SpyObj<StoragePrefixFactory>;
let appConfigGetSpy: jasmine.Spy;
let appConfigLoadSpy: jasmine.Spy;
let factoryFunction: () => void;
beforeEach(() => {
adfHttpClientSpy = jasmine.createSpyObj('AdfHttpClient', ['setDefaultSecurityOption']);
storagePrefixFactorySpy = jasmine.createSpyObj('StoragePrefixFactory', ['getPrefix']);
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [
{ provide: AppConfigService },
{ provide: StorageService },
{
provide: AdfHttpClient,
useValue: adfHttpClientSpy
},
{ provide: StoragePrefixFactory, useValue: storagePrefixFactorySpy }
]
});
appConfigServiceSpy = TestBed.inject(AppConfigService) as jasmine.SpyObj<AppConfigService>;
appConfigGetSpy = spyOn(appConfigServiceSpy, 'get');
appConfigLoadSpy = spyOn(appConfigServiceSpy, 'load');
appConfigLoadSpy.and.callFake((callback: () => void) => {
callback();
});
storageServiceSpy = TestBed.inject(StorageService) as jasmine.SpyObj<StorageService>;
spyOnProperty(storageServiceSpy, 'prefix', 'get').and.callThrough();
storagePrefixFactorySpy.getPrefix.and.returnValue({ subscribe: () => {} } as any);
factoryFunction = loadAppConfig(appConfigServiceSpy, storageServiceSpy, adfHttpClientSpy, storagePrefixFactorySpy);
});
it('should disable CSRF based on app config', () => {
appConfigGetSpy.and.callFake((key: string): any => {
if (key === AppConfigValues.DISABLECSRF) {
return true;
}
});
factoryFunction();
expect(appConfigServiceSpy.get).toHaveBeenCalledWith('disableCSRF', true);
expect(adfHttpClientSpy.disableCsrf).toBeTrue();
});
it('should set default security option when auth.withCredentials is defined', () => {
appConfigServiceSpy.get.and.callFake((key: string): any => {
if (key === AppConfigValues.AUTH_WITH_CREDENTIALS) {
return true;
}
});
factoryFunction();
expect(adfHttpClientSpy.setDefaultSecurityOption).toHaveBeenCalledWith({ withCredentials: true });
});
it('should set storage prefix from app config', () => {
appConfigServiceSpy.get.and.callFake((key: string, _default): any => {
if (key === AppConfigValues.STORAGE_PREFIX) {
return 'test-prefix';
}
});
factoryFunction();
expect(appConfigServiceSpy.get).toHaveBeenCalledWith(AppConfigValues.STORAGE_PREFIX, '');
expect(storageServiceSpy.prefix).toEqual('test-prefix_');
});
it('should update storage prefix from storagePrefixFactory', fakeAsync(() => {
storagePrefixFactorySpy.getPrefix.and.returnValue(of('new-amazing-prefix'));
factoryFunction();
tick();
expect(storagePrefixFactorySpy.getPrefix).toHaveBeenCalled();
expect(storageServiceSpy.prefix).toEqual('new-amazing-prefix_');
}));
it('should call appConfigService.load with the init function', () => {
factoryFunction();
expect(appConfigLoadSpy).toHaveBeenCalledWith(jasmine.any(Function));
});
});

View File

@ -37,11 +37,6 @@ export function loadAppConfig(
) {
const init = () => {
adfHttpClient.disableCsrf = appConfigService.get<boolean>(AppConfigValues.DISABLECSRF, true);
const withCredentials = appConfigService.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS);
if (withCredentials !== undefined && withCredentials !== null) {
adfHttpClient.setDefaultSecurityOption({ withCredentials });
}
storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, '');
storagePrefixFactory.getPrefix().subscribe((property) => {

View File

@ -18,7 +18,6 @@
import { Directive, EventEmitter, Input, Output } from '@angular/core';
import { ThemePalette } from '@angular/material/core';
import { FormFieldModel, FormFieldValidator, FormModel, FormOutcomeEvent, FormOutcomeModel } from './widgets';
import { isOutcomeButtonVisible } from './helpers/buttons-visibility';
@Directive({
standalone: true
@ -104,6 +103,10 @@ export abstract class FormBaseComponent {
*/
formStyle: string = '';
get hasVisibleOutcomes(): boolean {
return this.form?.outcomes?.some((outcome) => this.isOutcomeButtonVisible(outcome, this.form.readOnly));
}
get form(): FormModel {
return this._form;
}
@ -166,7 +169,22 @@ export abstract class FormBaseComponent {
}
isOutcomeButtonVisible(outcome: FormOutcomeModel, isFormReadOnly: boolean): boolean {
return isOutcomeButtonVisible(outcome, { isFormReadOnly, showCompleteButton: this.showCompleteButton, showSaveButton: this.showSaveButton });
if (outcome?.name) {
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
return this.showCompleteButton;
}
if (isFormReadOnly) {
return outcome.isSelected;
}
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
return this.showSaveButton;
}
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
return false;
}
return true;
}
return false;
}
/**

View File

@ -73,7 +73,6 @@ export class FormFieldComponent implements OnInit, OnDestroy {
if (w.adf === undefined) {
w.adf = {};
}
const originalField = this.getField();
if (originalField) {
const customTemplate = this.field.form.customFieldTemplates[originalField.type];

View File

@ -3,12 +3,8 @@
<div *ngIf="formDefinition.hasTabs()">
<div *ngIf="hasTabs()" class="alfresco-tabs-widget">
<mat-tab-group>
<mat-tab *ngFor="let tab of visibleTabs()" [label]="tab.title | translate ">
<ng-template matTabContent>
<div class="adf-form-tab-content">
<ng-template *ngTemplateOutlet="render; context: { fieldToRender: tab.fields }" />
</div>
</ng-template>
<mat-tab *ngFor="let tab of visibleTabs()" [label]="tab.title | translate ">
<ng-template *ngTemplateOutlet="render; context: { fieldToRender: tab.fields }" />
</mat-tab>
</mat-tab-group>
</div>
@ -42,8 +38,7 @@
<section class="adf-grid-list-column-view" *ngIf="currentRootElement?.isExpanded">
<div class="adf-grid-list-single-column"
*ngFor="let column of currentRootElement?.columns"
[style.width.%]="getColumnWidth(currentRootElement)"
>
[style.width.%]="getColumnWidth(currentRootElement)">
<ng-container *ngFor="let field of column?.fields">
<ng-container *ngIf="field.type === 'section'; else formField">
<adf-form-section [field]="field"/>
@ -64,11 +59,9 @@
</div>
</ng-template>
</div>
<div *ngIf="currentRootElement.type === 'dynamic-table'" class="adf-container-widget">
<adf-form-field [field]="currentRootElement" />
</div>
<div class="adf-container-widget"
*ngIf="currentRootElement.type === 'readonly' && currentRootElement.field.params.field.type === 'dynamic-table'">
<adf-form-field [field]="currentRootElement.field"/>

View File

@ -13,39 +13,6 @@
height: 100%;
}
.alfresco-tabs-widget {
width: 100%;
.adf-form-tab-group {
width: 100%;
}
#{ms.$mat-tab-body} {
margin-bottom: 8em;
}
#{ms.$mat-tab-header} {
z-index: 10;
margin: 0;
background-color: white;
position: absolute;
width: 96%;
/* stylelint-disable-next-line declaration-no-important */
margin-left: 0 !important;
/* stylelint-disable-next-line declaration-no-important */
margin-right: 10px !important;
top: 0;
}
#{ms.$mat-tab-body-wrapper} {
padding-top: 5%;
}
}
.mat-mdc-card-content:first-child {
padding-top: 1em;
}
.adf-container-widget {
.adf-grid-list {
display: grid;
@ -57,7 +24,6 @@
display: flex;
margin-right: -1%;
width: 100%;
&-item {
width: 100%;
@ -149,18 +115,12 @@
overflow: hidden;
}
& #{ms.$mat-tab-header} {
position: fixed;
z-index: 1000;
}
& #{ms.$mat-card-header-text} {
margin: 0;
}
& #{ms.$mat-tab-body-content} {
overflow: hidden;
padding-top: 0;
}
& #{mat-tab-label-text} {

View File

@ -1,45 +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 { FormOutcomeModel } from '../widgets';
interface IsOutcomeButtonVisibleProps {
isFormReadOnly: boolean;
showCompleteButton: boolean;
showSaveButton: boolean;
}
export const isOutcomeButtonVisible = (outcome: FormOutcomeModel, props: IsOutcomeButtonVisibleProps): boolean => {
const { isFormReadOnly, showCompleteButton, showSaveButton } = props;
if (outcome?.name) {
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
return showCompleteButton;
}
if (isFormReadOnly) {
return outcome.isSelected;
}
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
return showSaveButton;
}
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
return false;
}
return true;
}
return false;
};

View File

@ -146,7 +146,7 @@ describe('FormFieldModel', () => {
});
expect(field.options).toEqual([{ id: 'id_one', name: 'One' }]);
expect(field.value).toEqual({ id: 'id_one', name: 'One' });
expect(field.value).toEqual('id_one');
});
it('should add value (selected options) to field options if NOT present (multiple selection)', () => {
@ -176,7 +176,7 @@ describe('FormFieldModel', () => {
expect(field.hasEmptyValue).toBe(true);
expect(field.emptyOption).toEqual({ id: 'empty', name: 'Chose one...' });
expect(field.value).toEqual({ id: 'empty', name: 'Chose one...' });
expect(field.value).toEqual('empty');
});
it('should set hasEmptyValue to true if "empty" option is present in options', () => {
@ -272,8 +272,7 @@ describe('FormFieldModel', () => {
options: [],
value: { id: 'delayed-option-id', name: 'Delayed option' }
});
expect(field.value).toEqual({ id: 'delayed-option-id', name: 'Delayed option' });
expect(field.value).toBe('delayed-option-id');
});
});
});
@ -771,7 +770,7 @@ describe('FormFieldModel', () => {
];
});
it('should update form with selected option and options from which we chose when is a string', () => {
it('should update form with selected option and options from which we chose', () => {
field.value = 'restOpt2';
field.updateForm();
@ -1024,7 +1023,7 @@ describe('FormFieldModel', () => {
expect(field.options).toEqual(staticOptions);
});
it('should selected option appear in form values string', () => {
it('should selected option appear in form values', () => {
const field = getFieldConfig('manual', staticOptions, 'opt2');
field.updateForm();
@ -1032,15 +1031,6 @@ describe('FormFieldModel', () => {
expect(field.value).toEqual('opt2');
expect(field.form.values['dropdown_field']).toEqual({ id: 'opt2', name: 'Option 2' });
});
it('should selected option appear in form values obj', () => {
const field = getFieldConfig('manual', staticOptions, { id: 'opt3', name: 'opt3' });
field.updateForm();
expect(field.value).toEqual({ id: 'opt3', name: 'opt3' });
expect(field.form.values['dropdown_field']).toEqual({ id: 'opt3', name: 'opt3' });
});
});
describe('radio buttons field', () => {

View File

@ -331,13 +331,13 @@ export class FormFieldModel extends FormWidgetModel {
const isEmptyValue = !value || [this.emptyOption.id, this.emptyOption.name].includes(value);
if (isEmptyValue) {
return this.emptyOption;
return this.emptyOption.id;
}
}
if (this.isValidOption(value)) {
this.addOption({ id: value.id, name: value.name });
return value;
return value.id;
}
if (this.hasMultipleValues) {
@ -436,17 +436,6 @@ export class FormFieldModel extends FormWidgetModel {
this.form.values[this.id] = matchingOption || null;
}
if (typeof this.value === 'object') {
if (this.value.id === 'empty' || this.value.id === '') {
this.form.values[this.id] = null;
break;
}
const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value.id);
this.form.values[this.id] = matchingOption;
}
break;
}
case FormFieldTypes.RADIO_BUTTONS: {

View File

@ -358,7 +358,7 @@ export class FormModel implements ProcessFormModel {
this.handleSectionField(field, formFieldModel);
} else if (this.isContainerField(field)) {
this.handleContainerField(field, formFieldModel);
} else if (this.isFormField(field)) {
} else {
this.handleSingleField(field, formFieldModel);
}
});
@ -368,10 +368,6 @@ export class FormModel implements ProcessFormModel {
return field instanceof ContainerModel;
}
private isFormField(field: ContainerModel | FormFieldModel): field is FormFieldModel {
return field instanceof FormFieldModel;
}
private isSectionField(field: ContainerModel | FormFieldModel): field is FormFieldModel {
return field.type === FormFieldTypes.SECTION;
}

View File

@ -23,7 +23,6 @@ export * from './components/form-renderer.component';
export * from './components/widgets';
export * from './components/middlewares/middleware';
export * from './components/middlewares/decimal-middleware.service';
export * from './components/helpers/buttons-visibility';
export * from './services/form-rendering.service';
export * from './services/form.service';

View File

@ -18,7 +18,7 @@
"COMPLETE": "Abschließen",
"CANCEL": "Abbrechen",
"CLAIM": "Beanspruchen",
"UNCLAIM": "Freigeben ",
"UNCLAIM": "Anspruch aufheben",
"START PROCESS": "Prozess starten",
"DATA_LOADING": "Daten werden geladen",
"CLOSE": "Schließen",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "No se puede acceder al servidor '{{ hostname }}'",
"VARIABLE_DROPDOWN_OPTIONS_FAILED": "Ha habido un problema al cargar elementos desplegables. Contacte con el administrador.",
"DATA_TABLE_LOAD_FAILED": "Ha habido un problema al cargar elementos de tabla. Contacte con el administrador.",
"DATA_TABLE_EMPTY_CONTENT": "No se encontraron datos",
"DATA_TABLE_EMPTY_CONTENT": "No data found",
"EXTERNAL_PROPERTY_LOAD_FAILED": "Ha habido un problema al cargar una propiedad externa. Contacte con el administrador.",
"FILE_NAME": "Nombre del fichero",
"TITLE": "Título",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "Le serveur '{{ hostname }}' n'est pas accessible",
"VARIABLE_DROPDOWN_OPTIONS_FAILED": "Un problème est survenu lors du chargement des éléments de la liste déroulante. Veuillez contacter un administrateur.",
"DATA_TABLE_LOAD_FAILED": "Un problème est survenu lors du chargement des éléments du tableau. Veuillez contacter un administrateur.",
"DATA_TABLE_EMPTY_CONTENT": "Données introuvables",
"DATA_TABLE_EMPTY_CONTENT": "No data found",
"EXTERNAL_PROPERTY_LOAD_FAILED": "Un problème est survenu lors du chargement de la propriété externe. Veuillez contacter un administrateur.",
"FILE_NAME": "Nom de fichier",
"TITLE": "Titre",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "Il server '{{ hostname }}' non è raggiungibile",
"VARIABLE_DROPDOWN_OPTIONS_FAILED": "Si è verificato un problema durante il caricamento degli elementi a discesa. Si prega di contattare lamministratore.",
"DATA_TABLE_LOAD_FAILED": "Si è verificato un problema durante il caricamento degli elementi della tabella. Si prega di contattare lamministratore.",
"DATA_TABLE_EMPTY_CONTENT": "Nessun dato trovato",
"DATA_TABLE_EMPTY_CONTENT": "No data found",
"EXTERNAL_PROPERTY_LOAD_FAILED": "Si è verificato un problema durante il caricamento delle proprietà esterne. Si prega di contattare lamministratore.",
"FILE_NAME": "Nome file",
"TITLE": "Titolo",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "Serwer '{{ hostname }}' jest nieosiągalny",
"VARIABLE_DROPDOWN_OPTIONS_FAILED": "Wystąpił problem podczas ładowania elementów rozwijania. Skontaktuj się z administratorem.",
"DATA_TABLE_LOAD_FAILED": "Wystąpił problem podczas ładowania elementów tabeli. Skontaktuj się z administratorem.",
"DATA_TABLE_EMPTY_CONTENT": "Brak danych",
"DATA_TABLE_EMPTY_CONTENT": "No data found",
"EXTERNAL_PROPERTY_LOAD_FAILED": "Wystąpił problem podczas ładowania właściwości zewnętrznej. Skontaktuj się z administratorem.",
"FILE_NAME": "Nazwa pliku",
"TITLE": "Tytuł",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "O servidor `{{ hostname }}` não pode ser acedido",
"VARIABLE_DROPDOWN_OPTIONS_FAILED": "Problema ao carregar elementos suspensos. Entre em contacto com o administrador.",
"DATA_TABLE_LOAD_FAILED": "Problema ao carregar os elementos da tabela. Entre em contacto com o administrador.",
"DATA_TABLE_EMPTY_CONTENT": "Não foram encontrados dados",
"DATA_TABLE_EMPTY_CONTENT": "No data found",
"EXTERNAL_PROPERTY_LOAD_FAILED": "Houve um problema ao carregar a propriedade externa. Entre em contacto com o administrador.",
"FILE_NAME": "Nome do ficheiro",
"TITLE": "Título",

View File

@ -26,70 +26,70 @@
// map SCSS variables to expose as CSS variables
$defaults: (
// theme colors
--theme-primary-color: mat.m2-get-color-from-palette($primary),
--theme-primary-color-default-contrast: mat.m2-get-color-from-palette($primary, default-contrast),
--theme-header-text-color: mat.m2-get-color-from-palette($primary, default-contrast),
--adf-theme-primary-50: mat.m2-get-color-from-palette($primary, 50),
--adf-theme-primary-100: mat.m2-get-color-from-palette($primary, 100),
--adf-theme-primary-300: mat.m2-get-color-from-palette($primary, 300),
--adf-theme-primary-900: mat.m2-get-color-from-palette($primary, 900),
--theme-warn-color: mat.m2-get-color-from-palette($warn),
--theme-warn-color-a700: mat.m2-get-color-from-palette($warn, A700),
--theme-warn-color-default-contrast: mat.m2-get-color-from-palette($warn, default-contrast),
--theme-accent-color: mat.m2-get-color-from-palette($accent),
--theme-accent-color-a200: mat.m2-get-color-from-palette($accent, A200),
--theme-accent-color-default-contrast: mat.m2-get-color-from-palette($accent, default-contrast),
--theme-accent-500: mat.m2-get-color-from-palette($accent, 500),
--adf-theme-foreground-base-color: mat.m2-get-color-from-palette($foreground, base),
--adf-theme-foreground-base-color-065: mat.m2-get-color-from-palette($foreground, base, 0.65),
--adf-theme-foreground-base-color-045: mat.m2-get-color-from-palette($foreground, base, 0.45),
--adf-theme-foreground-disabled-text-color: mat.m2-get-color-from-palette($foreground, disabled-text),
--adf-theme-foreground-divider-color: mat.m2-get-color-from-palette($foreground, divider),
--adf-theme-foreground-icon-color: mat.m2-get-color-from-palette($foreground, icon),
--adf-theme-foreground-icon-color-054: mat.m2-get-color-from-palette($foreground, icon, 0.54),
--adf-theme-foreground-secondary-text-color: mat.m2-get-color-from-palette($foreground, secondary-text),
--adf-theme-foreground-text-color: mat.m2-get-color-from-palette($foreground, text),
--adf-theme-foreground-text-color-087: mat.m2-get-color-from-palette($foreground, text, 0.87),
--adf-theme-foreground-text-color-075: mat.m2-get-color-from-palette($foreground, text, 0.75),
--adf-theme-foreground-text-color-064: mat.m2-get-color-from-palette($foreground, text, 0.64),
--adf-theme-foreground-text-color-054: mat.m2-get-color-from-palette($foreground, text, 0.54),
--adf-theme-foreground-text-color-040: mat.m2-get-color-from-palette($foreground, text, 0.4),
--adf-theme-foreground-text-color-027: mat.m2-get-color-from-palette($foreground, text, 0.27),
--adf-theme-foreground-text-color-025: mat.m2-get-color-from-palette($foreground, text, 0.25),
--adf-theme-foreground-text-color-014: mat.m2-get-color-from-palette($foreground, text, 0.14),
--adf-theme-foreground-text-color-007: mat.m2-get-color-from-palette($foreground, text, 0.07),
--adf-theme-background-card-color: mat.m2-get-color-from-palette($background, card),
--adf-theme-background-card-color-087: mat.m2-get-color-from-palette($background, card, 0.87),
--theme-background-color: mat.m2-get-color-from-palette($background, background),
--adf-theme-background-dialog-color: mat.m2-get-color-from-palette($background, dialog),
--adf-theme-background-hover-color: mat.m2-get-color-from-palette($background, hover),
--adf-theme-background-selected-button-color: mat.m2-get-color-from-palette($background, selected-button),
--adf-theme-background-status-bar-color: mat.m2-get-color-from-palette($background, status-bar),
--adf-theme-background-unselected-chip-color: mat.m2-get-color-from-palette($background, unselected-chip),
--theme-primary-color: mat.get-color-from-palette($primary),
--theme-primary-color-default-contrast: mat.get-color-from-palette($primary, default-contrast),
--theme-header-text-color: mat.get-color-from-palette($primary, default-contrast),
--adf-theme-primary-50: mat.get-color-from-palette($primary, 50),
--adf-theme-primary-100: mat.get-color-from-palette($primary, 100),
--adf-theme-primary-300: mat.get-color-from-palette($primary, 300),
--adf-theme-primary-900: mat.get-color-from-palette($primary, 900),
--theme-warn-color: mat.get-color-from-palette($warn),
--theme-warn-color-a700: mat.get-color-from-palette($warn, A700),
--theme-warn-color-default-contrast: mat.get-color-from-palette($warn, default-contrast),
--theme-accent-color: mat.get-color-from-palette($accent),
--theme-accent-color-a200: mat.get-color-from-palette($accent, A200),
--theme-accent-color-default-contrast: mat.get-color-from-palette($accent, default-contrast),
--theme-accent-500: mat.get-color-from-palette($accent, 500),
--adf-theme-foreground-base-color: mat.get-color-from-palette($foreground, base),
--adf-theme-foreground-base-color-065: mat.get-color-from-palette($foreground, base, 0.65),
--adf-theme-foreground-base-color-045: mat.get-color-from-palette($foreground, base, 0.45),
--adf-theme-foreground-disabled-text-color: mat.get-color-from-palette($foreground, disabled-text),
--adf-theme-foreground-divider-color: mat.get-color-from-palette($foreground, divider),
--adf-theme-foreground-icon-color: mat.get-color-from-palette($foreground, icon),
--adf-theme-foreground-icon-color-054: mat.get-color-from-palette($foreground, icon, 0.54),
--adf-theme-foreground-secondary-text-color: mat.get-color-from-palette($foreground, secondary-text),
--adf-theme-foreground-text-color: mat.get-color-from-palette($foreground, text),
--adf-theme-foreground-text-color-087: mat.get-color-from-palette($foreground, text, 0.87),
--adf-theme-foreground-text-color-075: mat.get-color-from-palette($foreground, text, 0.75),
--adf-theme-foreground-text-color-064: mat.get-color-from-palette($foreground, text, 0.64),
--adf-theme-foreground-text-color-054: mat.get-color-from-palette($foreground, text, 0.54),
--adf-theme-foreground-text-color-040: mat.get-color-from-palette($foreground, text, 0.4),
--adf-theme-foreground-text-color-027: mat.get-color-from-palette($foreground, text, 0.27),
--adf-theme-foreground-text-color-025: mat.get-color-from-palette($foreground, text, 0.25),
--adf-theme-foreground-text-color-014: mat.get-color-from-palette($foreground, text, 0.14),
--adf-theme-foreground-text-color-007: mat.get-color-from-palette($foreground, text, 0.07),
--adf-theme-background-card-color: mat.get-color-from-palette($background, card),
--adf-theme-background-card-color-087: mat.get-color-from-palette($background, card, 0.87),
--theme-background-color: mat.get-color-from-palette($background, background),
--adf-theme-background-dialog-color: mat.get-color-from-palette($background, dialog),
--adf-theme-background-hover-color: mat.get-color-from-palette($background, hover),
--adf-theme-background-selected-button-color: mat.get-color-from-palette($background, selected-button),
--adf-theme-background-status-bar-color: mat.get-color-from-palette($background, status-bar),
--adf-theme-background-unselected-chip-color: mat.get-color-from-palette($background, unselected-chip),
// typography
--theme-font-family: mat.m2-font-family($typography),
--theme-font-family: mat.font-family($typography),
--theme-font-weight: normal,
--theme-body-1-font-size: mat.m2-font-size($typography, body-2),
--theme-body-2-font-size: mat.m2-font-size($typography, subtitle-2),
--theme-body-1-line-height: mat.m2-line-height($typography, body-2),
--theme-display-1-font-size: mat.m2-font-size($typography, headline-4),
--theme-display-3-font-size: mat.m2-font-size($typography, headline-2),
--theme-display-4-font-size: mat.m2-font-size($typography, headline-1),
--theme-caption-font-size: mat.m2-font-size($typography, caption),
--theme-title-font-size: mat.m2-font-size($typography, headline-6),
--theme-subheading-1-font-size: mat.m2-font-size($typography, body-1),
--theme-subheading-2-font-size: mat.m2-font-size($typography, subtitle-1),
--theme-button-font-size: mat.m2-font-size($typography, button),
--theme-headline-font-size: mat.m2-font-size($typography, headline-5),
--theme-headline-line-height: mat.m2-line-height($typography, headline-5),
--theme-body-1-font-size: mat.font-size($typography, body-2),
--theme-body-2-font-size: mat.font-size($typography, subtitle-2),
--theme-body-1-line-height: mat.line-height($typography, body-2),
--theme-display-1-font-size: mat.font-size($typography, headline-4),
--theme-display-3-font-size: mat.font-size($typography, headline-2),
--theme-display-4-font-size: mat.font-size($typography, headline-1),
--theme-caption-font-size: mat.font-size($typography, caption),
--theme-title-font-size: mat.font-size($typography, headline-6),
--theme-subheading-1-font-size: mat.font-size($typography, body-1),
--theme-subheading-2-font-size: mat.font-size($typography, subtitle-1),
--theme-button-font-size: mat.font-size($typography, button),
--theme-headline-font-size: mat.font-size($typography, headline-5),
--theme-headline-line-height: mat.line-height($typography, headline-5),
--theme-adf-icon-1-font-size: map-get($custom-css-variables, 'theme-adf-icon-1-font-size'),
--theme-adf-picture-1-font-size: map-get($custom-css-variables, 'theme-adf-picture-1-font-size'),
--theme-adf-task-footer-font-size: map-get($custom-css-variables, 'theme-adf-task-footer-font-size'),
--theme-adf-task-title-font-size: map-get($custom-css-variables, 'theme-adf-task-title-font-size'),
// specific colors
--adf-theme-mat-grey-color-a200: mat.m2-get-color-from-palette(mat.$m2-grey-palette, 'A200'),
--adf-theme-mat-grey-color-a400: mat.m2-get-color-from-palette(mat.$m2-grey-palette, 'A400'),
--adf-theme-mat-grey-color-50: mat.m2-get-color-from-palette(mat.$m2-grey-palette, 50),
--adf-theme-mat-grey-color-a200: mat.get-color-from-palette(mat.$grey-palette, A200),
--adf-theme-mat-grey-color-a400: mat.get-color-from-palette(mat.$grey-palette, A400),
--adf-theme-mat-grey-color-50: mat.get-color-from-palette(mat.$grey-palette, 50),
// spacing
--adf-theme-spacing: map-get($custom-css-variables, 'theme-adf-spacing'),
// components

View File

@ -6,11 +6,8 @@ $mat-tab-label-active: '.mdc-tab--active';
$mat-tab-label-container: '.mat-mdc-tab-label-container';
$mat-tab-label-text: '.mdc-tab__text-label';
$mat-tab-body: '.mat-mdc-tab-body';
$mat-tab-header: '.mat-mdc-tab-header';
$mat-tab-body-content: '.mat-mdc-tab-body-content';
$mat-tab-ink-bar: '.mdc-tab-indicator';
$mat-tab-body-wrapper: '.mat-mdc-tab-body-wrapper';
$mat-tab-body-content: '.mat-mdc-card-content';
$mat-chip: '.mat-mdc-chip';
$mat-chip-list: '.mat-mdc-chip-list';
$mat-checkbox: '.mat-mdc-checkbox';

View File

@ -1,18 +1,18 @@
@use '@angular/material' as mat;
$alfresco-typography: mat.m2-define-typography-config(
$alfresco-typography: mat.define-typography-config(
$font-family: 'Roboto, "Helvetica Neue", sans-serif',
$headline-1: mat.m2-define-typography-level(112px, 112px, 300),
$headline-2: mat.m2-define-typography-level(56px, 56px, 400),
$headline-3: mat.m2-define-typography-level(45px, 48px, 400),
$headline-4: mat.m2-define-typography-level(34px, 40px, 400),
$headline-5: mat.m2-define-typography-level(24px, 32px, 400),
$headline-6: mat.m2-define-typography-level(20px, 32px, 500),
$subtitle-1: mat.m2-define-typography-level(16px, 28px, 400),
$body-1: mat.m2-define-typography-level(15px, 24px, 400),
$subtitle-2: mat.m2-define-typography-level(14px, 24px, 500),
$body-2: mat.m2-define-typography-level(14px, 20px, 400),
$caption: mat.m2-define-typography-level(12px, 20px, 400),
$button: mat.m2-define-typography-level(14px, 14px, 500),
$headline-1: mat.define-typography-level(112px, 112px, 300),
$headline-2: mat.define-typography-level(56px, 56px, 400),
$headline-3: mat.define-typography-level(45px, 48px, 400),
$headline-4: mat.define-typography-level(34px, 40px, 400),
$headline-5: mat.define-typography-level(24px, 32px, 400),
$headline-6: mat.define-typography-level(20px, 32px, 500),
$subtitle-1: mat.define-typography-level(16px, 28px, 400),
$body-1: mat.define-typography-level(15px, 24px, 400),
$subtitle-2: mat.define-typography-level(14px, 24px, 500),
$body-2: mat.define-typography-level(14px, 20px, 400),
$caption: mat.define-typography-level(12px, 20px, 400),
$button: mat.define-typography-level(14px, 14px, 500),
// Line-height must be unit-less fraction of the font-size.
);

View File

@ -27,7 +27,7 @@
class="adf-viewer-pdf-viewer"
(window:resize)="onResize()">
<div [id]="randomPdfId + '-viewer-viewerPdf'"
class="adf-pdfViewer pdfViewer"
class="adf-pdfViewer"
role="document"
tabindex="0"
aria-expanded="true">

View File

@ -114,7 +114,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
displayPage: number;
totalPages: number;
loadingPercent: number;
pdfViewer: PDFViewer;
pdfViewer: any;
pdfJsWorkerUrl: string;
pdfJsWorkerInstance: Worker;
currentScaleMode: PdfScaleMode = 'init';
@ -132,12 +132,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
documentOverflow = false;
get currentScaleText(): string {
const currentScaleValueStr = this.pdfViewer?.currentScaleValue;
const scaleNumber = Number(currentScaleValueStr);
const currentScaleText = scaleNumber ? `${Math.round(scaleNumber * 100)}%` : '';
return currentScaleText;
return this.pdfViewer?.currentScaleValue ? Math.round(this.pdfViewer.currentScaleValue * 100) + '%' : '';
}
private pdfjsLib = inject(PDFJS_MODULE);
@ -457,9 +452,10 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
*/
setScaleUpdatePages(newScale: number) {
if (this.pdfViewer) {
if (!this.isSameScale(this.pdfViewer.currentScaleValue, newScale.toString())) {
this.pdfViewer.currentScaleValue = newScale.toString();
if (!this.isSameScale(this.pdfViewer.currentScaleValue, newScale)) {
this.pdfViewer.currentScaleValue = newScale;
}
this.pdfViewer.update();
}
this.setDocumentOverflow();
@ -472,7 +468,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
* @param newScale - new scale page
* @returns `true` if the scale is the same, otherwise `false`
*/
isSameScale(oldScale: string, newScale: string): boolean {
isSameScale(oldScale: number, newScale: number): boolean {
return newScale === oldScale;
}

View File

@ -1,105 +1,95 @@
<div *ngIf="isLoading$ | async" class="adf-viewer-render-main-loader">
<div *ngIf="(viewerType === 'media' || viewerType === 'pdf' || viewerType === 'image') ? isLoading || !isContentReady : isLoading"
class="adf-viewer-render-main-loader">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container">
<div class="adf-viewer-render__loading-screen">
<div class="adf-viewer-render__loading-screen ">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner class="adf-viewer-render__loading-screen__spinner" />
<mat-spinner class="adf-viewer-render__loading-screen__spinner"/>
</div>
</div>
</div>
</div>
</div>
<ng-container *ngIf="urlFile || blobFile">
<div [hidden]="isLoading$ | async" class="adf-viewer-render-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<adf-preview-extension
*ngIf="!!externalViewer"
[id]="externalViewer.component"
[url]="urlFile"
[extension]="externalViewer.fileExtension"
[nodeId]="nodeId"
[attr.data-automation-id]="externalViewer.component"
/>
<div *ngIf="!isLoading"
class="adf-viewer-render-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<adf-preview-extension *ngIf="!!externalViewer"
[id]="externalViewer.component"
[url]="urlFile"
[extension]="externalViewer.fileExtension"
[nodeId]="nodeId"
[attr.data-automation-id]="externalViewer.component" />
</ng-container>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer [thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFile"
[fileName]="internalFileName"
[cacheType]="cacheTypeForContent"
(pagesLoaded)="isContentReady = true"
(close)="onClose()"
(error)="onUnsupportedFile()" />
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFile"
[readOnly]="readOnly"
[fileName]="internalFileName"
[allowedEditActions]="allowedEditActions"
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
(imageLoaded)="isContentReady = true"
(isSaving)="isSaving.emit($event)"
/>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player id="adf-mdedia-player"
[urlFile]="urlFile"
[tracks]="tracks"
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="internalFileName"
(error)="onUnsupportedFile()"
(canPlay)="isContentReady = true"/>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile"
[blobFile]="blobFile" />
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<adf-preview-extension *ngIf="checkExtensions(ext.fileExtension)"
[id]="ext.component"
[url]="urlFile"
[extension]="extension"
[nodeId]="nodeId"
[attr.data-automation-id]="ext.component" />
</ng-container>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer
[thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFile"
[fileName]="internalFileName"
[cacheType]="cacheTypeForContent"
(pagesLoaded)="markAsLoaded()"
(close)="onClose()"
(error)="onUnsupportedFile()"
/>
<ng-container *ngFor="let extensionTemplate of extensionTemplates">
<span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
<ng-template [ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension }" />
</span>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer
[urlFile]="urlFile"
[readOnly]="readOnly"
[fileName]="internalFileName"
[allowedEditActions]="allowedEditActions"
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
(imageLoaded)="markAsLoaded()"
(isSaving)="isSaving.emit($event)"
/>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player
id="adf-mdedia-player"
[urlFile]="urlFile"
[tracks]="tracks"
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="internalFileName"
(error)="onUnsupportedFile()"
(canPlay)="markAsLoaded()"
/>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile" [blobFile]="blobFile" />
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<adf-preview-extension
*ngIf="checkExtensions(ext.fileExtension)"
[id]="ext.component"
[url]="urlFile"
[extension]="extension"
[nodeId]="nodeId"
[attr.data-automation-id]="ext.component"
/>
</ng-container>
<ng-container *ngFor="let extensionTemplate of extensionTemplates">
<span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
<ng-template
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension }"
/>
</span>
</ng-container>
</ng-container>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format [customError]="customError" />
</ng-container>
</div>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format [customError]="customError"/>
</ng-container>
</div>
</div>
</ng-container>
</div>
<ng-container *ngIf="viewerTemplateExtensions">
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletInjector]="injector" />
</ng-container>

View File

@ -24,7 +24,7 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { NoopTranslateModule, UnitTestingUtils } from '../../../testing';
import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { ViewerRenderComponent } from './viewer-render.component';
import { ImgViewerComponent, MediaPlayerComponent, PdfViewerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
import { ImgViewerComponent, MediaPlayerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
@ -483,16 +483,14 @@ describe('ViewerComponent', () => {
describe('Spinner', () => {
const getMainLoader = (): DebugElement => testingUtils.getByCSS('.adf-viewer-render-main-loader');
it('should not show spinner by default', (done) => {
component.isLoading$.subscribe((isLoading) => {
fixture.detectChanges();
expect(isLoading).toBeFalse();
expect(getMainLoader()).toBeNull();
done();
});
it('should show spinner when isLoading is true', () => {
component.isLoading = true;
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
});
it('should display spinner when viewerType is media', () => {
it('should show spinner until content is ready when viewerType is media', () => {
component.isLoading = false;
component.urlFile = 'some-file.mp4';
component.ngOnChanges();
@ -508,21 +506,24 @@ describe('ViewerComponent', () => {
expect(component.viewerType).toBe('media');
});
it('should display spinner when viewerType is pdf', () => {
// eslint-disable-next-line ban/ban
xit('should show spinner until content is ready when viewerType is pdf', () => {
component.isLoading = false;
component.urlFile = 'some-url.pdf';
expect(getMainLoader()).toBeNull();
component.ngOnChanges();
fixture.detectChanges();
const imgViewer = testingUtils.getByDirective(PdfViewerComponent);
imgViewer.triggerEventHandler('pagesLoaded', null);
expect(getMainLoader()).not.toBeNull();
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('pdf');
});
it('should display spinner when viewerType is image', () => {
it('should show spinner until content is ready when viewerType is image', () => {
component.isLoading = false;
component.urlFile = 'some-url.png';
component.ngOnChanges();
@ -536,5 +537,16 @@ describe('ViewerComponent', () => {
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('image');
});
it('should not show spinner when isLoading = false and isContentReady = false for other viewer types', () => {
component.isLoading = false;
component.urlFile = 'some-url.txt';
component.ngOnChanges();
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.isContentReady).toBeFalse();
});
});
});

View File

@ -16,7 +16,7 @@
*/
import { AppExtensionService, ExtensionsModule, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { AsyncPipe, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet } from '@angular/common';
import { NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet } from '@angular/common';
import { Component, EventEmitter, Injector, Input, OnChanges, OnInit, Output, TemplateRef, ViewEncapsulation } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@ -28,9 +28,6 @@ import { MediaPlayerComponent } from '../media-player/media-player.component';
import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
import { TxtViewerComponent } from '../txt-viewer/txt-viewer.component';
import { UnknownFormatComponent } from '../unknown-format/unknown-format.component';
import { BehaviorSubject } from 'rxjs';
type ViewerType = 'media' | 'image' | 'pdf' | 'external' | 'text' | 'custom' | 'unknown';
@Component({
selector: 'adf-viewer-render',
@ -53,8 +50,7 @@ type ViewerType = 'media' | 'image' | 'pdf' | 'external' | 'text' | 'custom' | '
UnknownFormatComponent,
ExtensionsModule,
NgForOf,
NgSwitchDefault,
AsyncPipe
NgSwitchDefault
],
providers: [ViewUtilService]
})
@ -90,6 +86,10 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
@Input()
fileName: string;
/** Override loading status */
@Input()
isLoading = false;
/** Enable when where is possible the editing functionalities */
@Input()
readOnly = true;
@ -141,8 +141,8 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
extensionsSupportedByTemplates: string[] = [];
extension: string;
internalFileName: string;
viewerType: ViewerType = 'unknown';
readonly isLoading$ = new BehaviorSubject(false);
viewerType: string = 'unknown';
isContentReady = false;
/**
* Returns a list of the active Viewer content extensions.
@ -182,10 +182,12 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
ngOnInit() {
this.cacheTypeForContent = 'no-cache';
this.setDefaultLoadingState();
}
ngOnChanges() {
this.isContentReady = false;
this.isLoading = !this.blobFile && !this.urlFile;
if (this.blobFile) {
this.setUpBlobData();
} else if (this.urlFile) {
@ -193,13 +195,9 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
}
}
markAsLoaded() {
this.isLoading$.next(false);
}
private setUpBlobData() {
this.internalFileName = this.fileName;
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.blobFile.type) as ViewerType;
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.blobFile.type);
this.extensionChange.emit(this.blobFile.type);
this.scrollTop();
@ -208,7 +206,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
private setUpUrlFile() {
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
this.extension = this.viewUtilService.getFileExtension(this.internalFileName);
this.viewerType = this.viewUtilService.getViewerType(this.extension, this.mimeType, this.extensionsSupportedByTemplates) as ViewerType;
this.viewerType = this.viewUtilService.getViewerType(this.extension, this.mimeType, this.extensionsSupportedByTemplates);
this.extensionChange.emit(this.extension);
this.scrollTop();
@ -237,14 +235,4 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
onClose() {
this.close.next(true);
}
private canBePreviewed(): boolean {
return this.viewerType === 'media' || this.viewerType === 'pdf' || this.viewerType === 'image';
}
private setDefaultLoadingState() {
if (this.canBePreviewed()) {
this.isLoading$.next(true);
}
}
}

View File

@ -5,9 +5,6 @@ export default {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/js-api',
moduleMNameMapper: {
'^pdfjs-dist$': 'pdfjs-dist/legacy/build/pdf'
},
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',

View File

@ -209,9 +209,9 @@ export class TagsApi extends BaseApi {
/**
* Create specified by **tags** list of tags.
* @param tags List of tags to create.
* @returns Promise<TagEntry | TagPaging>
* @returns Promise<TagEntry[]>
*/
createTags(tags: TagBody[]): Promise<TagEntry | TagPaging> {
createTags(tags: TagBody[]): Promise<TagEntry[]> {
throwIfNotDefined(tags, 'tags');
return this.post({

View File

@ -240,7 +240,7 @@ Create specified by **tags** list of tags.
|----------|-----------------------|-------------------------|
| **tags** | [TagBody[]](#TagBody) | List of tags to create. |
**Return type**: [TagEntry](#TagEntry) | [TagPaging](#TagPaging)
**Return type**: [TagEntry[]](#TagEntry)
**Example**

View File

@ -16,7 +16,7 @@
*/
import assert from 'assert';
import { AlfrescoApi, TagBody, TagEntry, TagPaging, TagsApi } from '../../src';
import { AlfrescoApi, TagBody, TagEntry, TagsApi } from '../../src';
import { EcmAuthMock, TagMock } from '../mockObjects';
describe('Tags', () => {
@ -105,10 +105,10 @@ describe('Tags', () => {
describe('createTags', () => {
it('should return created tags', (done) => {
tagMock.createTags201Response();
tagsApi.createTags([new TagBody(), new TagBody()]).then((tags: TagPaging) => {
assert.equal(tags.list.entries.length, 2);
assert.equal(tags.list.entries[0].entry.tag, 'tag-test-1');
assert.equal(tags.list.entries[1].entry.tag, 'tag-test-2');
tagsApi.createTags([new TagBody(), new TagBody()]).then((tags) => {
assert.equal(tags.length, 2);
assert.equal(tags[0].entry.tag, 'tag-test-1');
assert.equal(tags[1].entry.tag, 'tag-test-2');
done();
});
});

View File

@ -65,7 +65,7 @@ export class TagMock extends BaseMock {
createTags201Response(): void {
nock(this.host, { encodedQueryParams: true })
.post('/alfresco/api/-default-/public/alfresco/versions/1/tags')
.reply(201, this.getPaginatedListOfTags());
.reply(201, [this.mockTagEntry(), this.mockTagEntry('tag-test-2', 'd79bdbd0-9f55-45bb-9521-811e15bf48f6')]);
}
get201ResponseForAssigningTagsToNode(body: TagBody[]): void {

View File

@ -1 +1 @@
22.14.0
20.18.1

View File

@ -4,106 +4,94 @@
<div
*ngIf="hasForm()"
class="adf-cloud-form-container adf-cloud-form-{{ displayConfiguration?.options?.fullscreen ? 'fullscreen' : 'inline' }}-container"
[style]="formStyle"
>
<div class="adf-cloud-form-content" [cdkTrapFocus]="displayConfiguration?.options?.trapFocus" cdkTrapFocusAutoCapture>
<adf-toolbar class="adf-cloud-form-toolbar" *ngIf="displayConfiguration?.options?.displayToolbar">
<div class="adf-cloud-form__form-title">
<span class="adf-cloud-form__display-name" [title]="form.taskName">
{{ form.taskName }}
<ng-container *ngIf="!form.taskName">
{{ 'FORM.FORM_RENDERER.NAMELESS_TASK' | translate }}
</ng-container>
</span>
</div>
class="adf-cloud-form-container adf-cloud-form-{{displayConfiguration?.options?.fullscreen ? 'fullscreen' : 'inline'}}-container"
[style]="formStyle">
<div class="adf-cloud-form-content"
[cdkTrapFocus]="displayConfiguration?.options?.trapFocus"
cdkTrapFocusAutoCapture>
<adf-toolbar-divider *ngIf="displayConfiguration?.options?.displayCloseButton" />
<button
*ngIf="displayConfiguration?.options?.displayCloseButton"
class="adf-cloud-form-close-button"
data-automation-id="adf-toolbar-right-back"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
[attr.data-automation-id]="'adf-cloud-form-close-button'"
[title]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
mat-icon-button
title="{{ 'ADF_VIEWER.ACTIONS.CLOSE' | translate }}"
(click)="switchToDisplayMode()"
<adf-toolbar class="adf-cloud-form-toolbar" *ngIf="displayConfiguration?.options?.displayToolbar">
<div class="adf-cloud-form__form-title">
<span class="adf-cloud-form__display-name" [title]="form.taskName">
{{form.taskName}}
<ng-container *ngIf="!form.taskName">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
</div>
<adf-toolbar-divider *ngIf="displayConfiguration?.options?.displayCloseButton" />
<button
*ngIf="displayConfiguration?.options?.displayCloseButton"
class="adf-cloud-form-close-button"
data-automation-id="adf-toolbar-right-back"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
[attr.data-automation-id]="'adf-cloud-form-close-button'"
[title]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
mat-icon-button
title="{{ 'ADF_VIEWER.ACTIONS.CLOSE' | translate }}"
(click)="switchToDisplayMode()">
<mat-icon>close</mat-icon>
</button>
</adf-toolbar>
<mat-card
appearance="outlined"
class="adf-cloud-form-content-card"
[class.adf-cloud-form-content-card-fullscreen]="displayMode === 'fullScreen'"
>
<mat-icon>close</mat-icon>
</button>
</adf-toolbar>
<mat-card
appearance="outlined"
class="adf-cloud-form-content-card"
[class.adf-cloud-form-content-card-fullscreen]="displayMode === 'fullScreen'"
>
<div class="adf-cloud-form-content-card-container">
<mat-card-header *ngIf="showTitle || showRefreshButton || showValidationIcon">
<mat-card-title>
<h4>
<div *ngIf="showValidationIcon" class="adf-form-validation-button">
<i id="adf-valid-form-icon" class="material-icons" *ngIf="form.isValid; else no_valid_form">check_circle</i>
<ng-template #no_valid_form>
<i id="adf-invalid-form-icon" class="material-icons adf-invalid-color">error</i>
</ng-template>
</div>
<div
*ngIf="!displayConfiguration?.options?.fullscreen && findDisplayConfiguration('fullScreen')"
class="adf-cloud-form-fullscreen-button"
>
<button
mat-icon-button
(click)="switchToDisplayMode('fullScreen')"
[attr.data-automation-id]="'adf-cloud-form-fullscreen-button'"
>
<mat-icon>fullscreen</mat-icon>
</button>
</div>
<div *ngIf="showRefreshButton" class="adf-cloud-form-reload-button" [title]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate">
<button mat-icon-button (click)="onRefreshClicked()" [attr.aria-label]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate">
<mat-icon>refresh</mat-icon>
</button>
</div>
<span *ngIf="isTitleEnabled()" class="adf-cloud-form-title" [title]="form.taskName"
>{{ form.taskName }}
<ng-container *ngIf="!form.taskName">
{{ 'FORM.FORM_RENDERER.NAMELESS_TASK' | translate }}
</ng-container>
</span>
</h4>
</mat-card-title>
</mat-card-header>
<mat-card-content class="adf-form-container-card-content">
<adf-form-renderer [formDefinition]="form" [readOnly]="readOnly" />
</mat-card-content>
<mat-card-actions *ngIf="form.hasOutcomes()" class="adf-cloud-form-content-card-actions" align="end">
<mat-checkbox
id="adf-form-open-next-task"
*ngIf="showNextTaskCheckbox && showCompleteButton"
[checked]="isNextTaskCheckboxChecked"
(change)="onNextTaskCheckboxCheckedChanged($event)"
>{{ 'ADF_CLOUD_TASK_FORM.OPEN_NEXT_TASK.LABEL' | translate }}</mat-checkbox
>
<div class="adf-cloud-form-content-card-container">
<mat-card-header *ngIf="showTitle || showRefreshButton || showValidationIcon">
<mat-card-title>
<h4>
<div *ngIf="showValidationIcon" class="adf-form-validation-button">
<i id="adf-valid-form-icon" class="material-icons"
*ngIf="form.isValid; else no_valid_form">check_circle</i>
<ng-template #no_valid_form>
<i id="adf-invalid-form-icon" class="material-icons adf-invalid-color">error</i>
</ng-template>
</div>
<div *ngIf="!displayConfiguration?.options?.fullscreen && findDisplayConfiguration('fullScreen')" class="adf-cloud-form-fullscreen-button">
<button mat-icon-button (click)="switchToDisplayMode('fullScreen')" [attr.data-automation-id]="'adf-cloud-form-fullscreen-button'">
<mat-icon>fullscreen</mat-icon>
</button>
</div>
<div *ngIf="showRefreshButton" class="adf-cloud-form-reload-button" [title]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate">
<button mat-icon-button (click)="onRefreshClicked()" [attr.aria-label]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate">
<mat-icon>refresh</mat-icon>
</button>
</div>
<span *ngIf="isTitleEnabled()" class="adf-cloud-form-title" [title]="form.taskName"
>{{form.taskName}}
<ng-container *ngIf="!form.taskName">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
</h4>
</mat-card-title>
</mat-card-header>
<mat-card-content class="adf-form-container-card-content">
<adf-form-renderer [formDefinition]="form" [readOnly]="readOnly" />
</mat-card-content>
<mat-card-actions *ngIf="form.hasOutcomes()" class="adf-form-mat-card-actions" align="end">
<mat-checkbox id="adf-form-open-next-task" *ngIf="showNextTaskCheckbox" [checked]="isNextTaskCheckboxChecked" (change)="onNextTaskCheckboxCheckedChanged($event)">{{'ADF_CLOUD_TASK_FORM.OPEN_NEXT_TASK.LABEL' | translate}}</mat-checkbox>
<span class="adf-card-actions-spacer"></span>
<ng-content select="adf-cloud-form-custom-outcomes" />
<ng-container *ngFor="let outcome of form.outcomes">
<button
*ngIf="outcome.isVisible"
[id]="'adf-form-' + outcome.name | formatSpace"
[id]="'adf-form-'+ outcome.name | formatSpace"
[color]="getColorForOutcome(outcome.name)"
mat-button
[disabled]="!isOutcomeButtonEnabled(outcome)"
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
class="adf-cloud-form-custom-outcome-button"
(click)="onOutcomeClicked(outcome)"
>
{{ outcome.name | translate | uppercase }}
{{outcome.name | translate | uppercase }}
</button>
</ng-container>
</mat-card-actions>
</div>
</mat-card>
</div>
</div>
</mat-card>
</div>
</div>

View File

@ -17,7 +17,6 @@
flex: 1;
flex-direction: column;
display: flex;
position: relative;
}
.adf-card-actions-spacer {
@ -72,18 +71,14 @@
}
&-content-card {
padding-bottom: 2em;
overflow-y: auto;
position: static;
height: 70%;
&-fullscreen {
padding: 0;
height: 100%;
width: 100%;
position: relative;
&-container {
.adf-cloud-form-content-card-container {
display: flex;
flex-direction: column;
height: 100%;

View File

@ -670,36 +670,12 @@ describe('FormCloudComponent', () => {
done();
});
const formValues: TaskVariableCloud[] = [
{
name: 'var1',
value: 'value1',
id: 'var1',
type: 'string',
hasValue: () => true
}
];
const formValues: any[] = [];
const change = new SimpleChange(null, formValues, false);
formComponent.data = formValues;
formComponent.ngOnChanges({ data: change });
});
it('should not change form if custom form values is empty array', () => {
const formModel = new FormModel({
id: 'id',
taskId: 'task-id',
fields: [{ id: 'field1' }, { id: 'field2' }]
});
formComponent.form = formModel;
const formValues: TaskVariableCloud[] = [];
const change = new SimpleChange(null, formValues, false);
formComponent.ngOnChanges({ data: change });
expect(formComponent.form).toEqual(formModel);
});
it('should save task form and raise corresponding event', () => {
spyOn(formCloudService, 'saveTaskForm').and.callFake(
() =>
@ -1216,7 +1192,7 @@ describe('FormCloudComponent', () => {
expect(form.fieldValidators.length).toBe(10);
});
it('should allow controlling [open next task] checkbox visibility', async () => {
it('should allow controlling [open next task] checkbox visibility', () => {
const formModel = new FormModel({ fields: [{ id: 'field2' }] });
formComponent.form = formModel;
@ -1226,19 +1202,14 @@ describe('FormCloudComponent', () => {
};
fixture.detectChanges();
await fixture.whenStable();
expect(isCheckboxShown()).toBeFalse();
formComponent.showNextTaskCheckbox = true;
formComponent.showCompleteButton = true;
fixture.detectChanges();
await fixture.whenStable();
expect(isCheckboxShown()).toBeTrue();
formComponent.showNextTaskCheckbox = false;
formComponent.showCompleteButton = false;
fixture.detectChanges();
await fixture.whenStable();
expect(isCheckboxShown()).toBeFalse();
});
@ -1246,58 +1217,32 @@ describe('FormCloudComponent', () => {
const formModel = new FormModel({ fields: [{ id: 'field2' }] });
formComponent.form = formModel;
formComponent.showNextTaskCheckbox = true;
formComponent.showCompleteButton = true;
fixture.detectChanges();
await fixture.whenStable();
const isCheckboxChecked = async () => {
if (formComponent.showNextTaskCheckbox && formComponent.showCompleteButton) {
const checkbox = await documentRootLoader.getHarness(MatCheckboxHarness.with({ selector: '#adf-form-open-next-task' }));
return checkbox.isChecked();
}
return null;
const checkbox = await documentRootLoader.getHarness(MatCheckboxHarness.with({ selector: '#adf-form-open-next-task' }));
return checkbox.isChecked();
};
expect(await isCheckboxChecked()).toBeFalse();
formComponent.isNextTaskCheckboxChecked = true;
formComponent.showCompleteButton = true;
fixture.detectChanges();
await fixture.whenStable();
expect(await isCheckboxChecked()).toBeTrue();
formComponent.isNextTaskCheckboxChecked = false;
formComponent.showCompleteButton = false;
fixture.detectChanges();
await fixture.whenStable();
// Skip the checkbox visibility test if it's not supposed to be visible
if (formComponent.showNextTaskCheckbox && formComponent.showCompleteButton) {
expect(await isCheckboxChecked()).toBeFalse();
} else {
// Alternative test when checkbox shouldn't be visible
const checkboxElement = fixture.debugElement.query(By.css('#adf-form-open-next-task'));
expect(checkboxElement).toBeNull();
}
expect(await isCheckboxChecked()).toBeFalse();
});
it('should call onNextTaskCheckboxCheckedChanged when the checkbox is checked', async () => {
// Add fields to make sure the components are shown which contain the checkbox
// Add fields to make sure the components are shown which contain the the checkbox
const formModel = new FormModel({ fields: [{ id: 'field2' }] });
formComponent.form = formModel;
// Set both required properties to make the checkbox visible
formComponent.showNextTaskCheckbox = true;
formComponent.showCompleteButton = true;
fixture.detectChanges();
await fixture.whenStable();
// Use a specific selector to target the correct checkbox
const checkbox = await documentRootLoader.getHarnessOrNull(MatCheckboxHarness.with({ selector: '#adf-form-open-next-task' }));
// Ensure checkbox was found
expect(checkbox).not.toBeNull();
const checkbox = await documentRootLoader.getHarnessOrNull(MatCheckboxHarness);
spyOn(formComponent.nextTaskCheckboxCheckedChanged, 'emit');
await checkbox.check();
@ -1766,13 +1711,4 @@ describe('retrieve metadata on submit', () => {
expect(formComponent.disableSaveButton).toBeFalse();
});
it('should not show next task checkbox when complete button is not shown', () => {
formComponent.showNextTaskCheckbox = false;
formComponent.showCompleteButton = false;
fixture.detectChanges();
const checkbox = fixture.debugElement.query(By.css('#adf-form-open-next-task'));
expect(checkbox).toBeNull();
});
});

View File

@ -128,10 +128,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Input()
isNextTaskCheckboxChecked = false;
/** Toggle rendering of the `Complete` button. */
@Input()
showCompleteButton = false;
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved = new EventEmitter<FormModel>();
@ -242,14 +238,13 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
return;
}
const data = changes['data']?.currentValue;
if (data?.length > 0) {
const data = changes['data'];
if (data?.currentValue) {
this.refreshFormData();
return;
}
const formRepresentation = changes['form'];
if (formRepresentation?.currentValue) {
this.form = formRepresentation.currentValue;
this.onFormLoaded(this.form);

View File

@ -983,34 +983,6 @@ describe('DropdownCloudWidgetComponent', () => {
expect(widget.field.options.length).toEqual(0);
};
it('should set dropdownControl value without emitting events if the mapping is a string', () => {
widget.field = {
value: 'testValue',
options: [],
isVisible: true
} as any; // Mock field
spyOn(widget.dropdownControl, 'setValue').and.callThrough();
widget['setFormControlValue']();
expect(widget.dropdownControl.setValue).toHaveBeenCalledWith({ id: 'testValue', name: '' }, { emitEvent: false });
expect(widget.dropdownControl.value).toEqual({ id: 'testValue', name: '' });
});
it('should set dropdownControl value without emitting events if is an object', () => {
widget.field = {
value: { id: 'testValueObj', name: 'testValueObjName' },
options: [],
isVisible: true
} as any; // Mock field
spyOn(widget.dropdownControl, 'setValue').and.callThrough();
widget['setFormControlValue']();
expect(widget.dropdownControl.setValue).toHaveBeenCalledWith({ id: 'testValueObj', name: 'testValueObjName' }, { emitEvent: false });
expect(widget.dropdownControl.value).toEqual({ id: 'testValueObj', name: 'testValueObjName' });
});
it('should display options persisted from process variable', async () => {
widget.field = getVariableDropdownWidget(
'variables.json-variable',

View File

@ -195,15 +195,7 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
}
private setFormControlValue(): void {
if (Array.isArray(this.field.value)) {
this.dropdownControl.setValue(this.field?.value, { emitEvent: false });
} else if (this.field?.value && typeof this.field?.value === 'object') {
this.dropdownControl.setValue({ id: this.field?.value.id, name: this.field?.value.name }, { emitEvent: false });
} else if (this.field.value === null) {
this.dropdownControl.setValue(this.field?.value, { emitEvent: false });
} else {
this.dropdownControl.setValue({ id: this.field?.value, name: '' }, { emitEvent: false });
}
this.dropdownControl.setValue(this.field?.value, { emitEvent: false });
}
private updateFormControlState(): void {
@ -477,11 +469,7 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
const fieldValueIds = this.field.value.map((valueOption) => valueOption.id);
return fieldValueIds.every((valueOptionId) => optionIdList.includes(valueOptionId));
} else {
if (this.field?.value && typeof this.field?.value === 'object') {
return [...this.field.options].map((option) => option.id).includes(this.field.value.id);
} else {
return [...this.field.options].map((option) => option.id).includes(this.field.value);
}
return [...this.field.options].map((option) => option.id).includes(this.field.value);
}
}

View File

@ -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 { FormFieldModel } from '@alfresco/adf-core';
import { FormControl } from '@angular/forms';
import { defaultValueValidator } from './validators';
import { DEFAULT_OPTION } from './dropdown-cloud.widget';
describe('defaultValueValidator', () => {
let mockField: FormFieldModel;
beforeEach(() => {
mockField = new FormFieldModel(null, {
options: [
{ id: DEFAULT_OPTION.id, name: DEFAULT_OPTION.name },
{ id: 'opt_1', name: 'Option 1' },
{ id: 'opt_2', name: 'Option 2' }
]
});
});
it('should return null when a valid option is selected', () => {
const validator = defaultValueValidator(mockField);
const control = new FormControl({ id: 'opt_1' });
const result = validator(control);
expect(result).toBeNull();
});
it('should return a required error when no valid option is selected', () => {
const validator = defaultValueValidator(mockField);
const control = new FormControl(null);
const result = validator(control);
expect(result).toEqual({ required: true });
});
it('should return a required error when the default "choose one" option is selected', () => {
const validator = defaultValueValidator(mockField);
const control = new FormControl(DEFAULT_OPTION.id);
const result = validator(control);
expect(result).toEqual({ required: true });
});
});

View File

@ -30,7 +30,6 @@ export const defaultValueValidator =
const isSomeOptionSelected = optionsWithNoDefaultValue.some((dropdownOption) => {
const isOptionSelected = dropdownOption.id === control.value?.id;
return isOptionSelected;
});

View File

@ -312,7 +312,7 @@
"ADF_CLOUD_TASK_HEADER": {
"BUTTON": {
"CLAIM": "Beanspruchen",
"RELEASE": "Freigeben"
"RELEASE": "Anspruch aufheben"
},
"PROPERTIES": {
"TASK_NAME": "Aufgabe",
@ -373,7 +373,7 @@
"COMPLETE": "Abschließen",
"CANCEL": "Abbrechen",
"CLAIM": "Beanspruchen",
"UNCLAIM": "Freigeben "
"UNCLAIM": "Anspruch aufheben"
}
},
"ERROR": {

View File

@ -381,7 +381,7 @@
"DESTINATION_FOLDER_PATH_ERROR": "La ruta de destino es incorrecta o no existe, vuelva a -mi- ubicación"
},
"OPEN_NEXT_TASK": {
"LABEL": "Abrir la siguiente tarea"
"LABEL": "Open next task"
}
},
"ADF_CLOUD_FORM_COMPONENT": {

View File

@ -381,7 +381,7 @@
"DESTINATION_FOLDER_PATH_ERROR": "Le chemin de destination est incorrect ou n'existe pas. Revenir à 'mon emplacement'"
},
"OPEN_NEXT_TASK": {
"LABEL": "Ouvrir la tâche suivante"
"LABEL": "Open next task"
}
},
"ADF_CLOUD_FORM_COMPONENT": {

View File

@ -381,7 +381,7 @@
"DESTINATION_FOLDER_PATH_ERROR": "Il percorso di destinazione non è corretto o non esiste: eseguire il ripristino alla posizione personale"
},
"OPEN_NEXT_TASK": {
"LABEL": "Apri l'attività successiva"
"LABEL": "Open next task"
}
},
"ADF_CLOUD_FORM_COMPONENT": {

View File

@ -381,7 +381,7 @@
"DESTINATION_FOLDER_PATH_ERROR": "Ścieżka docelowa jest nieprawidłowa lub nie istnieje. Cofnij do mojej lokalizacji"
},
"OPEN_NEXT_TASK": {
"LABEL": "Otwórz następne zadanie"
"LABEL": "Open next task"
}
},
"ADF_CLOUD_FORM_COMPONENT": {

View File

@ -381,7 +381,7 @@
"DESTINATION_FOLDER_PATH_ERROR": "O caminho de destino está incorreto ou não existe, reverter para a localização -meu- "
},
"OPEN_NEXT_TASK": {
"LABEL": "Abrir a próxima tarefa"
"LABEL": "Open next task"
}
},
"ADF_CLOUD_FORM_COMPONENT": {

View File

@ -1,5 +1,6 @@
<form>
<mat-form-field
[appearance]="formFieldAppearance"
[subscriptSizing]="formFieldSubscriptSizing"
[floatLabel]="'auto'"
class="adf-people-cloud"

View File

@ -26,8 +26,8 @@ import { LocalPreferenceCloudService } from '../../../../services/local-preferen
import { mockProcessFilters } from '../../mock/process-filters-cloud.mock';
import { AppConfigService, AppConfigServiceMock, NoopTranslateModule } from '@alfresco/adf-core';
import { ProcessListCloudService } from '../../../process-list/services/process-list-cloud.service';
import { ApolloModule } from 'apollo-angular';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ApolloTestingModule } from 'apollo-angular/testing';
const ProcessFilterCloudServiceMock = {
getProcessFilters: () => of(mockProcessFilters),
@ -44,7 +44,7 @@ describe('ProcessFiltersCloudComponent', () => {
const configureTestingModule = (searchApiMethod: 'GET' | 'POST') => {
TestBed.configureTestingModule({
imports: [NoopTranslateModule, NoopAnimationsModule, ProcessFiltersCloudComponent, ApolloTestingModule],
imports: [NoopTranslateModule, NoopAnimationsModule, ProcessFiltersCloudComponent],
providers: [
{ provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
{ provide: AppConfigService, useClass: AppConfigServiceMock },
@ -55,7 +55,8 @@ describe('ProcessFiltersCloudComponent', () => {
getProcessListCounter: () => of(10)
}
},
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock }
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock },
ApolloModule
]
});
fixture = TestBed.createComponent(ProcessFiltersCloudComponent);

View File

@ -32,7 +32,6 @@ import {
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { IdentityUserService } from '../../../people/services/identity-user.service';
import { NotificationCloudService } from '../../../services/notification-cloud.service';
import { ApolloTestingModule } from 'apollo-angular/testing';
describe('ProcessFilterCloudService', () => {
let service: ProcessFilterCloudService;
@ -52,7 +51,7 @@ describe('ProcessFilterCloudService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ProcessServiceCloudTestingModule, ApolloTestingModule],
imports: [ProcessServiceCloudTestingModule],
providers: [{ provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
});
service = TestBed.inject(ProcessFilterCloudService);

View File

@ -17,16 +17,13 @@
class="adf-process-input-container"
floatLabel="always"
*ngIf="showSelectProcessDropdown"
data-automation-id="adf-select-cloud-process-dropdown"
>
data-automation-id="adf-select-cloud-process-dropdown">
<mat-label class="adf-start-process-input-label">{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.TYPE' | translate }}</mat-label>
<input
matInput
formControlName="processDefinition"
[matAutocomplete]="auto"
id="processDefinitionName"
>
id="processDefinitionName">
<div class="adf-process-input-autocomplete">
<mat-autocomplete
#auto="matAutocomplete"
@ -40,7 +37,6 @@
{{ getProcessDefinitionValue(processDef) }}
</mat-option>
</mat-autocomplete>
<button
id="adf-select-process-dropdown"
title="{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.SELECT_PROCESS_DROPDOWN' | translate}}"
@ -48,7 +44,6 @@
(click)="displayDropdown($event)">
<mat-icon>arrow_drop_down</mat-icon>
</button>
</div>
<mat-error
*ngIf="processDefinition.hasError('required')"
@ -84,8 +79,8 @@
[data]="resolvedValues"
[formId]="processDefinitionCurrent.formKey"
[displayModeConfigurations]="displayModeConfigurations"
[showSaveButton]="showSaveButton"
[showCompleteButton]="showCompleteButton"
[showSaveButton]="false"
[showCompleteButton]="false"
[showRefreshButton]="false"
[showValidationIcon]="false"
[showTitle]="false"

View File

@ -35,8 +35,7 @@ import {
FormModel,
InplaceFormInputComponent,
LocalizedDatePipe,
TranslationService,
isOutcomeButtonVisible
TranslationService
} from '@alfresco/adf-core';
import { AbstractControl, FormControl, FormGroup, ReactiveFormsModule, ValidatorFn, Validators } from '@angular/forms';
import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete';
@ -96,6 +95,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
@ViewChild(MatAutocompleteTrigger)
inputAutocomplete: MatAutocompleteTrigger;
@ViewChild('startForm') startForm: FormCloudComponent;
/** (required) Name of the app. */
@Input()
appName: string = '';
@ -206,9 +207,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
private readonly hasVisibleOutcomesSubject = new BehaviorSubject<boolean>(false);
private readonly dialog = inject(MatDialog);
showSaveButton = false;
showCompleteButton = false;
get isProcessFormValid(): boolean {
if (this.hasForm && this.isFormCloudLoaded) {
return (this.formCloud ? !Object.keys(this.formCloud.values).length : false) || this.formCloud?.isValid || this.isProcessStarting;
@ -258,7 +256,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
.subscribe((processDefinitionName) => {
this.selectProcessDefinitionByProcessDefinitionName(processDefinitionName);
});
this.showStartProcessButton$ = combineLatest([this.displayStartSubject, this.hasVisibleOutcomesSubject]).pipe(
map(([displayStart, hasVisibleOutcomes]) => (displayStart !== null ? displayStart === 'true' : !hasVisibleOutcomes))
);
@ -287,14 +284,9 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
this.isFormCloudLoaded = true;
this.formCloud = form;
const anyOutcomeVisible = form?.outcomes?.some((outcome) =>
isOutcomeButtonVisible(outcome, {
isFormReadOnly: form.readOnly,
showCompleteButton: this.showCompleteButton,
showSaveButton: this.showSaveButton
})
);
this.hasVisibleOutcomesSubject.next(anyOutcomeVisible);
if (this.startForm) {
this.hasVisibleOutcomesSubject.next(this.startForm.hasVisibleOutcomes);
}
}
private getMaxNameLength(): number {

View File

@ -21,7 +21,6 @@ import { ScreenRenderingService } from '../../../services/public-api';
import { MatCardModule } from '@angular/material/card';
import { UserTaskCustomUi } from '../../models/screen-cloud.model';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCheckboxChange } from '@angular/material/checkbox';
@Component({
selector: 'adf-cloud-task-screen',
@ -69,21 +68,13 @@ export class TaskScreenCloudComponent implements OnInit {
@Input()
rootProcessInstanceId: string = '';
/** Whether the `Open next task` checkbox is checked by default or not. */
@Input()
isNextTaskCheckboxChecked = false;
/** Toggle rendering of the `Open next task` checkbox. */
@Input()
showNextTaskCheckbox = false;
/** Emitted when the task is saved. */
@Output()
taskSaved = new EventEmitter();
/** Emitted when the task is completed. */
@Output()
taskCompleted = new EventEmitter<any>();
taskCompleted = new EventEmitter();
/** Emitted when there is an error. */
@Output()
@ -101,10 +92,6 @@ export class TaskScreenCloudComponent implements OnInit {
@Output()
unclaimTask = new EventEmitter<any>();
/** Emitted when the `Open next task` checkbox was toggled. */
@Output()
nextTaskCheckboxCheckedChanged = new EventEmitter<MatCheckboxChange>();
@ViewChild('container', { read: ViewContainerRef, static: true })
container: ViewContainerRef;
@ -153,12 +140,6 @@ export class TaskScreenCloudComponent implements OnInit {
if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'rootProcessInstanceId')) {
this.componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId);
}
if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showNextTaskCheckbox')) {
this.componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox);
}
if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'isNextTaskCheckboxChecked')) {
this.componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked);
}
}
subscribeToOutputs() {
@ -166,9 +147,7 @@ export class TaskScreenCloudComponent implements OnInit {
this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit());
}
if (this.componentRef.instance?.taskCompleted) {
this.componentRef.instance.taskCompleted
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((openNextTask) => this.taskCompleted.emit(openNextTask));
this.componentRef.instance.taskCompleted.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskCompleted.emit());
}
if (this.componentRef.instance?.error) {
this.componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data));
@ -183,11 +162,6 @@ export class TaskScreenCloudComponent implements OnInit {
if (this.componentRef.instance?.cancelTask) {
this.componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data));
}
if (this.componentRef.instance?.nextTaskCheckboxCheckedChanged) {
this.componentRef.instance.nextTaskCheckboxCheckedChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data) => this.nextTaskCheckboxCheckedChanged.emit(data));
}
}
switchToDisplayMode(newDisplayMode?: string) {

View File

@ -27,14 +27,11 @@ export interface UserTaskCustomUi {
showCancelButton: boolean;
taskName: string;
taskId: string;
isNextTaskCheckboxChecked: boolean;
showNextTaskCheckbox: boolean;
cancelTask: EventEmitter<any>;
claimTask: EventEmitter<any>;
error: EventEmitter<any>;
switchToDisplayMode?: (newDisplayMode?: string) => void;
taskCompleted: EventEmitter<any>;
taskCompleted: EventEmitter<string>;
taskSaved: EventEmitter<string>;
unclaimTask: EventEmitter<any>;
nextTaskCheckboxCheckedChanged: EventEmitter<any>;
}

View File

@ -21,6 +21,7 @@ import { MatDialog } from '@angular/material/dialog';
import { of, Subject } from 'rxjs';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../../../services/local-preference-cloud.service';
import { ProcessServiceCloudTestingModule } from '../../../../../testing/process-service-cloud.testing.module';
import { AppsProcessCloudService } from '../../../../../app/services/apps-process-cloud.service';
import { fakeApplicationInstance, fakeApplicationInstanceWithEnvironment } from '../../../../../app/mock/app-model.mock';
import { ServiceTaskFilterCloudService } from '../../../services/service-task-filter-cloud.service';
@ -37,11 +38,6 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatExpansionPanelHarness } from '@angular/material/expansion/testing';
import { MatSelectHarness } from '@angular/material/select/testing';
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
import { NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core';
import { ApolloTestingModule } from 'apollo-angular/testing';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { DateFnsAdapter } from '@angular/material-date-fns-adapter';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('EditServiceTaskFilterCloudComponent', () => {
let loader: HarnessLoader;
@ -57,19 +53,8 @@ describe('EditServiceTaskFilterCloudComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
MatIconTestingModule,
NoopAnimationsModule,
EditServiceTaskFilterCloudComponent,
NoopTranslateModule,
ApolloTestingModule,
NoopAuthModule
],
providers: [
MatDialog,
{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
{ provide: DateAdapter, useClass: DateFnsAdapter, deps: [MAT_DATE_LOCALE] }
]
imports: [ProcessServiceCloudTestingModule, MatIconTestingModule, EditServiceTaskFilterCloudComponent],
providers: [MatDialog, { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
});
fixture = TestBed.createComponent(EditServiceTaskFilterCloudComponent);
component = fixture.componentInstance;

View File

@ -22,6 +22,7 @@ import { MatDialog } from '@angular/material/dialog';
import { of, Subject } from 'rxjs';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../../../services/local-preference-cloud.service';
import { ProcessServiceCloudTestingModule } from '../../../../../testing/process-service-cloud.testing.module';
import { AppsProcessCloudService } from '../../../../../app/services/apps-process-cloud.service';
import { fakeApplicationInstance } from '../../../../../app/mock/app-model.mock';
import { EditTaskFilterCloudComponent } from './edit-task-filter-cloud.component';
@ -55,11 +56,6 @@ import { MatSelectHarness } from '@angular/material/select/testing';
import { MatExpansionPanelHarness } from '@angular/material/expansion/testing';
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
import { PeopleCloudComponent } from '@alfresco/adf-process-services-cloud';
import { ApolloTestingModule } from 'apollo-angular/testing';
import { ADF_DATE_FORMATS, NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { DateFnsAdapter } from '@angular/material-date-fns-adapter';
describe('EditTaskFilterCloudComponent', () => {
let loader: HarnessLoader;
@ -77,21 +73,8 @@ describe('EditTaskFilterCloudComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
NoopAuthModule,
NoopAnimationsModule,
NoopTranslateModule,
PeopleCloudComponent,
MatIconTestingModule,
EditTaskFilterCloudComponent,
ApolloTestingModule
],
providers: [
MatDialog,
{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
{ provide: DateAdapter, useClass: DateFnsAdapter, deps: [MAT_DATE_LOCALE] },
{ provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS }
]
imports: [ProcessServiceCloudTestingModule, PeopleCloudComponent, MatIconTestingModule, EditTaskFilterCloudComponent],
providers: [MatDialog, { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
});
fixture = TestBed.createComponent(EditTaskFilterCloudComponent);
component = fixture.componentInstance;

View File

@ -40,6 +40,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { DateRangeFilterComponent } from '../../../../../common/date-range-filter/date-range-filter.component';
import { PeopleCloudComponent } from '../../../../../people/components/people-cloud.component';
import { TaskAssignmentFilterCloudComponent } from '../../task-assignment-filter/task-assignment-filter.component';
import { ApolloModule } from 'apollo-angular';
@Component({
selector: 'adf-cloud-edit-task-filter',
@ -60,7 +61,8 @@ import { TaskAssignmentFilterCloudComponent } from '../../task-assignment-filter
MatCheckboxModule,
DateRangeFilterComponent,
PeopleCloudComponent,
TaskAssignmentFilterCloudComponent
TaskAssignmentFilterCloudComponent,
ApolloModule
],
templateUrl: './edit-task-filter-cloud.component.html',
styleUrls: ['./edit-task-filter-cloud.component.scss'],

View File

@ -15,13 +15,14 @@
* limitations under the License.
*/
import { AppConfigService, NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core';
import { AppConfigService } from '@alfresco/adf-core';
import { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { first, of, throwError } from 'rxjs';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service';
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
import { defaultTaskFiltersMock, fakeGlobalFilter, taskNotifications } from '../../mock/task-filters-cloud.mock';
import { TaskFilterCloudService } from '../../services/task-filter-cloud.service';
import { TaskFiltersCloudComponent } from './task-filters-cloud.component';
@ -30,8 +31,6 @@ import { HarnessLoader } from '@angular/cdk/testing';
import { MatActionListItemHarness } from '@angular/material/list/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { TaskFilterCloudAdapter } from '../../../../models/filter-cloud-model';
import { ApolloTestingModule } from 'apollo-angular/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('TaskFiltersCloudComponent', () => {
let loader: HarnessLoader;
@ -47,7 +46,7 @@ describe('TaskFiltersCloudComponent', () => {
const configureTestingModule = (searchApiMethod: 'GET' | 'POST') => {
TestBed.configureTestingModule({
imports: [NoopAuthModule, NoopAnimationsModule, NoopTranslateModule, TaskFiltersCloudComponent, ApolloTestingModule],
imports: [ProcessServiceCloudTestingModule, TaskFiltersCloudComponent],
providers: [{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
});
taskFilterService = TestBed.inject(TaskFilterCloudService);

View File

@ -34,9 +34,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NotificationCloudService } from '../../../services/notification-cloud.service';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { IdentityUserService } from '../../../people/services/identity-user.service';
import { ApolloModule } from 'apollo-angular';
import { StorageService } from '@alfresco/adf-core';
import { TaskStatusFilter } from '../public-api';
import { ApolloTestingModule } from 'apollo-angular/testing';
describe('TaskFilterCloudService', () => {
let service: TaskFilterCloudService;
@ -56,7 +56,7 @@ describe('TaskFilterCloudService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, ProcessServiceCloudTestingModule, ApolloTestingModule],
imports: [HttpClientTestingModule, ProcessServiceCloudTestingModule, ApolloModule],
providers: [{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: UserPreferenceCloudService }]
});
service = TestBed.inject(TaskFilterCloudService);
@ -265,7 +265,7 @@ describe('Inject [LocalPreferenceCloudService] into the TaskFilterCloudService',
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, ProcessServiceCloudTestingModule, ApolloTestingModule],
imports: [HttpClientTestingModule, ProcessServiceCloudTestingModule, ApolloModule],
providers: [{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
});
service = TestBed.inject(TaskFilterCloudService);

View File

@ -1,4 +1,4 @@
<div class="adf-user-task-cloud-container">
<div class="adf-user-task-cloud-container">
<div *ngIf="!loading; else loadingTemplate">
<ng-container [ngSwitch]="taskType">
<ng-container *ngSwitchCase="taskTypeEnum.Form">
@ -38,16 +38,12 @@
[showCancelButton]="showCancelButton"
[taskName]="taskDetails.name"
[taskId]="taskId"
[showNextTaskCheckbox]="showNextTaskCheckbox && canCompleteTask()"
[isNextTaskCheckboxChecked]="isNextTaskCheckboxChecked"
(cancelTask)="onCancelClick()"
(claimTask)="onClaimTask()"
(error)="onError($event)"
(taskCompleted)="onCompleteTask($event)"
(taskCompleted)="onCompleteTask()"
(taskSaved)="onFormSaved()"
(unclaimTask)="onUnclaimTask()"
(nextTaskCheckboxCheckedChanged)="onNextTaskCheckboxCheckedChanged($event)"
/>
</ng-container>

View File

@ -334,7 +334,7 @@ describe('UserTaskCloudComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(component.taskCompleted.emit).toHaveBeenCalledOnceWith(false);
expect(component.taskCompleted.emit).toHaveBeenCalledOnceWith('task1');
});
it('should emit taskClaimed when task is claimed', async () => {

View File

@ -150,7 +150,7 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
/** Emitted when the task is completed. */
@Output()
taskCompleted = new EventEmitter<boolean>();
taskCompleted = new EventEmitter<string>();
candidateUsers: string[] = [];
candidateGroups: string[] = [];
@ -242,9 +242,9 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
this.taskClaimed.emit(this.taskId);
}
onCompleteTask(openNextTask: boolean = false): void {
onCompleteTask(): void {
this.loadTask();
this.taskCompleted.emit(openNextTask);
this.taskCompleted.emit(this.taskId);
}
onCompleteTaskForm(): void {

View File

@ -20,7 +20,8 @@ import { of, throwError } from 'rxjs';
import { By } from '@angular/platform-browser';
import { ComponentFixture, TestBed, fakeAsync, flush, discardPeriodicTasks } from '@angular/core/testing';
import { AlfrescoApiService } from '@alfresco/adf-content-services';
import { AppConfigService, NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core';
import { AppConfigService } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { TaskCloudService } from '../../services/task-cloud.service';
import {
assignedTaskDetailsCloudMock,
@ -33,8 +34,6 @@ import {
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatSelectHarness } from '@angular/material/select/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('TaskHeaderCloudComponent', () => {
let component: TaskHeaderCloudComponent;
@ -61,7 +60,7 @@ describe('TaskHeaderCloudComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TaskHeaderCloudComponent, HttpClientTestingModule, NoopTranslateModule, NoopAuthModule, NoopAnimationsModule]
imports: [ProcessServiceCloudTestingModule, TaskHeaderCloudComponent]
});
appConfigService = TestBed.inject(AppConfigService);
appConfigService.config = {
@ -189,9 +188,8 @@ describe('TaskHeaderCloudComponent', () => {
fixture.detectChanges();
expect(taskCloudService.updateTask).toHaveBeenCalled();
});
// This test is keep failing even though not clearly it just triggers an error in the afterAll so it's hidden
// eslint-disable-next-line
xit('should roll back task description on error', fakeAsync(() => {
it('should roll back task description on error', fakeAsync(() => {
spyOn(taskCloudService, 'updateTask').and.returnValue(throwError('fake'));
fixture.detectChanges();

View File

@ -941,7 +941,7 @@ describe('FormComponent', () => {
let dropdownField = formFields.find((field) => field.id === 'dropdownId');
let radioField = formFields.find((field) => field.id === 'radio');
expect(dropdownField.value).toEqual({ id: 'empty', name: 'Choose one...' });
expect(dropdownField.value).toBe('empty');
expect(radioField.value).toBeNull();
const formValues: any = {};
@ -961,10 +961,7 @@ describe('FormComponent', () => {
dropdownField = formFields.find((field) => field.id === 'dropdownId');
radioField = formFields.find((field) => field.id === 'radio');
expect(dropdownField.value).toEqual({
id: 'dropdown_option_2',
name: 'Dropdown option 2'
});
expect(dropdownField.value).toBe('dropdown_option_2');
expect(radioField.value).toBe('radio_option_3');
});

View File

@ -73,7 +73,7 @@
"BUTTON": {
"COMPLETE": "Abschließen",
"CLAIM": "Beanspruchen",
"UNCLAIM": "Freigeben ",
"UNCLAIM": "Anspruch aufheben",
"DRAG-ATTACHMENT": "Dateien zum Hochladen ablegen",
"UPLOAD-ATTACHMENT": "Anhang hochladen"
},
@ -343,7 +343,7 @@
"COMPLETE": "Abschließen",
"CANCEL": "Abbrechen",
"CLAIM": "Beanspruchen",
"UNCLAIM": "Freigeben "
"UNCLAIM": "Anspruch aufheben"
}
},
"COMPLETED_TASK": {

14475
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -29,25 +29,26 @@
"process services-cloud"
],
"dependencies": {
"@angular/animations": "18.2.13",
"@angular/cdk": "18.2.14",
"@angular/common": "18.2.13",
"@angular/compiler": "18.2.13",
"@angular/core": "18.2.13",
"@angular/forms": "18.2.13",
"@angular/material": "18.2.14",
"@angular/material-date-fns-adapter": "18.2.14",
"@angular/platform-browser": "18.2.13",
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
"@apollo/client": "3.13.1",
"@angular/animations": "17.1.3",
"@angular/cdk": "17.1.2",
"@angular/common": "17.1.3",
"@angular/compiler": "17.1.3",
"@angular/core": "17.1.3",
"@angular/forms": "17.1.3",
"@angular/material": "17.1.2",
"@angular/material-date-fns-adapter": "17.1.2",
"@angular/platform-browser": "17.1.3",
"@angular/platform-browser-dynamic": "17.1.3",
"@angular/router": "17.1.3",
"@apollo/client": "^3.13.4",
"@cspell/eslint-plugin": "8.16.1",
"@mat-datetimepicker/core": "14.0.0",
"@mat-datetimepicker/core": "13.0.2",
"@ngx-translate/core": "^14.0.0",
"@nx/webpack": "^20.0.0",
"@valano/change-font-size": "^1.0.0",
"angular-oauth2-oidc": "17.0.2",
"angular-oauth2-oidc-jwks": "^17.0.2",
"apollo-angular": "10.0.3",
"apollo-angular": "6.0.0",
"chart.js": "4.4.4",
"cropperjs": "1.6.2",
"date-fns": "^2.30.0",
@ -55,28 +56,27 @@
"event-emitter": "^0.3.5",
"graphql-ws": "^5.16.0",
"material-icons": "^1.13.12",
"minimatch-browser": "1.0.0",
"minimatch": "^10.0.1",
"ng2-charts": "^4.1.1",
"node-fetch": "^3.3.2",
"pdfjs-dist": "5.1.91",
"raphael": "2.3.0",
"rxjs": "7.8.1",
"superagent": "^9.0.1",
"ts-morph": "^20.0.0",
"tslib": "2.8.1",
"zone.js": "0.14.10"
"zone.js": "0.14.8"
},
"devDependencies": {
"@alfresco/eslint-plugin-eslint-angular": "file:lib/eslint-angular",
"@angular-devkit/architect": "0.1802.13",
"@angular-devkit/build-angular": "18.2.19",
"@angular-devkit/core": "18.2.13",
"@angular-devkit/schematics": "18.2.13",
"@angular-devkit/architect": "0.1701.4",
"@angular-devkit/build-angular": "17.3.16",
"@angular-devkit/core": "17.1.4",
"@angular-devkit/schematics": "17.1.4",
"@angular-eslint/eslint-plugin": "17.0.1",
"@angular-eslint/eslint-plugin-template": "17.0.1",
"@angular-eslint/template-parser": "17.0.1",
"@angular/cli": "~17.1.0",
"@angular/compiler-cli": "18.2.13",
"@angular/compiler-cli": "17.1.3",
"@chromatic-com/storybook": "1.7.0",
"@editorjs/code": "2.9.3",
"@editorjs/editorjs": "2.30.8",
@ -84,20 +84,20 @@
"@editorjs/inline-code": "1.5.1",
"@editorjs/list": "2.0.4",
"@editorjs/marker": "1.4.0",
"@editorjs/paragraph": "^2.11.7",
"@editorjs/paragraph": "2.11.7",
"@editorjs/underline": "1.2.1",
"@nx/angular": "19.2.0",
"@nx/angular": "17.3.1",
"@nx/eslint-plugin": "20.6.0",
"@nx/js": "18.3.5",
"@nx/js": "17.3.1",
"@nx/node": "20.6.2",
"@nx/storybook": "20.6.4",
"@nx/workspace": "18.3.5",
"@nx/workspace": "17.3.1",
"@paperist/types-remark": "0.1.3",
"@playwright/test": "1.46.1",
"@schematics/angular": "17.1.4",
"@storybook/addon-essentials": "8.4.7",
"@storybook/addon-interactions": "8.4.7",
"@storybook/angular": "8.4.7",
"@storybook/angular": "^8.4.6",
"@storybook/core-server": "8.4.7",
"@storybook/manager-api": "^8.4.5",
"@storybook/theming": "^8.2.9",
@ -108,6 +108,7 @@
"@types/jest": "^29.5.14",
"@types/jsdom": "^21.1.5",
"@types/minimatch": "5.1.2",
"@types/mocha": "^10.0.6",
"@types/node": "^18.16.9",
"@types/pdfjs-dist": "2.10.378",
"@types/shelljs": "^0.8.15",
@ -116,7 +117,6 @@
"@typescript-eslint/parser": "6.21.0",
"@typescript-eslint/typescript-estree": "7.1.1",
"@typescript-eslint/utils": "^8.8.1",
"@valano/change-font-size": "^1.0.1",
"ajv": "^8.12.0",
"commander": "12.0.0",
"dotenv": "16.4.7",
@ -159,12 +159,9 @@
"lint-staged": "15.2.9",
"mocha": "10.7.3",
"moment": "^2.29.4",
"ng-packagr": "18.2.1",
"ng-packagr": "17.1.2",
"nock": "13.5.5",
"npm-run-all": "^4.1.5",
"nx": "^20.0.0",
"postcss": "8.4.41",
"postcss-sass": "^0.5.0",
"prettier": "2.8.8",
"resize-observer-polyfill": "^1.5.1",
"rimraf": "6.0.1",
@ -174,11 +171,8 @@
"stylelint": "16.8.2",
"stylelint-config-standard-scss": "^13.1.0",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.1.1",
"typescript": "5.5.4",
"webdriver-manager": "12.1.9",
"webpack": "5.97.1",
"webpack-cli": "^5.1.4"
"typescript": "5.3.3",
"webpack": "5.97.1"
},
"license": "Apache-2.0",
"engines": {