Compare commits

..

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

147 changed files with 6857 additions and 10858 deletions

View File

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

View File

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

View File

@ -30,7 +30,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - 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 # Override language selection by uncommenting this and choosing your languages
with: with:
languages: javascript languages: javascript
@ -39,7 +39,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - 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. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -53,4 +53,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - 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 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Ensure SHA pinned actions - 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 - name: Check package-lock.json version
run: | run: |
@ -84,10 +84,10 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Get branch name - name: Get branch name
uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@b3070acf733fdcfd2698065e99017d4969256e5b # v8.22.1 uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@09293790e3d482b6376a602f607e009ef1025698 # v8.19.0
- name: Save commit message - name: Save commit message
uses: Alfresco/alfresco-build-tools/.github/actions/get-commit-message@b3070acf733fdcfd2698065e99017d4969256e5b # v8.22.1 uses: Alfresco/alfresco-build-tools/.github/actions/get-commit-message@09293790e3d482b6376a602f607e009ef1025698 # v8.19.0
- name: ci:force flag parser - name: ci:force flag parser
shell: bash shell: bash
@ -277,7 +277,7 @@ jobs:
uses: ./.github/actions/slack-group-area uses: ./.github/actions/slack-group-area
with: with:
affected: ${{ steps.e2e-result.outputs.result }} 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 name: Nofify QA failure
if: ${{ github.event_name == 'schedule' && contains(needs.*.result, 'failure') }} if: ${{ github.event_name == 'schedule' && contains(needs.*.result, 'failure') }}
env: env:

View File

@ -229,7 +229,7 @@ jobs:
needs: [release-storybook, release-npm, npm-check-bundle] needs: [release-storybook, release-npm, npm-check-bundle]
steps: 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 name: Nofify FE eng-guild-front-end workflow failed
if: ${{ contains(toJson(needs.*.result), 'failure') }} if: ${{ contains(toJson(needs.*.result), 'failure') }}
env: env:

2
.nvmrc
View File

@ -1 +1 @@
22.14.0 20.18.1

View File

@ -8,6 +8,6 @@
"source": "/**/**/i18n/en.json", "source": "/**/**/i18n/en.json",
"translation": "/%original_path%/%two_letters_code%.%file_extension%", "translation": "/%original_path%/%two_letters_code%.%file_extension%",
"export_only_approved": "true", "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. - _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. - _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. - **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. Creates tags.
- _tags:_ `TagBody[]` - list of tags to create. - _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/> - **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. 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 - _tagId:_ `string` - of the tag to be deleted

View File

@ -4,18 +4,16 @@ Added: v4.1.0
--- ---
## Form Extensibility for APA Form Widget ## Form Extensibility for APA Form Widget
This page describes how you can customize ADF forms to your own specification. This page describes how you can customize ADF forms to your own specification.
## Contents ## Contents
There are two ways to customize the form There are two ways to customize the form
- [Replace default form widgets with custom components](#replace-default-form-widgets-with-apa-form-widgets) - [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 custom form widget with custom components](#replace-custom-form-widgets-with-custom-components)
## Replace default form widgets with APA form widgets ## 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: 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 ```ts
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { WidgetComponent } from '@alfresco/adf-core'; import { WidgetComponent } from '@alfresco/adf-core';
@Component({ @Component({
selector: 'custom-editor', selector: 'custom-editor',
standalone: true,
template: ` template: `
<div style="color: red">Look, I'm a APA custom editor!</div> <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 {} 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 ```ts
import { NgModule } from '@angular/core';
import { CustomEditorComponent } from './custom-editor.component'; import { CustomEditorComponent } from './custom-editor.component';
import { FormRenderingService } from '@alfresco/adf-core'; @NgModule({
declarations: [ CustomEditorComponent ],
exports: [ CustomEditorComponent ]
})
export class CustomEditorsModule {}
```
@NgModule({...}) 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:
export class ProcessServicesExtensionModule {
```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) { constructor(formRenderingService: FormRenderingService) {
this.formRenderingService.register({ this.formRenderingService.register({
'text': () => CustomEditorComponent 'text': () => CustomEditorComponent
@ -53,12 +75,10 @@ This is an example of replacing the standard `Text` with a custom component for
} }
``` ```
> [!IMPORTANT] 5. At runtime the form should look similar to the following:
> The widget should be registered outside the custom widget component, otherwise the widget will not be registered correctly.
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 ## 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: To render the missing content:
1. Create a standalone Angular component: 1. Create an Angular component:
```ts ```ts
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { WidgetComponent } from '@alfresco/adf-core'; import { WidgetComponent } from '@alfresco/adf-core';
@Component({ @Component({
selector: 'app-demo-widget', selector: 'app-demo-widget',
standalone: true,
template: `<div style="color: green">ADF version of custom form widget</div>` template: `<div style="color: green">ADF version of custom form widget</div>`
}) })
export class DemoWidgetComponent extends WidgetComponent {} 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 ```ts
import { NgModule } from '@angular/core';
import { DemoWidgetComponent } from './demo-widget.component'; import { DemoWidgetComponent } from './demo-widget.component';
import { FormRenderingService } from '@alfresco/adf-core'; @NgModule({
declarations: [ DemoWidgetComponent ],
exports: [ DemoWidgetComponent ]
})
export class CustomWidgetsModule {}
```
@NgModule({/*...*/}) 3. Import it into your Application Module:
export class ProcessServicesExtensionModule {
```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) { constructor(formRenderingService: FormRenderingService) {
formRenderingService.register({ this.formRenderingService.register({
'custom-editor': () => DemoWidgetComponent '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) ![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 ## See Also
- [Extensibility](./extensibility.md) - [Extensibility](./extensibility.md)

View File

@ -9,7 +9,7 @@
"version": "8.0.0", "version": "8.0.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@alfresco/js-api": ">=8.0.0-alpha.7", "@alfresco/js-api": ">=8.0.0-alpha.7-0",
"commander": "^6.2.1", "commander": "^6.2.1",
"ejs": "^3.1.9", "ejs": "^3.1.9",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
@ -39,27 +39,6 @@
"tslib": "^2.6.1" "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": { "node_modules/@types/ejs": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz",
@ -456,18 +435,14 @@
} }
}, },
"node_modules/formidable": { "node_modules/formidable": {
"version": "3.5.4", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz",
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==",
"license": "MIT",
"dependencies": { "dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"dezalgo": "^1.0.4", "dezalgo": "^1.0.4",
"hexoid": "^1.0.0",
"once": "^1.4.0" "once": "^1.4.0"
}, },
"engines": {
"node": ">=14.0.0"
},
"funding": { "funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions" "url": "https://ko-fi.com/tunnckoCore/commissions"
} }
@ -601,6 +576,14 @@
"node": ">= 0.4" "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": { "node_modules/hosted-git-info": {
"version": "2.8.9", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",

View File

@ -19,7 +19,6 @@ import { AlfrescoApiConfig } from '@alfresco/js-api';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AppConfigService, AppConfigValues, StorageService } from '@alfresco/adf-core'; import { AppConfigService, AppConfigValues, StorageService } from '@alfresco/adf-core';
import { AlfrescoApiService } from '../services/alfresco-api.service'; import { AlfrescoApiService } from '../services/alfresco-api.service';
import { SecurityOptionsLoaderService } from '../security-options-loader/security-options-loader.service';
/** /**
* Create a factory to resolve an api service instance * Create a factory to resolve an api service instance
@ -38,12 +37,11 @@ export class AlfrescoApiLoaderService {
constructor( constructor(
private readonly appConfig: AppConfigService, private readonly appConfig: AppConfigService,
private readonly apiService: AlfrescoApiService, private readonly apiService: AlfrescoApiService,
private readonly securityOptionsLoaderService: SecurityOptionsLoaderService,
private storageService: StorageService private storageService: StorageService
) {} ) {}
async init(): Promise<any> { async init(): Promise<any> {
await this.appConfig.load(this.securityOptionsLoaderService.load); await this.appConfig.load();
return this.initAngularAlfrescoApi(); return this.initAngularAlfrescoApi();
} }

View File

@ -92,19 +92,4 @@ describe('LibraryRoleColumnComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(value).toBe('LIBRARY.ROLE.NONE'); expect(value).toBe('LIBRARY.ROLE.NONE');
}); });
it('should take role from obj when node entry role is not provided', () => {
component.context = {
row: {
node: { entry: {} },
obj: { role: 'SiteManager' }
}
};
let value = '';
component.displayText$.subscribe((val) => (value = val));
fixture.detectChanges();
expect(value).toBe('LIBRARY.ROLE.MANAGER');
});
}); });

View File

@ -17,7 +17,7 @@
import { ChangeDetectionStrategy, Component, DestroyRef, inject, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { ChangeDetectionStrategy, Component, DestroyRef, inject, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { Site } from '@alfresco/js-api'; import { Site, SiteEntry } from '@alfresco/js-api';
import { ShareDataRow } from '../../data/share-data-row.model'; import { ShareDataRow } from '../../data/share-data-row.model';
import { NodesApiService } from '../../../common/services/nodes-api.service'; import { NodesApiService } from '../../../common/services/nodes-api.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@ -64,23 +64,26 @@ export class LibraryRoleColumnComponent implements OnInit {
} }
protected updateValue() { protected updateValue() {
const role = this.context.row.node?.entry.role ?? this.context.row.obj.role; const node: SiteEntry = this.context.row.node;
switch (role) { if (node?.entry) {
case Site.RoleEnum.SiteManager: const role: string = node.entry.role;
this.displayText$.next('LIBRARY.ROLE.MANAGER'); switch (role) {
break; case Site.RoleEnum.SiteManager:
case Site.RoleEnum.SiteCollaborator: this.displayText$.next('LIBRARY.ROLE.MANAGER');
this.displayText$.next('LIBRARY.ROLE.COLLABORATOR'); break;
break; case Site.RoleEnum.SiteCollaborator:
case Site.RoleEnum.SiteContributor: this.displayText$.next('LIBRARY.ROLE.COLLABORATOR');
this.displayText$.next('LIBRARY.ROLE.CONTRIBUTOR'); break;
break; case Site.RoleEnum.SiteContributor:
case Site.RoleEnum.SiteConsumer: this.displayText$.next('LIBRARY.ROLE.CONTRIBUTOR');
this.displayText$.next('LIBRARY.ROLE.CONSUMER'); break;
break; case Site.RoleEnum.SiteConsumer:
default: this.displayText$.next('LIBRARY.ROLE.CONSUMER');
this.displayText$.next('LIBRARY.ROLE.NONE'); break;
break; default:
this.displayText$.next('LIBRARY.ROLE.NONE');
break;
}
} }
} }
} }

View File

@ -16,54 +16,9 @@
*/ */
import { LibraryStatusColumnComponent } from './library-status-column.component'; import { LibraryStatusColumnComponent } from './library-status-column.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { Site } from '@alfresco/js-api';
describe('LibraryStatusColumnComponent', () => { describe('LibraryStatusColumnComponent', () => {
let fixture: ComponentFixture<LibraryStatusColumnComponent>; it('should be defined', () => {
let component: LibraryStatusColumnComponent; expect(LibraryStatusColumnComponent).toBeDefined();
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ContentTestingModule, LibraryStatusColumnComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
fixture = TestBed.createComponent(LibraryStatusColumnComponent);
component = fixture.componentInstance;
});
it('should be defined', () => {
expect(LibraryStatusColumnComponent).toBeDefined();
});
it('should take default visibility from node entry', () => {
component.context = {
row: {
node: { entry: { visibility: Site.VisibilityEnum.PUBLIC } }
}
};
let value = '';
component.displayText$.subscribe((val) => (value = val));
fixture.detectChanges();
expect(value).toBe('LIBRARY.VISIBILITY.PUBLIC');
});
it('should take visibility from obj when node entry visibility is not provided', () => {
component.context = {
row: {
node: { entry: {} },
obj: { visibility: Site.VisibilityEnum.PUBLIC }
}
};
let value = '';
component.displayText$.subscribe((val) => (value = val));
fixture.detectChanges();
expect(value).toBe('LIBRARY.VISIBILITY.PUBLIC');
});
}); });

View File

@ -18,7 +18,7 @@
import { Component, DestroyRef, inject, Input, OnInit } from '@angular/core'; import { Component, DestroyRef, inject, Input, OnInit } from '@angular/core';
import { NodesApiService } from '../../../common/services/nodes-api.service'; import { NodesApiService } from '../../../common/services/nodes-api.service';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { Site } from '@alfresco/js-api'; import { Site, SiteEntry } from '@alfresco/js-api';
import { ShareDataRow } from '../../data/share-data-row.model'; import { ShareDataRow } from '../../data/share-data-row.model';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@ -62,21 +62,24 @@ export class LibraryStatusColumnComponent implements OnInit {
} }
protected updateValue() { protected updateValue() {
const visibility = this.context.row.node?.entry.visibility ?? this.context.row.obj.visibility; const node: SiteEntry = this.context.row.node;
if (node?.entry) {
const visibility: string = node.entry.visibility;
switch (visibility) { switch (visibility) {
case Site.VisibilityEnum.PUBLIC: case Site.VisibilityEnum.PUBLIC:
this.displayText$.next('LIBRARY.VISIBILITY.PUBLIC'); this.displayText$.next('LIBRARY.VISIBILITY.PUBLIC');
break; break;
case Site.VisibilityEnum.PRIVATE: case Site.VisibilityEnum.PRIVATE:
this.displayText$.next('LIBRARY.VISIBILITY.PRIVATE'); this.displayText$.next('LIBRARY.VISIBILITY.PRIVATE');
break; break;
case Site.VisibilityEnum.MODERATED: case Site.VisibilityEnum.MODERATED:
this.displayText$.next('LIBRARY.VISIBILITY.MODERATED'); this.displayText$.next('LIBRARY.VISIBILITY.MODERATED');
break; break;
default: default:
this.displayText$.next('UNKNOWN'); this.displayText$.next('UNKNOWN');
break; break;
}
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,6 @@ export abstract class InfiniteScrollDatasource<T> extends DataSource<T> {
reset(): void { reset(): void {
this.isLoading$.next(true); this.isLoading$.next(true);
this.dataStream.next([]);
this.getNextBatch({ skipCount: 0, maxItems: this.batchSize }) this.getNextBatch({ skipCount: 0, maxItems: this.batchSize })
.pipe(take(1)) .pipe(take(1))
.subscribe((firstBatch) => { .subscribe((firstBatch) => {

View File

@ -1,73 +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 { SecurityOptionsLoaderService } from './security-options-loader.service';
import { AppConfigService, AppConfigValues } from '@alfresco/adf-core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
describe('SecurityOptionsLoaderService', () => {
let service: SecurityOptionsLoaderService;
let appConfigServiceSpy: jasmine.SpyObj<AppConfigService>;
let adfHttpClientSpy: jasmine.SpyObj<AdfHttpClient>;
beforeEach(() => {
appConfigServiceSpy = jasmine.createSpyObj('AppConfigService', ['get']);
adfHttpClientSpy = jasmine.createSpyObj('AdfHttpClient', ['setDefaultSecurityOption']);
service = new SecurityOptionsLoaderService(appConfigServiceSpy, adfHttpClientSpy);
});
it('should set withCredentials when value is true', () => {
appConfigServiceSpy.get.and.callFake((key: string): any => {
if (key === AppConfigValues.AUTH_WITH_CREDENTIALS) {
return true;
}
});
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).toHaveBeenCalledWith({ withCredentials: true });
});
it('should set withCredentials when value is false', () => {
appConfigServiceSpy.get.and.callFake((key: string): any => {
if (key === AppConfigValues.AUTH_WITH_CREDENTIALS) {
return false;
}
});
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).toHaveBeenCalledWith({ withCredentials: false });
});
it('should not call setDefaultSecurityOption when value is undefined', () => {
appConfigServiceSpy.get.and.returnValue(undefined);
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).not.toHaveBeenCalled();
});
it('should not call setDefaultSecurityOption when value is null', () => {
appConfigServiceSpy.get.and.returnValue(null);
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).not.toHaveBeenCalled();
});
});

View File

@ -1,34 +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 { AppConfigService, AppConfigValues } from '@alfresco/adf-core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SecurityOptionsLoaderService {
constructor(private appConfigService: AppConfigService, private adfHttpClient: AdfHttpClient) {}
load = () => {
const withCredentials = this.appConfigService.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS);
if (withCredentials !== undefined && withCredentials !== null) {
this.adfHttpClient.setDefaultSecurityOption({ withCredentials });
}
};
}

View File

@ -89,7 +89,7 @@ describe('TagService', () => {
describe('createTags', () => { describe('createTags', () => {
it('should call createTags on tagsApi', () => { 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(); const tag1 = new TagBody();
tag1.tag = 'Some tag 1'; tag1.tag = 'Some tag 1';
const tag2 = new TagBody(); const tag2 = new TagBody();
@ -101,17 +101,19 @@ describe('TagService', () => {
}); });
it('should emit refresh when tags creation is success', async () => { it('should emit refresh when tags creation is success', async () => {
const tag: TagEntry = { const tags: TagEntry[] = [
entry: { {
id: 'Some id 1', entry: {
tag: 'Some tag 1' id: 'Some id 1',
tag: 'Some tag 1'
}
} }
}; ];
spyOn(service.refresh, 'emit'); 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(); 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. * @param tags list of tags to create.
* @returns Created tags. * @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))); return from(this.tagsApi.createTags(tags)).pipe(tap((tagEntries) => this.refresh.emit(tagEntries)));
} }

View File

@ -45,7 +45,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) { if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) {
return this.authService.addTokenToHeader(req.url, req.headers).pipe( return this.authService.addTokenToHeader(req.url, req.headers).pipe(
mergeMap((headersWithBearer) => { mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer, req.body); const headerWithContentType = this.appendJsonContentType(headersWithBearer);
const kcReq = req.clone({ headers: headerWithContentType }); const kcReq = req.clone({ headers: headerWithContentType });
return next.handle(kcReq).pipe(catchError((error) => observableThrowError(error))); return next.handle(kcReq).pipe(catchError((error) => observableThrowError(error)));
}) })
@ -55,7 +55,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
return next.handle(req).pipe(catchError((error) => observableThrowError(error))); return next.handle(req).pipe(catchError((error) => observableThrowError(error)));
} }
private appendJsonContentType(headers: HttpHeaders, reqBody: any): HttpHeaders { private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
// prevent adding any content type, to properly handle formData with boundary browser generated value, // prevent adding any content type, to properly handle formData with boundary browser generated value,
// as adding any Content-Type its going to break the upload functionality // as adding any Content-Type its going to break the upload functionality
@ -63,7 +63,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
return headers.delete('Content-Type'); return headers.delete('Content-Type');
} }
if (!headers.get('Content-Type') && !(reqBody instanceof FormData)) { if (!headers.get('Content-Type')) {
return headers.set('Content-Type', 'application/json;charset=UTF-8'); return headers.set('Content-Type', 'application/json;charset=UTF-8');
} }

View File

@ -2,15 +2,15 @@
@use '@angular/material' as mat; @use '@angular/material' as mat;
@mixin adf-breadcrumb-theme($theme) { @mixin adf-breadcrumb-theme($theme) {
$config: mat.m2-get-color-config($theme); $config: mat.get-color-config($theme);
$foreground-palette: map.get($config, foreground); $foreground-palette: map.get($config, foreground);
$primary-palette: map.get($config, primary); $primary-palette: map.get($config, primary);
$text-color: mat.m2-get-color-from-palette($foreground-palette, text); $text-color: mat.get-color-from-palette($foreground-palette, text);
$primary: mat.m2-get-color-from-palette($primary-palette, text); $primary: mat.get-color-from-palette($primary-palette, text);
adf-breadcrumb { adf-breadcrumb {
.adf-breadcrumb__show-all-button-icon--rotate { .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 { .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; @use '@angular/material' as mat;
@import './theme/theme-data'; @import './theme/theme-data';
$custom-theme: mat.m2-define-light-theme( $custom-theme: mat.define-light-theme(
( (
color: ( color: (
primary: map.get($palettes, primary), primary: map.get($palettes, primary),
accent: map.get($palettes, accent), accent: map.get($palettes, accent),
warn: map.get($palettes, warning) warn: map.get($palettes, warning),
), ),
typography: $app-typography typography: $app-typography,
) )
); );
@if $background-color { @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 { @if $text-color {

View File

@ -1,27 +1,31 @@
@use '@angular/material' as mat; @use '@angular/material' as mat;
@import './default-colors'; @import './default-colors.scss';
@import './custom-palette-creator'; @import './custom-palette-creator.scss';
@function get-mat-palettes($primary-color, $accent-color) { @function get-mat-palettes($primary-color, $accent-color) {
$mat-primary-palette: null; $mat-primary-palette: null;
@if ($primary-color) { @if ($primary-color) {
$custom-theme-primary-palette: create-color-palette($primary-color, 'primary'); $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 { } @else {
$mat-primary-palette: mat.m2-define-palette($default-primary, A100); $mat-primary-palette: mat.define-palette($default-primary, A100);
} }
$mat-accent-palette: null; $mat-accent-palette: null;
@if ($accent-color) { @if ($accent-color) {
$custom-theme-accent-palette: create-color-palette($accent-color, 'accent'); $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 { } @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'; @import '../variables/font-family';
@function get-mat-typography($base-font-size, $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', $font-family: 'Muli, Roboto, "Helvetica Neue", sans-serif',
$headline-1: mat.m2-define-typography-level(112px, 112px, 300), $headline-1: mat.define-typography-level(112px, 112px, 300),
$headline-2: mat.m2-define-typography-level(56px, 56px, 400), $headline-2: mat.define-typography-level(56px, 56px, 400),
$headline-3: mat.m2-define-typography-level(45px, 48px, 400), $headline-3: mat.define-typography-level(45px, 48px, 400),
$headline-4: mat.m2-define-typography-level(34px, 40px, 400), $headline-4: mat.define-typography-level(34px, 40px, 400),
$headline-5: mat.m2-define-typography-level(24px, 32px, 400), $headline-5: mat.define-typography-level(24px, 32px, 400),
$headline-6: mat.m2-define-typography-level(20px, 32px, 500), $headline-6: mat.define-typography-level(20px, 32px, 500),
$subtitle-1: mat.m2-define-typography-level(16px, 28px, 400), $subtitle-1: mat.define-typography-level(16px, 28px, 400),
$body-1: mat.m2-define-typography-level(15px, 24px, 400), $body-1: mat.define-typography-level(15px, 24px, 400),
$subtitle-2: mat.m2-define-typography-level(14px, 24px, 500), $subtitle-2: mat.define-typography-level(14px, 24px, 500),
$body-2: mat.m2-define-typography-level(14px, 20px, 400), $body-2: mat.define-typography-level(14px, 20px, 400),
$caption: mat.m2-define-typography-level(12px, 20px, 400), $caption: mat.define-typography-level(12px, 20px, 400),
$button: mat.m2-define-typography-level(14px, 14px, 500), $button: mat.define-typography-level(14px, 14px, 500),
// Line-height must be unit-less fraction of the font-size. // Line-height must be unit-less fraction of the font-size.
); );
@if $base-font-size { @if $base-font-size {
$custom-typography: mat.m2-define-typography-config( $custom-typography: mat.define-typography-config(
$headline-1: mat.m2-define-typography-level(8rem, 8rem, 300), $headline-1: mat.define-typography-level(8rem, 8rem, 300),
$headline-2: mat.m2-define-typography-level(4rem, 4rem, 400), $headline-2: mat.define-typography-level(4rem, 4rem, 400),
$headline-3: mat.m2-define-typography-level(3.21rem, 3.21rem, 400), $headline-3: mat.define-typography-level(3.21rem, 3.21rem, 400),
$headline-4: mat.m2-define-typography-level(2.42rem, 2.85rem, 400), $headline-4: mat.define-typography-level(2.42rem, 2.85rem, 400),
$headline-5: mat.m2-define-typography-level(1.71rem, 2.28rem, 400), $headline-5: mat.define-typography-level(1.71rem, 2.28rem, 400),
$headline-6: mat.m2-define-typography-level(1.42rem, 2.28rem, 500), $headline-6: mat.define-typography-level(1.42rem, 2.28rem, 500),
$subtitle-1: mat.m2-define-typography-level(1.14rem, 2rem, 400), $subtitle-1: mat.define-typography-level(1.14rem, 2rem, 400),
$body-1: mat.m2-define-typography-level(1.07rem, 1.71rem, 400), $body-1: mat.define-typography-level(1.07rem, 1.71rem, 400),
$subtitle-2: mat.m2-define-typography-level(1rem, 1.71rem, 500), $subtitle-2: mat.define-typography-level(1rem, 1.71rem, 500),
$body-2: mat.m2-define-typography-level(1rem, 1.42rem, 400), $body-2: mat.define-typography-level(1rem, 1.42rem, 400),
$caption: mat.m2-define-typography-level(0.86rem, 1.42rem, 400), $caption: mat.define-typography-level(0.86rem, 1.42rem, 400),
$button: mat.m2-define-typography-level(1rem, 1rem, 500), $button: mat.define-typography-level(1rem, 1rem, 500),
$font-family: $default-font-family $font-family: $default-font-family
); );
} }

View File

@ -17,37 +17,37 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { DebugFeaturesService } from './debug-features.service'; 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 { DummyFeaturesService } from './dummy-features.service';
import { StorageFeaturesService } from './storage-features.service'; import { StorageFeaturesService } from './storage-features.service';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
describe('DebugFeaturesService', () => { describe('DebugFeaturesService', () => {
let service: DebugFeaturesService; let service: DebugFeaturesService;
let mockStorageKey: string; const mockStorage = {
let mockStorage; getItem: () =>
JSON.stringify({
feature1: {
current: true
},
feature2: {
current: false,
fictive: true
}
}),
setItem: () => {}
};
beforeEach(() => { beforeEach(() => {
mockStorageKey = 'storage-key-test';
mockStorage = { [mockStorageKey]: true };
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
DebugFeaturesService, DebugFeaturesService,
{ { provide: StorageService, useValue: mockStorage },
provide: WritableFeaturesServiceConfigToken,
useValue: { storageKey: mockStorageKey }
},
{ provide: WritableFeaturesServiceToken, useClass: StorageFeaturesService }, { provide: WritableFeaturesServiceToken, useClass: StorageFeaturesService },
{ provide: OverridableFeaturesServiceToken, useClass: DummyFeaturesService } { 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); service = TestBed.inject(DebugFeaturesService);
}); });

View File

@ -16,8 +16,8 @@
*/ */
import { Inject, Injectable, Optional } from '@angular/core'; import { Inject, Injectable, Optional } from '@angular/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators'; import { skip, switchMap } from 'rxjs/operators';
import { import {
IDebugFeaturesService, IDebugFeaturesService,
IFeaturesService, IFeaturesService,
@ -29,12 +29,12 @@ import {
FlagSet, FlagSet,
IWritableFeaturesService IWritableFeaturesService
} from '../interfaces/features.interface'; } from '../interfaces/features.interface';
import { StorageService } from '@alfresco/adf-core';
@Injectable() @Injectable()
export class DebugFeaturesService implements IDebugFeaturesService { export class DebugFeaturesService implements IDebugFeaturesService {
private readonly isInDebugModeSubject = new BehaviorSubject<boolean>(false); private isInDebugMode: BehaviorSubject<boolean>;
private readonly isInDebugMode$ = this.isInDebugModeSubject.asObservable(); private isInDebugMode$: Observable<boolean>;
private readonly initSubject = new BehaviorSubject<boolean>(false);
get storageKey(): string { get storageKey(): string {
return `${this.config?.storageKey || 'feature-flags'}-override`; return `${this.config?.storageKey || 'feature-flags'}-override`;
@ -43,15 +43,14 @@ export class DebugFeaturesService implements IDebugFeaturesService {
constructor( constructor(
@Inject(OverridableFeaturesServiceToken) private overriddenFeaturesService: IFeaturesService, @Inject(OverridableFeaturesServiceToken) private overriddenFeaturesService: IFeaturesService,
@Inject(WritableFeaturesServiceToken) private writableFeaturesService: IFeaturesService & IWritableFeaturesService, @Inject(WritableFeaturesServiceToken) private writableFeaturesService: IFeaturesService & IWritableFeaturesService,
private storageService: StorageService,
@Optional() @Inject(WritableFeaturesServiceConfigToken) private config?: WritableFeaturesServiceConfig @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({ this.isInDebugMode.pipe(skip(1)).subscribe((debugMode) => {
debugMode: this.isInDebugModeSubject, this.storageService.setItem(this.storageKey, JSON.stringify(debugMode));
init: this.waitForInitializationToFinish()
}).subscribe(({ debugMode }) => {
sessionStorage.setItem(this.storageKey, JSON.stringify(debugMode));
}); });
} }
@ -88,20 +87,10 @@ export class DebugFeaturesService implements IDebugFeaturesService {
} }
enable(on: boolean): void { enable(on: boolean): void {
this.isInDebugModeSubject.next(on); this.isInDebugMode.next(on);
} }
isEnabled$(): Observable<boolean> { isEnabled$(): Observable<boolean> {
return this.isInDebugMode$; 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 { TestBed } from '@angular/core/testing';
import { StorageFeaturesService } from './storage-features.service'; import { StorageFeaturesService } from './storage-features.service';
import { StorageService } from '../../../../src/public-api';
import { FlagSet, WritableFeaturesServiceConfigToken } from '../interfaces/features.interface'; import { FlagSet, WritableFeaturesServiceConfigToken } from '../interfaces/features.interface';
import { skip, take } from 'rxjs/operators'; import { skip, take } from 'rxjs/operators';
describe('StorageFeaturesService', () => { describe('StorageFeaturesService', () => {
let storageFeaturesService: StorageFeaturesService; let storageFeaturesService: StorageFeaturesService;
describe('if flags are present in sessionStorage', () => { describe('if flags are present in LocalStorage', () => {
let mockStorageKey: string; const mockStorage = {
let mockStorage; getItem: () =>
JSON.stringify({
beforeEach(() => {
mockStorageKey = 'storage-key-test';
mockStorage = {
[mockStorageKey]: {
feature1: { feature1: {
current: true current: true
}, },
@ -38,23 +35,23 @@ describe('StorageFeaturesService', () => {
current: false, current: false,
fictive: true fictive: true
} }
} }),
}; setItem: () => {}
};
beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
{ provide: StorageService, useValue: mockStorage },
{ {
provide: WritableFeaturesServiceConfigToken, 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 = TestBed.inject(StorageFeaturesService);
storageFeaturesService.init(); storageFeaturesService.init();
}); });

View File

@ -16,8 +16,8 @@
*/ */
import { Inject, Injectable, Optional } from '@angular/core'; import { Inject, Injectable, Optional } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { BehaviorSubject, Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators'; import { map, skip } from 'rxjs/operators';
import { import {
FlagChangeset, FlagChangeset,
IFeaturesService, IFeaturesService,
@ -28,21 +28,21 @@ import {
WritableFeaturesServiceConfig WritableFeaturesServiceConfig
} from '../interfaces/features.interface'; } from '../interfaces/features.interface';
import { FlagSetParser } from './flagset.parser'; import { FlagSetParser } from './flagset.parser';
import { StorageService } from '@alfresco/adf-core';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class StorageFeaturesService implements IFeaturesService, IWritableFeaturesService { export class StorageFeaturesService implements IFeaturesService, IWritableFeaturesService {
private currentFlagState: WritableFlagChangeset = {}; private currentFlagState: WritableFlagChangeset = {};
private readonly flags = new BehaviorSubject<WritableFlagChangeset>({}); private flags = new BehaviorSubject<WritableFlagChangeset>({});
private readonly flags$ = this.flags.asObservable(); private flags$ = this.flags.asObservable();
private readonly initSubject = new BehaviorSubject<boolean>(false);
constructor(@Optional() @Inject(WritableFeaturesServiceConfigToken) private config?: WritableFeaturesServiceConfig) { constructor(
combineLatest({ private storageService: StorageService,
flags: this.flags, @Optional() @Inject(WritableFeaturesServiceConfigToken) private config?: WritableFeaturesServiceConfig
init: this.waitForInitializationToFinish() ) {
}).subscribe(({ flags }) => { this.flags.pipe(skip(1)).subscribe((flags) => {
this.currentFlagState = 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> { 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); const initialFlagChangeSet = FlagSetParser.deserialize(storedFlags);
this.flags.next(initialFlagChangeSet); this.flags.next(initialFlagChangeSet);
this.initSubject.next(true);
return of(initialFlagChangeSet); return of(initialFlagChangeSet);
} }
@ -134,8 +133,4 @@ export class StorageFeaturesService implements IFeaturesService, IWritableFeatur
this.flags.next(mergedFlags); 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 = () => { const init = () => {
adfHttpClient.disableCsrf = appConfigService.get<boolean>(AppConfigValues.DISABLECSRF, true); 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, ''); storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, '');
storagePrefixFactory.getPrefix().subscribe((property) => { storagePrefixFactory.getPrefix().subscribe((property) => {

View File

@ -18,7 +18,6 @@
import { Directive, EventEmitter, Input, Output } from '@angular/core'; import { Directive, EventEmitter, Input, Output } from '@angular/core';
import { ThemePalette } from '@angular/material/core'; import { ThemePalette } from '@angular/material/core';
import { FormFieldModel, FormFieldValidator, FormModel, FormOutcomeEvent, FormOutcomeModel } from './widgets'; import { FormFieldModel, FormFieldValidator, FormModel, FormOutcomeEvent, FormOutcomeModel } from './widgets';
import { isOutcomeButtonVisible } from './helpers/buttons-visibility';
@Directive({ @Directive({
standalone: true standalone: true
@ -104,6 +103,10 @@ export abstract class FormBaseComponent {
*/ */
formStyle: string = ''; formStyle: string = '';
get hasVisibleOutcomes(): boolean {
return this.form?.outcomes?.some((outcome) => this.isOutcomeButtonVisible(outcome, this.form.readOnly));
}
get form(): FormModel { get form(): FormModel {
return this._form; return this._form;
} }
@ -166,7 +169,22 @@ export abstract class FormBaseComponent {
} }
isOutcomeButtonVisible(outcome: FormOutcomeModel, isFormReadOnly: boolean): boolean { 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) { if (w.adf === undefined) {
w.adf = {}; w.adf = {};
} }
const originalField = this.getField(); const originalField = this.getField();
if (originalField) { if (originalField) {
const customTemplate = this.field.form.customFieldTemplates[originalField.type]; const customTemplate = this.field.form.customFieldTemplates[originalField.type];

View File

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

View File

@ -13,43 +13,6 @@
height: 100%; height: 100%;
} }
.alfresco-tabs-widget {
width: 100%;
.adf-form-tab-content {
margin-top: 1em;
}
.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-container-widget {
.adf-grid-list { .adf-grid-list {
display: grid; display: grid;
@ -61,7 +24,6 @@
display: flex; display: flex;
margin-right: -1%; margin-right: -1%;
width: 100%;
&-item { &-item {
width: 100%; width: 100%;
@ -153,18 +115,12 @@
overflow: hidden; overflow: hidden;
} }
& #{ms.$mat-tab-header} {
position: fixed;
z-index: 1000;
}
& #{ms.$mat-card-header-text} { & #{ms.$mat-card-header-text} {
margin: 0; margin: 0;
} }
& #{ms.$mat-tab-body-content} { & #{ms.$mat-tab-body-content} {
overflow: hidden; overflow: hidden;
padding-top: 0;
} }
& #{mat-tab-label-text} { & #{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

@ -10,9 +10,9 @@
> >
</div> </div>
<div> <div>
<mat-form-field class="adf-amount-widget__input" [hideRequiredMarker]="true" [floatLabel]="placeholder ? 'always' : 'auto'"> <mat-form-field class="adf-amount-widget__input" [hideRequiredMarker]="true">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id" <label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id"
>{{field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span></mat-label >{{field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span></label
> >
<span matTextPrefix class="adf-amount-widget__prefix-spacing">{{ currency }}&nbsp;</span> <span matTextPrefix class="adf-amount-widget__prefix-spacing">{{ currency }}&nbsp;</span>
<input <input

View File

@ -24,10 +24,6 @@
align-self: flex-end; align-self: flex-end;
} }
.adf-input {
margin-top: 3px;
}
&:not(#{ms.$mat-focused}):not(#{ms.$mat-form-field-invalid}) { &:not(#{ms.$mat-focused}):not(#{ms.$mat-form-field-invalid}) {
.adf-amount-widget__prefix-spacing { .adf-amount-widget__prefix-spacing {
color: var(--adf-theme-foreground-secondary-text-color); color: var(--adf-theme-foreground-secondary-text-color);

View File

@ -192,7 +192,7 @@ describe('AmountWidgetComponent - rendering', () => {
expect(inputField).toBeTruthy(); expect(inputField).toBeTruthy();
expect(await field.getPrefixText()).toBe('$'); expect(await field.getPrefixText()).toBe('$');
const widgetLabel = testingUtils.getByCSS('.adf-label').nativeElement; const widgetLabel = testingUtils.getByCSS('label.adf-label').nativeElement;
expect(widgetLabel.textContent.trim()).toBe('Test Amount*'); expect(widgetLabel.textContent.trim()).toBe('Test Amount*');
expect(widget.field.isValid).toBe(false); expect(widget.field.isValid).toBe(false);
@ -228,7 +228,7 @@ describe('AmountWidgetComponent - rendering', () => {
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
const widgetLabel = testingUtils.getByCSS('.adf-label').nativeElement; const widgetLabel = testingUtils.getByCSS('label.adf-label').nativeElement;
expect(widgetLabel.textContent.trim()).toBe('Test Amount*'); expect(widgetLabel.textContent.trim()).toBe('Test Amount*');
const field = await testingUtils.getMatFormField(); const field = await testingUtils.getMatFormField();

View File

@ -146,7 +146,7 @@ describe('FormFieldModel', () => {
}); });
expect(field.options).toEqual([{ id: 'id_one', name: 'One' }]); 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)', () => { 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.hasEmptyValue).toBe(true);
expect(field.emptyOption).toEqual({ id: 'empty', name: 'Chose one...' }); 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', () => { it('should set hasEmptyValue to true if "empty" option is present in options', () => {
@ -272,8 +272,7 @@ describe('FormFieldModel', () => {
options: [], options: [],
value: { id: 'delayed-option-id', name: 'Delayed option' } value: { id: 'delayed-option-id', name: 'Delayed option' }
}); });
expect(field.value).toBe('delayed-option-id');
expect(field.value).toEqual({ id: 'delayed-option-id', name: 'Delayed option' });
}); });
}); });
}); });
@ -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.value = 'restOpt2';
field.updateForm(); field.updateForm();
@ -1024,7 +1023,7 @@ describe('FormFieldModel', () => {
expect(field.options).toEqual(staticOptions); 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'); const field = getFieldConfig('manual', staticOptions, 'opt2');
field.updateForm(); field.updateForm();
@ -1032,15 +1031,6 @@ describe('FormFieldModel', () => {
expect(field.value).toEqual('opt2'); expect(field.value).toEqual('opt2');
expect(field.form.values['dropdown_field']).toEqual({ id: 'opt2', name: 'Option 2' }); 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', () => { 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); const isEmptyValue = !value || [this.emptyOption.id, this.emptyOption.name].includes(value);
if (isEmptyValue) { if (isEmptyValue) {
return this.emptyOption; return this.emptyOption.id;
} }
} }
if (this.isValidOption(value)) { if (this.isValidOption(value)) {
this.addOption({ id: value.id, name: value.name }); this.addOption({ id: value.id, name: value.name });
return value; return value.id;
} }
if (this.hasMultipleValues) { if (this.hasMultipleValues) {
@ -436,17 +436,6 @@ export class FormFieldModel extends FormWidgetModel {
this.form.values[this.id] = matchingOption || null; 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; break;
} }
case FormFieldTypes.RADIO_BUTTONS: { case FormFieldTypes.RADIO_BUTTONS: {

View File

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

View File

@ -11,9 +11,9 @@
<mat-form-field class="adf-date-time-widget" <mat-form-field class="adf-date-time-widget"
[class.adf-left-label-input-datepicker]="field.leftLabels" [class.adf-left-label-input-datepicker]="field.leftLabels"
[hideRequiredMarker]="true"> [hideRequiredMarker]="true">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id"> <label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }} ({{ field.dateDisplayFormat }})<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span> {{ field.name | translate }} ({{ field.dateDisplayFormat }})<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</mat-label> </label>
<input matInput <input matInput
[matDatetimepicker]="datetimePicker" [matDatetimepicker]="datetimePicker"
[id]="field.id" [id]="field.id"

View File

@ -10,9 +10,9 @@
<div> <div>
<mat-form-field [hideRequiredMarker]="true"> <mat-form-field [hideRequiredMarker]="true">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id"> <label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span> {{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</mat-label> </label>
<input matInput <input matInput
class="adf-input" class="adf-input"

View File

@ -2,9 +2,9 @@
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-invalid]="!field.isValid && isTouched()"
[class.adf-readonly]="field.readOnly"> [class.adf-readonly]="field.readOnly">
<mat-form-field floatPlaceholder="never" [hideRequiredMarker]="true"> <mat-form-field floatPlaceholder="never" [hideRequiredMarker]="true">
<mat-label class="adf-label" [attr.for]="field.id"> <label class="adf-label" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span> {{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</mat-label> </label>
<textarea matInput <textarea matInput
class="adf-input" class="adf-input"
[cdkTextareaAutosize]="true" [cdkTextareaAutosize]="true"

View File

@ -10,9 +10,9 @@
</div> </div>
<div> <div>
<mat-form-field [hideRequiredMarker]="true"> <mat-form-field [hideRequiredMarker]="true">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id"> <label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span> {{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</mat-label> </label>
<input matInput <input matInput
class="adf-input" class="adf-input"
type="text" type="text"

View File

@ -9,9 +9,9 @@
</div> </div>
<div> <div>
<mat-form-field [hideRequiredMarker]="true"> <mat-form-field [hideRequiredMarker]="true">
<mat-label *ngIf="!field.leftLabels" class="adf-label" [attr.for]="field.id"> <label *ngIf="!field.leftLabels" class="adf-label" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span> {{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</mat-label> </label>
<input matInput <input matInput
class="adf-input" class="adf-input"
type="text" type="text"

View File

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

View File

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

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "No se puede acceder al servidor '{{ hostname }}'", "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.", "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_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.", "EXTERNAL_PROPERTY_LOAD_FAILED": "Ha habido un problema al cargar una propiedad externa. Contacte con el administrador.",
"FILE_NAME": "Nombre del fichero", "FILE_NAME": "Nombre del fichero",
"TITLE": "Título", "TITLE": "Título",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "Le serveur '{{ hostname }}' n'est pas accessible", "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.", "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_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.", "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", "FILE_NAME": "Nom de fichier",
"TITLE": "Titre", "TITLE": "Titre",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "Il server '{{ hostname }}' non è raggiungibile", "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.", "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_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.", "EXTERNAL_PROPERTY_LOAD_FAILED": "Si è verificato un problema durante il caricamento delle proprietà esterne. Si prega di contattare lamministratore.",
"FILE_NAME": "Nome file", "FILE_NAME": "Nome file",
"TITLE": "Titolo", "TITLE": "Titolo",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "Serwer '{{ hostname }}' jest nieosiągalny", "REST_API_FAILED": "Serwer '{{ hostname }}' jest nieosiągalny",
"VARIABLE_DROPDOWN_OPTIONS_FAILED": "Wystąpił problem podczas ładowania elementów rozwijania. Skontaktuj się z administratorem.", "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_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.", "EXTERNAL_PROPERTY_LOAD_FAILED": "Wystąpił problem podczas ładowania właściwości zewnętrznej. Skontaktuj się z administratorem.",
"FILE_NAME": "Nazwa pliku", "FILE_NAME": "Nazwa pliku",
"TITLE": "Tytuł", "TITLE": "Tytuł",

View File

@ -51,7 +51,7 @@
"REST_API_FAILED": "O servidor `{{ hostname }}` não pode ser acedido", "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.", "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_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.", "EXTERNAL_PROPERTY_LOAD_FAILED": "Houve um problema ao carregar a propriedade externa. Entre em contacto com o administrador.",
"FILE_NAME": "Nome do ficheiro", "FILE_NAME": "Nome do ficheiro",
"TITLE": "Título", "TITLE": "Título",

View File

@ -26,70 +26,70 @@
// map SCSS variables to expose as CSS variables // map SCSS variables to expose as CSS variables
$defaults: ( $defaults: (
// theme colors // theme colors
--theme-primary-color: mat.m2-get-color-from-palette($primary), --theme-primary-color: mat.get-color-from-palette($primary),
--theme-primary-color-default-contrast: mat.m2-get-color-from-palette($primary, default-contrast), --theme-primary-color-default-contrast: mat.get-color-from-palette($primary, default-contrast),
--theme-header-text-color: mat.m2-get-color-from-palette($primary, default-contrast), --theme-header-text-color: mat.get-color-from-palette($primary, default-contrast),
--adf-theme-primary-50: mat.m2-get-color-from-palette($primary, 50), --adf-theme-primary-50: mat.get-color-from-palette($primary, 50),
--adf-theme-primary-100: mat.m2-get-color-from-palette($primary, 100), --adf-theme-primary-100: mat.get-color-from-palette($primary, 100),
--adf-theme-primary-300: mat.m2-get-color-from-palette($primary, 300), --adf-theme-primary-300: mat.get-color-from-palette($primary, 300),
--adf-theme-primary-900: mat.m2-get-color-from-palette($primary, 900), --adf-theme-primary-900: mat.get-color-from-palette($primary, 900),
--theme-warn-color: mat.m2-get-color-from-palette($warn), --theme-warn-color: mat.get-color-from-palette($warn),
--theme-warn-color-a700: mat.m2-get-color-from-palette($warn, A700), --theme-warn-color-a700: mat.get-color-from-palette($warn, A700),
--theme-warn-color-default-contrast: mat.m2-get-color-from-palette($warn, default-contrast), --theme-warn-color-default-contrast: mat.get-color-from-palette($warn, default-contrast),
--theme-accent-color: mat.m2-get-color-from-palette($accent), --theme-accent-color: mat.get-color-from-palette($accent),
--theme-accent-color-a200: mat.m2-get-color-from-palette($accent, A200), --theme-accent-color-a200: mat.get-color-from-palette($accent, A200),
--theme-accent-color-default-contrast: mat.m2-get-color-from-palette($accent, default-contrast), --theme-accent-color-default-contrast: mat.get-color-from-palette($accent, default-contrast),
--theme-accent-500: mat.m2-get-color-from-palette($accent, 500), --theme-accent-500: mat.get-color-from-palette($accent, 500),
--adf-theme-foreground-base-color: mat.m2-get-color-from-palette($foreground, base), --adf-theme-foreground-base-color: mat.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-065: mat.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-base-color-045: mat.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-disabled-text-color: mat.get-color-from-palette($foreground, disabled-text),
--adf-theme-foreground-divider-color: mat.m2-get-color-from-palette($foreground, divider), --adf-theme-foreground-divider-color: mat.get-color-from-palette($foreground, divider),
--adf-theme-foreground-icon-color: mat.m2-get-color-from-palette($foreground, icon), --adf-theme-foreground-icon-color: mat.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-icon-color-054: mat.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-secondary-text-color: mat.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: mat.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-087: mat.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-075: mat.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-064: mat.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-054: mat.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-040: mat.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-027: mat.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-025: mat.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-014: mat.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-foreground-text-color-007: mat.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: mat.get-color-from-palette($background, card),
--adf-theme-background-card-color-087: mat.m2-get-color-from-palette($background, card, 0.87), --adf-theme-background-card-color-087: mat.get-color-from-palette($background, card, 0.87),
--theme-background-color: mat.m2-get-color-from-palette($background, background), --theme-background-color: mat.get-color-from-palette($background, background),
--adf-theme-background-dialog-color: mat.m2-get-color-from-palette($background, dialog), --adf-theme-background-dialog-color: mat.get-color-from-palette($background, dialog),
--adf-theme-background-hover-color: mat.m2-get-color-from-palette($background, hover), --adf-theme-background-hover-color: mat.get-color-from-palette($background, hover),
--adf-theme-background-selected-button-color: mat.m2-get-color-from-palette($background, selected-button), --adf-theme-background-selected-button-color: mat.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-status-bar-color: mat.get-color-from-palette($background, status-bar),
--adf-theme-background-unselected-chip-color: mat.m2-get-color-from-palette($background, unselected-chip), --adf-theme-background-unselected-chip-color: mat.get-color-from-palette($background, unselected-chip),
// typography // typography
--theme-font-family: mat.m2-font-family($typography), --theme-font-family: mat.font-family($typography),
--theme-font-weight: normal, --theme-font-weight: normal,
--theme-body-1-font-size: mat.m2-font-size($typography, body-2), --theme-body-1-font-size: mat.font-size($typography, body-2),
--theme-body-2-font-size: mat.m2-font-size($typography, subtitle-2), --theme-body-2-font-size: mat.font-size($typography, subtitle-2),
--theme-body-1-line-height: mat.m2-line-height($typography, body-2), --theme-body-1-line-height: mat.line-height($typography, body-2),
--theme-display-1-font-size: mat.m2-font-size($typography, headline-4), --theme-display-1-font-size: mat.font-size($typography, headline-4),
--theme-display-3-font-size: mat.m2-font-size($typography, headline-2), --theme-display-3-font-size: mat.font-size($typography, headline-2),
--theme-display-4-font-size: mat.m2-font-size($typography, headline-1), --theme-display-4-font-size: mat.font-size($typography, headline-1),
--theme-caption-font-size: mat.m2-font-size($typography, caption), --theme-caption-font-size: mat.font-size($typography, caption),
--theme-title-font-size: mat.m2-font-size($typography, headline-6), --theme-title-font-size: mat.font-size($typography, headline-6),
--theme-subheading-1-font-size: mat.m2-font-size($typography, body-1), --theme-subheading-1-font-size: mat.font-size($typography, body-1),
--theme-subheading-2-font-size: mat.m2-font-size($typography, subtitle-1), --theme-subheading-2-font-size: mat.font-size($typography, subtitle-1),
--theme-button-font-size: mat.m2-font-size($typography, button), --theme-button-font-size: mat.font-size($typography, button),
--theme-headline-font-size: mat.m2-font-size($typography, headline-5), --theme-headline-font-size: mat.font-size($typography, headline-5),
--theme-headline-line-height: mat.m2-line-height($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-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-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-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'), --theme-adf-task-title-font-size: map-get($custom-css-variables, 'theme-adf-task-title-font-size'),
// specific colors // specific colors
--adf-theme-mat-grey-color-a200: mat.m2-get-color-from-palette(mat.$m2-grey-palette, 'A200'), --adf-theme-mat-grey-color-a200: mat.get-color-from-palette(mat.$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-a400: mat.get-color-from-palette(mat.$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-50: mat.get-color-from-palette(mat.$grey-palette, 50),
// spacing // spacing
--adf-theme-spacing: map-get($custom-css-variables, 'theme-adf-spacing'), --adf-theme-spacing: map-get($custom-css-variables, 'theme-adf-spacing'),
// components // 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-container: '.mat-mdc-tab-label-container';
$mat-tab-label-text: '.mdc-tab__text-label'; $mat-tab-label-text: '.mdc-tab__text-label';
$mat-tab-body: '.mat-mdc-tab-body'; $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-body-content: '.mat-mdc-tab-body-content';
$mat-tab-ink-bar: '.mdc-tab-indicator'; $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: '.mat-mdc-chip';
$mat-chip-list: '.mat-mdc-chip-list'; $mat-chip-list: '.mat-mdc-chip-list';
$mat-checkbox: '.mat-mdc-checkbox'; $mat-checkbox: '.mat-mdc-checkbox';

View File

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

View File

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

View File

@ -114,7 +114,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
displayPage: number; displayPage: number;
totalPages: number; totalPages: number;
loadingPercent: number; loadingPercent: number;
pdfViewer: PDFViewer; pdfViewer: any;
pdfJsWorkerUrl: string; pdfJsWorkerUrl: string;
pdfJsWorkerInstance: Worker; pdfJsWorkerInstance: Worker;
currentScaleMode: PdfScaleMode = 'init'; currentScaleMode: PdfScaleMode = 'init';
@ -132,12 +132,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
documentOverflow = false; documentOverflow = false;
get currentScaleText(): string { get currentScaleText(): string {
const currentScaleValueStr = this.pdfViewer?.currentScaleValue; return this.pdfViewer?.currentScaleValue ? Math.round(this.pdfViewer.currentScaleValue * 100) + '%' : '';
const scaleNumber = Number(currentScaleValueStr);
const currentScaleText = scaleNumber ? `${Math.round(scaleNumber * 100)}%` : '';
return currentScaleText;
} }
private pdfjsLib = inject(PDFJS_MODULE); private pdfjsLib = inject(PDFJS_MODULE);
@ -457,9 +452,10 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
*/ */
setScaleUpdatePages(newScale: number) { setScaleUpdatePages(newScale: number) {
if (this.pdfViewer) { if (this.pdfViewer) {
if (!this.isSameScale(this.pdfViewer.currentScaleValue, newScale.toString())) { if (!this.isSameScale(this.pdfViewer.currentScaleValue, newScale)) {
this.pdfViewer.currentScaleValue = newScale.toString(); this.pdfViewer.currentScaleValue = newScale;
} }
this.pdfViewer.update(); this.pdfViewer.update();
} }
this.setDocumentOverflow(); this.setDocumentOverflow();
@ -472,7 +468,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
* @param newScale - new scale page * @param newScale - new scale page
* @returns `true` if the scale is the same, otherwise `false` * @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; 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-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-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> <h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div> <div>
<mat-spinner class="adf-viewer-render__loading-screen__spinner" /> <mat-spinner class="adf-viewer-render__loading-screen__spinner"/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<ng-container *ngIf="urlFile || blobFile"> <div *ngIf="!isLoading"
<div [hidden]="isLoading$ | async" class="adf-viewer-render-main"> class="adf-viewer-render-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container"> <div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container" [ngSwitch]="viewerType"> <div class="adf-viewer-render-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'"> <ng-container *ngSwitchCase="'external'">
<adf-preview-extension <adf-preview-extension *ngIf="!!externalViewer"
*ngIf="!!externalViewer" [id]="externalViewer.component"
[id]="externalViewer.component" [url]="urlFile"
[url]="urlFile" [extension]="externalViewer.fileExtension"
[extension]="externalViewer.fileExtension" [nodeId]="nodeId"
[nodeId]="nodeId" [attr.data-automation-id]="externalViewer.component" />
[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>
<ng-container *ngSwitchCase="'pdf'"> <ng-container *ngFor="let extensionTemplate of extensionTemplates">
<adf-pdf-viewer <span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
[thumbnailsTemplate]="thumbnailsTemplate" <ng-template [ngTemplateOutlet]="extensionTemplate.template"
[allowThumbnails]="allowThumbnails" [ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension }" />
[blobFile]="blobFile" </span>
[urlFile]="urlFile"
[fileName]="internalFileName"
[cacheType]="cacheTypeForContent"
(pagesLoaded)="markAsLoaded()"
(close)="onClose()"
(error)="onUnsupportedFile()"
/>
</ng-container> </ng-container>
</ng-container>
<ng-container *ngSwitchCase="'image'"> <ng-container *ngSwitchDefault>
<adf-img-viewer <adf-viewer-unknown-format [customError]="customError"/>
[urlFile]="urlFile" </ng-container>
[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>
</div> </div>
</div> </div>
</ng-container> </div>
<ng-container *ngIf="viewerTemplateExtensions"> <ng-container *ngIf="viewerTemplateExtensions">
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletInjector]="injector" /> <ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletInjector]="injector" />
</ng-container> </ng-container>

View File

@ -24,7 +24,7 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { NoopTranslateModule, UnitTestingUtils } from '../../../testing'; import { NoopTranslateModule, UnitTestingUtils } from '../../../testing';
import { RenderingQueueServices } from '../../services/rendering-queue.services'; import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { ViewerRenderComponent } from './viewer-render.component'; 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'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({ @Component({
@ -483,16 +483,14 @@ describe('ViewerComponent', () => {
describe('Spinner', () => { describe('Spinner', () => {
const getMainLoader = (): DebugElement => testingUtils.getByCSS('.adf-viewer-render-main-loader'); const getMainLoader = (): DebugElement => testingUtils.getByCSS('.adf-viewer-render-main-loader');
it('should not show spinner by default', (done) => { it('should show spinner when isLoading is true', () => {
component.isLoading$.subscribe((isLoading) => { component.isLoading = true;
fixture.detectChanges(); fixture.detectChanges();
expect(isLoading).toBeFalse(); expect(getMainLoader()).not.toBeNull();
expect(getMainLoader()).toBeNull();
done();
});
}); });
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.urlFile = 'some-file.mp4';
component.ngOnChanges(); component.ngOnChanges();
@ -508,21 +506,24 @@ describe('ViewerComponent', () => {
expect(component.viewerType).toBe('media'); 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'; component.urlFile = 'some-url.pdf';
expect(getMainLoader()).toBeNull();
component.ngOnChanges(); component.ngOnChanges();
fixture.detectChanges(); fixture.detectChanges();
const imgViewer = testingUtils.getByDirective(PdfViewerComponent); expect(getMainLoader()).not.toBeNull();
imgViewer.triggerEventHandler('pagesLoaded', null);
fixture.detectChanges(); fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('pdf'); 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.urlFile = 'some-url.png';
component.ngOnChanges(); component.ngOnChanges();
@ -536,5 +537,16 @@ describe('ViewerComponent', () => {
expect(getMainLoader()).toBeNull(); expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('image'); 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 { 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 { Component, EventEmitter, Injector, Input, OnChanges, OnInit, Output, TemplateRef, ViewEncapsulation } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 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 { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
import { TxtViewerComponent } from '../txt-viewer/txt-viewer.component'; import { TxtViewerComponent } from '../txt-viewer/txt-viewer.component';
import { UnknownFormatComponent } from '../unknown-format/unknown-format.component'; import { UnknownFormatComponent } from '../unknown-format/unknown-format.component';
import { BehaviorSubject } from 'rxjs';
type ViewerType = 'media' | 'image' | 'pdf' | 'external' | 'text' | 'custom' | 'unknown';
@Component({ @Component({
selector: 'adf-viewer-render', selector: 'adf-viewer-render',
@ -53,8 +50,7 @@ type ViewerType = 'media' | 'image' | 'pdf' | 'external' | 'text' | 'custom' | '
UnknownFormatComponent, UnknownFormatComponent,
ExtensionsModule, ExtensionsModule,
NgForOf, NgForOf,
NgSwitchDefault, NgSwitchDefault
AsyncPipe
], ],
providers: [ViewUtilService] providers: [ViewUtilService]
}) })
@ -90,6 +86,10 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
@Input() @Input()
fileName: string; fileName: string;
/** Override loading status */
@Input()
isLoading = false;
/** Enable when where is possible the editing functionalities */ /** Enable when where is possible the editing functionalities */
@Input() @Input()
readOnly = true; readOnly = true;
@ -141,8 +141,8 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
extensionsSupportedByTemplates: string[] = []; extensionsSupportedByTemplates: string[] = [];
extension: string; extension: string;
internalFileName: string; internalFileName: string;
viewerType: ViewerType = 'unknown'; viewerType: string = 'unknown';
readonly isLoading$ = new BehaviorSubject(false); isContentReady = false;
/** /**
* Returns a list of the active Viewer content extensions. * Returns a list of the active Viewer content extensions.
@ -182,10 +182,12 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
ngOnInit() { ngOnInit() {
this.cacheTypeForContent = 'no-cache'; this.cacheTypeForContent = 'no-cache';
this.setDefaultLoadingState();
} }
ngOnChanges() { ngOnChanges() {
this.isContentReady = false;
this.isLoading = !this.blobFile && !this.urlFile;
if (this.blobFile) { if (this.blobFile) {
this.setUpBlobData(); this.setUpBlobData();
} else if (this.urlFile) { } else if (this.urlFile) {
@ -193,13 +195,9 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
} }
} }
markAsLoaded() {
this.isLoading$.next(false);
}
private setUpBlobData() { private setUpBlobData() {
this.internalFileName = this.fileName; 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.extensionChange.emit(this.blobFile.type);
this.scrollTop(); this.scrollTop();
@ -208,7 +206,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
private setUpUrlFile() { private setUpUrlFile() {
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile); this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
this.extension = this.viewUtilService.getFileExtension(this.internalFileName); 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.extensionChange.emit(this.extension);
this.scrollTop(); this.scrollTop();
@ -237,14 +235,4 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
onClose() { onClose() {
this.close.next(true); 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

@ -49,7 +49,8 @@
<div class="adf-viewer__display-name" <div class="adf-viewer__display-name"
id="adf-viewer-display-name" id="adf-viewer-display-name"
[title]="fileName"> [title]="fileName">
<span>{{ displayName }}</span> <span class="adf-viewer__display-name-without-extension">{{ fileNameWithoutExtension }}</span>
<span class="adf-viewer__display-name-extension">{{ fileExtension }}</span>
</div> </div>
<button *ngIf="allowNavigate && canNavigateNext" <button *ngIf="allowNavigate && canNavigateNext"
data-automation-id="adf-toolbar-next-file" data-automation-id="adf-toolbar-next-file"

View File

@ -55,10 +55,23 @@
&__display-name { &__display-name {
font-size: var(--theme-subheading-2-font-size); font-size: var(--theme-subheading-2-font-size);
opacity: 0.87;
line-height: 1.5; line-height: 1.5;
letter-spacing: -0.4px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
vertical-align: middle; vertical-align: middle;
color: var(--adf-theme-foreground-text-color); color: var(--adf-theme-foreground-text-color);
white-space: nowrap;
&-without-extension {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
max-width: 400px;
white-space: nowrap;
vertical-align: bottom;
}
} }
&-container { &-container {

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, SimpleChanges } from '@angular/core'; import { Component } from '@angular/core';
import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -143,61 +143,36 @@ describe('ViewerComponent', () => {
}); });
}); });
describe('File Name Display Tests', () => { describe('File Name Test', () => {
describe('displayFileName method', () => { const getFileNameWithoutExtension = (): string =>
it('should return full filename when total length is 80 characters or less', () => { testingUtils.getByCSS('.adf-viewer__display-name-without-extension').nativeElement.textContent;
const fileShortName = 'shortname.txt'; const getExtension = (): string => testingUtils.getByCSS('.adf-viewer__display-name-extension').nativeElement.textContent;
component.fileName = fileShortName;
fixture.detectChanges();
expect(component.getDisplayFileName()).toBe(fileShortName); it('should fileName be set by urlFile input if the fileName is not provided as Input', () => {
expect(getFileName()).toBe(fileShortName); component.fileName = '';
}); spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
const mockSimpleChanges: any = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } };
it('should truncate filename when total length exceeds 80 characters', () => { component.ngOnChanges(mockSimpleChanges);
const longName = fixture.detectChanges();
'verylongfilenamethatexceedsmaximumlengthallowedverylongfilenamethatexceedsmaximumlengthallowed.verylongextensionnamethatistoolongverylongextensionnamethatistoolong';
component.fileName = longName; expect(getFileName()).toEqual('fakeFileName.jpeg');
fixture.detectChanges(); expect(getFileNameWithoutExtension()).toBe('fakeFileName.');
expect(getExtension()).toBe('jpeg');
const result = component.getDisplayFileName();
expect(result).toContain('.....');
expect(result.length).toBe(50);
});
it('should handle empty filename', () => {
component.fileName = '';
fixture.detectChanges();
expect(component.getDisplayFileName()).toBe('');
expect(getFileName()).toBe('');
});
}); });
describe('fileName setter integration', () => { it('should set fileName providing fileName input', () => {
it('should fileName be set by urlFile input if the fileName is not provided as Input', () => { component.fileName = 'testFileName.jpg';
component.fileName = ''; spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg'); const mockSimpleChanges: any = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } };
const mockSimpleChanges = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } } as unknown as SimpleChanges;
component.ngOnChanges(mockSimpleChanges); component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges(); fixture.detectChanges();
fixture.detectChanges();
expect(getFileName()).toEqual('fakeFileName.jpeg'); expect(getFileName()).toEqual('testFileName.jpg');
}); expect(getFileNameWithoutExtension()).toBe('testFileName.');
expect(getExtension()).toBe('jpg');
it('should set fileName providing fileName input', () => {
component.fileName = 'testFileName.jpg';
spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
const mockSimpleChanges = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } } as unknown as SimpleChanges;
component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges();
expect(getFileName()).toEqual('testFileName.jpg');
});
}); });
}); });

View File

@ -297,7 +297,6 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
private _fileNameWithoutExtension: string; private _fileNameWithoutExtension: string;
private _fileExtension: string; private _fileExtension: string;
public displayName: string;
public downloadPromptTimer: number; public downloadPromptTimer: number;
public downloadPromptReminderTimer: number; public downloadPromptReminderTimer: number;
public mimeTypeIconUrl: string; public mimeTypeIconUrl: string;
@ -310,7 +309,6 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
this._fileName = fileName; this._fileName = fileName;
this._fileExtension = this.viewUtilsService.getFileExtension(this.fileName); this._fileExtension = this.viewUtilsService.getFileExtension(this.fileName);
this._fileNameWithoutExtension = this.fileName?.replace(new RegExp(`${this.fileExtension}$`), '') || ''; this._fileNameWithoutExtension = this.fileName?.replace(new RegExp(`${this.fileExtension}$`), '') || '';
this.displayName = this.getDisplayFileName();
} }
get fileName(): string { get fileName(): string {
@ -464,24 +462,6 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
this.clearDownloadPromptTimeouts(); this.clearDownloadPromptTimeouts();
} }
getDisplayFileName(): string {
const fullName = (this.fileNameWithoutExtension || '') + (this.fileExtension || '');
const maxLength = 50;
if (fullName.length <= maxLength) {
return fullName;
}
const amountOfTruncateDots = 5;
const availableSpace = maxLength - amountOfTruncateDots;
const endLength = 8;
const startLength = availableSpace - endLength;
const start = fullName.substring(0, startLength);
const end = fullName.substring(fullName.length - endLength);
return start + '.....' + end;
}
private configureAndInitDownloadPrompt() { private configureAndInitDownloadPrompt() {
this.configureDownloadPromptProperties(); this.configureDownloadPromptProperties();
if (this.enableDownloadPrompt) { if (this.enableDownloadPrompt) {

View File

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

View File

@ -209,9 +209,9 @@ export class TagsApi extends BaseApi {
/** /**
* Create specified by **tags** list of tags. * Create specified by **tags** list of tags.
* @param tags List of tags to create. * @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'); throwIfNotDefined(tags, 'tags');
return this.post({ return this.post({

View File

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

View File

@ -24,7 +24,6 @@ import { BaseApi } from './base.api';
import { buildCollectionParam } from '../../../alfrescoApiClient'; import { buildCollectionParam } from '../../../alfrescoApiClient';
import { throwIfNotDefined } from '../../../assert'; import { throwIfNotDefined } from '../../../assert';
import { RecordsIncludeQuery, RecordsPagingQuery, RecordsSourceQuery } from './types'; import { RecordsIncludeQuery, RecordsPagingQuery, RecordsSourceQuery } from './types';
import { FilePlanRolePaging, FilePlanRoleParameters } from '../model';
/** /**
* FilePlansApi service. * FilePlansApi service.
@ -159,26 +158,4 @@ export class FilePlansApi extends BaseApi {
returnType: FilePlanEntry returnType: FilePlanEntry
}); });
} }
/**
* Gets a list of roles for the specified file plan.
* @param filePlanId The identifier of a file plan. You can also use the -filePlan- alias.
* @param parameters Optional parameters
* @returns Promise<FilePlanEntry>
*/
getFilePlanRoles(filePlanId: string, parameters?: FilePlanRoleParameters): Promise<FilePlanRolePaging> {
throwIfNotDefined(filePlanId, 'filePlanId');
return this.get({
path: '/file-plans/{filePlanId}/roles',
pathParams: {
filePlanId
},
queryParams: {
where: parameters?.where?.capabilityNames
? `(capabilityName in (${parameters.where.capabilityNames.map((value) => "'" + value + "'").join(', ')}))`
: undefined
}
});
}
} }

View File

@ -1,12 +0,0 @@
# FilePlanRole
## Properties
| Name | Type |
|--------------------|-----------------------------------------------------------|
| **displayLabel** | string |
| **groupShortName** | string |
| **name** | string |
| **roleGroupName** | string |
| **capabilities** | [**FilePlanRoleCapability[]**](FilePlanRoleCapability.md) |

View File

@ -1,11 +0,0 @@
# FilePLanRoleCapability
## Properties
| Name | Type |
|-----------|-------------------------------------------------------------------|
| **group** | [**FilePlanRoleCapabilityGroup**](FilePlanRoleCapabilityGroup.md) |
| **index** | number |
| **name** | string |
| **title** | string |

View File

@ -1,9 +0,0 @@
# FilePlanRoleCapabilityGroup
## Properties
| Name | Type |
|-----------|--------|
| **id** | string |
| **title** | string |

View File

@ -1,8 +0,0 @@
# FilePlanRoleEntry
## Properties
| Name | Type |
|-----------|-------------------------------------|
| **entry** | [**FilePlanRole**](FilePlanRole.md) |

View File

@ -1,8 +0,0 @@
# FilePLanRolePaging
## Properties
| Name | Type |
|----------|---------------------------------------------------------|
| **list** | [**FilePlanRolePagingList**](FilePlanRolePagingList.md) |

View File

@ -1,9 +0,0 @@
# FilePLanRolePagingList
## Properties
| Name | Type |
|----------------|-------------------------------------------------------------|
| **entries** | [**FilePlanRoleEntry[]**](FilePlanRoleEntry.md) |
| **pagination** | [**Pagination**](../../content-rest-api/docs/Pagination.md) |

View File

@ -1,8 +0,0 @@
# FilePlanRoleParameters
## Properties
| Name | Type |
|-----------|-------------------------------------------------------------------|
| **where** | [**FilePlanRoleParametersWhere**](FilePlanRoleParametersWhere.md) |

View File

@ -1,8 +0,0 @@
# FilePlanRoleParametersWhere
## Properties
| Name | Type |
|---------------------|----------|
| **capabilityNames** | string[] |

View File

@ -2,13 +2,13 @@
All URIs are relative to *https://localhost/alfresco/api/-default-/public/gs/versions/1* All URIs are relative to *https://localhost/alfresco/api/-default-/public/gs/versions/1*
| Method | HTTP request | Description | Method | HTTP request | Description
|--------------------------------------------------------------------------|----------------------------------------------|---------------------------------------------------| ------------- | ------------- | -------------
| [**createFilePlanCategories**](FilePlansApi.md#createFilePlanCategories) | **POST** /file-plans/{filePlanId}/categories | Create record categories for a file plan | [**createFilePlanCategories**](FilePlansApi.md#createFilePlanCategories) | **POST** /file-plans/{filePlanId}/categories | Create record categories for a file plan
| [**getFilePlan**](FilePlansApi.md#getFilePlan) | **GET** /file-plans/{filePlanId} | Get a file plan | [**getFilePlan**](FilePlansApi.md#getFilePlan) | **GET** /file-plans/{filePlanId} | Get a file plan
| [**getFilePlanCategories**](FilePlansApi.md#getFilePlanCategories) | **GET** /file-plans/{filePlanId}/categories | List file plans's children | [**getFilePlanCategories**](FilePlansApi.md#getFilePlanCategories) | **GET** /file-plans/{filePlanId}/categories | List file plans's children
| [**updateFilePlan**](FilePlansApi.md#updateFilePlan) | **PUT** /file-plans/{filePlanId} | Update a file plan | [**updateFilePlan**](FilePlansApi.md#updateFilePlan) | **PUT** /file-plans/{filePlanId} | Update a file plan
| [**getFilePlanRoles**](FilePlansApi.md#getFilePlanRoles) | **GET** /file-plans/{filePlanId}/roles | Gets a list of roles for the specified file plan. |
<a name="createFilePlanCategories"></a> <a name="createFilePlanCategories"></a>
# **createFilePlanCategories** # **createFilePlanCategories**
@ -402,39 +402,3 @@ parameter are returned in addition to those specified in the **fields** paramete
[**FilePlanEntry**](FilePlanEntry.md) [**FilePlanEntry**](FilePlanEntry.md)
<a name="getFilePlanRoles"></a>
# **getFilePlanRoles**
> FilePlanEntry getFilePlanRoles(filePlanId, parameters)
Gets a list of roles for the specified file plan.
### Example
```javascript
import FilePlansApi from 'FilePlansApi';
import { AlfrescoApi } from '@alfresco/js-api';
this.alfrescoApi = new AlfrescoApi();
this.alfrescoApi.setConfig({
hostEcm: 'http://127.0.0.1:8080'
});
let fileplansApi = new FilePlansApi(this.alfrescoApi);
const filePlanId = 'some id';
fileplansApi.updateFilePlan(filePlanId, {
where: {
capabilityNames: ['ViewRecords']
}
}).then((data) => console.log('API called successfully. Returned data: ' + data))
.catch((error) => console.log(error));
```
### Parameters
| Name | Type | Description |
|----------------|---------------------------------------------------------|-----------------------------------------------------------------------|
| **filePlanId** | **string** | The identifier of a file plan. You can also use the -filePlan- alias. |
| **parameters** | [**FilePlanRoleParameters**](FilePlanRoleParameters.md) | Optional parameters. |
### Return type
[**FilePlanRolePaging**](FilePlanRolePaging.md)

View File

@ -1,33 +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 { FilePlanRoleCapability } from './filePlanRoleCapability';
export class FilePlanRole {
displayLabel: string;
groupShortName: string;
name: string;
roleGroupName: string;
capabilities: FilePlanRoleCapability[];
constructor(input?: Partial<FilePlanRole>) {
if (input) {
Object.assign(this, input);
this.capabilities = input.capabilities?.map((capability) => new FilePlanRoleCapability(capability));
}
}
}

View File

@ -1,32 +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 { FilePlanRoleCapabilityGroup } from './filePlanRoleCapabilityGroup';
export class FilePlanRoleCapability {
group: FilePlanRoleCapabilityGroup;
index: number;
name: string;
title: string;
constructor(input?: Partial<FilePlanRoleCapability>) {
if (input) {
Object.assign(this, input);
this.group = input.group ? new FilePlanRoleCapabilityGroup(input.group) : undefined;
}
}
}

View File

@ -1,27 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class FilePlanRoleCapabilityGroup {
id: string;
title: string;
constructor(input?: Partial<FilePlanRoleCapabilityGroup>) {
if (input) {
Object.assign(this, input);
}
}
}

View File

@ -1,29 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRole } from './filePlanRole';
export class FilePlanRoleEntry {
entry: FilePlanRole;
constructor(input?: Partial<FilePlanRoleEntry>) {
if (input) {
Object.assign(this, input);
this.entry = input.entry ? new FilePlanRole(input.entry) : undefined;
}
}
}

View File

@ -1,29 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRolePagingList } from './filePlanRolePagingList';
export class FilePlanRolePaging {
list: FilePlanRolePagingList;
constructor(input: Partial<FilePlanRolePaging>) {
if (input) {
Object.assign(this, input);
this.list = input.list ? new FilePlanRolePagingList(input.list) : undefined;
}
}
}

View File

@ -1,32 +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 { Pagination } from '../../content-rest-api';
import { FilePlanRoleEntry } from './filePlanRoleEntry';
export class FilePlanRolePagingList {
entries: FilePlanRoleEntry[];
pagination: Pagination;
constructor(input: Partial<FilePlanRolePagingList>) {
if (input) {
Object.assign(this, input);
this.pagination = input.pagination ? new Pagination(input.pagination) : undefined;
this.entries = input?.entries.map((entry) => new FilePlanRoleEntry(entry));
}
}
}

View File

@ -1,22 +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 { FilePlanRoleParametersWhere } from './filePlanRoleParametersWhere';
export interface FilePlanRoleParameters {
where?: FilePlanRoleParametersWhere;
}

View File

@ -1,20 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface FilePlanRoleParametersWhere {
capabilityNames?: string[];
}

View File

@ -19,14 +19,6 @@ export * from './filePlan';
export * from './filePlanBodyUpdate'; export * from './filePlanBodyUpdate';
export * from './filePlanComponentBodyUpdate'; export * from './filePlanComponentBodyUpdate';
export * from './filePlanEntry'; export * from './filePlanEntry';
export * from './filePlanRole';
export * from './filePlanRoleCapability';
export * from './filePlanRoleCapabilityGroup';
export * from './filePlanRoleEntry';
export * from './filePlanRolePaging';
export * from './filePlanRolePagingList';
export * from './filePlanRoleParameters';
export * from './filePlanRoleParametersWhere';
export * from './rMNodeBodyCreate'; export * from './rMNodeBodyCreate';
export * from './rMNodeBodyCreateWithRelativePath'; export * from './rMNodeBodyCreateWithRelativePath';
export * from './rMSite'; export * from './rMSite';

View File

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

View File

@ -1,129 +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 { EcmAuthMock, FilePlansMock } from '../mockObjects';
import { AlfrescoApi, FilePlanRolePaging, FilePlansApi } from '../../src';
describe('FilePlansApi', () => {
let filePlansApiMock: FilePlansMock;
let filePlansApi: FilePlansApi;
beforeEach(async () => {
const hostEcm = 'https://127.0.0.1:8080';
const authResponseMock = new EcmAuthMock(hostEcm);
authResponseMock.get201Response();
filePlansApiMock = new FilePlansMock(hostEcm);
const alfrescoApi = new AlfrescoApi({
hostEcm
});
filePlansApi = new FilePlansApi(alfrescoApi);
await alfrescoApi.login('admin', 'admin');
});
describe('getFilePlanRoles', () => {
let expectedRolePaging: FilePlanRolePaging;
beforeEach(() => {
expectedRolePaging = {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
displayLabel: 'Role One',
groupShortName: 'group short name 1',
name: 'role1',
roleGroupName: 'role group name 1',
capabilities: [
{
index: 0,
name: 'capability1',
title: 'Capability One',
group: {
id: 'group1',
title: 'Group One'
}
},
{
index: 1,
name: 'capability2',
title: 'Capability Two',
group: {
id: 'group2',
title: 'Group Two'
}
}
]
}
},
{
entry: {
displayLabel: 'Role Two',
groupShortName: 'group short name 2',
name: 'role2',
roleGroupName: 'role group name 2',
capabilities: [
{
index: 0,
name: 'capability3',
title: 'Capability Three',
group: {
id: 'group3',
title: 'Group Three'
}
}
]
}
}
]
}
};
});
it('should get file plan roles', (done) => {
const filePlanId = 'filePlanId123';
filePlansApiMock.get200FilePlanRoles(filePlanId);
filePlansApi.getFilePlanRoles(filePlanId).then((rolePaging) => {
expect(rolePaging).toEqual(expectedRolePaging);
done();
});
});
it('should get file plan roles with filtering by capability names', (done) => {
const filePlanId = 'filePlanId123';
filePlansApiMock.get200FilePlanRolesWithFilteringByCapabilityNames(filePlanId);
filePlansApi
.getFilePlanRoles(filePlanId, {
where: {
capabilityNames: ['capability1', 'capability2']
}
})
.then((rolePaging) => {
expect(rolePaging).toEqual(expectedRolePaging);
done();
});
});
});
});

View File

@ -65,7 +65,7 @@ export class TagMock extends BaseMock {
createTags201Response(): void { createTags201Response(): void {
nock(this.host, { encodedQueryParams: true }) nock(this.host, { encodedQueryParams: true })
.post('/alfresco/api/-default-/public/alfresco/versions/1/tags') .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 { get201ResponseForAssigningTagsToNode(body: TagBody[]): void {

View File

@ -1,101 +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 { BaseMock } from '../base.mock';
import nock from 'nock';
import { FilePlanRolePaging } from '@alfresco/js-api';
export class FilePlansMock extends BaseMock {
get200FilePlanRoles(filePlanId: string): void {
nock(this.host, { encodedQueryParams: true })
.get(`/alfresco/api/-default-/public/gs/versions/1/file-plans/${filePlanId}/roles`)
.query({})
.reply(200, this.mockFilePlanRolePaging());
}
get200FilePlanRolesWithFilteringByCapabilityNames(filePlanId: string): void {
nock(this.host, { encodedQueryParams: true })
.get(`/alfresco/api/-default-/public/gs/versions/1/file-plans/${filePlanId}/roles`)
.query({
where: "(capabilityName in ('capability1', 'capability2'))"
})
.reply(200, this.mockFilePlanRolePaging());
}
private mockFilePlanRolePaging(): FilePlanRolePaging {
return {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
displayLabel: 'Role One',
groupShortName: 'group short name 1',
name: 'role1',
roleGroupName: 'role group name 1',
capabilities: [
{
index: 0,
name: 'capability1',
title: 'Capability One',
group: {
id: 'group1',
title: 'Group One'
}
},
{
index: 1,
name: 'capability2',
title: 'Capability Two',
group: {
id: 'group2',
title: 'Group Two'
}
}
]
}
},
{
entry: {
displayLabel: 'Role Two',
groupShortName: 'group short name 2',
name: 'role2',
roleGroupName: 'role group name 2',
capabilities: [
{
index: 0,
name: 'capability3',
title: 'Capability Three',
group: {
id: 'group3',
title: 'Group Three'
}
}
]
}
}
]
}
};
}
}

View File

@ -34,7 +34,6 @@ export * from './content-services/version.mock';
export * from './content-services/webscript.mock'; export * from './content-services/webscript.mock';
export * from './goverance-services/authority-clearance.mock'; export * from './goverance-services/authority-clearance.mock';
export * from './goverance-services/file-plans.mock';
export * from './goverance-services/gs-sites.mock'; export * from './goverance-services/gs-sites.mock';
export * from './goverance-services/node-security-marks.mock'; export * from './goverance-services/node-security-marks.mock';
export * from './goverance-services/security-groups.mock'; export * from './goverance-services/security-groups.mock';

Some files were not shown because too many files have changed in this diff Show More