mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-10117] Deprecate ADF Storybook and custom Docker builds
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
Title: Login Dialog component
|
||||||
|
Added: v2.6.0
|
||||||
|
Status: Active
|
||||||
|
Last reviewed: 2018-10-02
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Login Dialog component](../../../lib/core/src/lib/login/components/login-dialog.component.ts "Defined in login-dialog.component.ts")
|
||||||
|
|
||||||
|
This component is deprecated and will removed because it's unused.
|
||||||
|
|
||||||
|
Allows a user to perform a login via a dialog.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
The [Login Dialog component](login-dialog.component.md) allows you to perform a login via a dialog.
|
||||||
|
|
||||||
|
### Showing the dialog
|
||||||
|
|
||||||
|
Unlike most components, the [Login Dialog Component](login-dialog.component.md) is typically shown in a dialog box
|
||||||
|
rather than the main page and you are responsible for opening the dialog yourself. You can use the
|
||||||
|
[Angular Material Dialog](https://material.angular.io/components/dialog/overview) for this,
|
||||||
|
as shown in the usage example. ADF provides the [`LoginDialogComponentData`](../../../lib/core/src/lib/login/components/login-dialog-component-data.interface.ts) interface
|
||||||
|
to work with the Dialog's
|
||||||
|
[data option](https://material.angular.io/components/dialog/overview#sharing-data-with-the-dialog-component-):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface LoginDialogComponentData {
|
||||||
|
title: string;
|
||||||
|
actionName?: string;
|
||||||
|
logged: Subject<any>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The properties are described in the table below:
|
||||||
|
|
||||||
|
| Name | Type | Default value | Description |
|
||||||
|
| ---- | ---- | ------------- | ----------- |
|
||||||
|
| title | `string` | "" | Dialog title |
|
||||||
|
| actionName | `string` | "" | Text to appear on the dialog's main action button ("Login", "Access", etc) |
|
||||||
|
| logged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | | Event emitted when the login succeeds. |
|
||||||
|
|
||||||
|
If you don't want to manage the dialog yourself then it is easier to use the
|
||||||
|
[Login Dialog Panel component](login-dialog-panel.component.md), or the
|
||||||
|
methods of the [Login Dialog service](../services/login-dialog.service.md), which create
|
||||||
|
the dialog for you.
|
||||||
|
|
||||||
|
### Usage example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { LoginDialogComponentData, LoginDialogComponent} from '@adf/core'
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
...
|
||||||
|
|
||||||
|
constructor(dialog: MatDialog ... ) {}
|
||||||
|
|
||||||
|
openLoginDialog() {
|
||||||
|
data: LoginDialogComponentData = {
|
||||||
|
title: "Perform a Login",
|
||||||
|
actionName: "Access",
|
||||||
|
logged: new Subject<any>()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dialog.open(
|
||||||
|
LoginDialogComponent,
|
||||||
|
{
|
||||||
|
data, panelClass: 'adf-login-dialog',
|
||||||
|
width: '630px'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
data.logged.subscribe(() => {
|
||||||
|
// Action after being logged in...
|
||||||
|
},
|
||||||
|
(error)=>{
|
||||||
|
//your error handling
|
||||||
|
},
|
||||||
|
()=>{
|
||||||
|
//action called when an action or cancel is clicked on the dialog
|
||||||
|
this.dialog.closeAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All the results will be streamed to the logged [subject](http://reactivex.io/rxjs/manual/overview.html#subject) present in the [`LoginDialogComponentData`](../../../lib/core/src/lib/login/components/login-dialog-component-data.interface.ts) object passed to the dialog.
|
||||||
|
When the dialog action is selected by clicking, the `data.logged` stream will be completed.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Login component](login.component.md)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
Title: Form Definition Selector Cloud
|
||||||
|
Added: v3.3.0
|
||||||
|
Status: Active
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Form Definition Selector Cloud](../../../lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.ts "Defined in form-definition-selector-cloud.component.ts")
|
||||||
|
|
||||||
|
Allows one form to be selected from a dropdown list. For forms to be displayed in this component they will need to be compatible with standAlone tasks.
|
||||||
|
|
||||||
|
This component will be removed because it's unused.
|
||||||
|
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-cloud-form-definition-selector
|
||||||
|
[appName]="'simple-app'"
|
||||||
|
(selectForm)="onFormSelect($event)">
|
||||||
|
</adf-cloud-form-definition-selector>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Class members
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Default value | Description |
|
||||||
|
| ---- | ---- | ------------- | ----------- |
|
||||||
|
| appName | `string` | "" | Name of the application. If specified, this shows the users who have access to the app. |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| selectForm | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when a form is selected. |
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
export interface LoginDialogComponentData {
|
||||||
|
title: string;
|
||||||
|
actionName?: string;
|
||||||
|
logged: Subject<any>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<header mat-dialog-title
|
||||||
|
data-automation-id="login-dialog-title">
|
||||||
|
{{ data?.title }}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<mat-dialog-content class="adf-login-dialog-content">
|
||||||
|
<adf-login-dialog-panel #adfLoginPanel (success)="onLoginSuccess($event)" />
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions class="adf-login-dialog-content-actions" align="end">
|
||||||
|
<button mat-button (click)="close()"
|
||||||
|
data-automation-id="login-dialog-actions-cancel">
|
||||||
|
{{ 'LOGIN.DIALOG.CANCEL' | translate }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button mat-button
|
||||||
|
class="choose-action"
|
||||||
|
data-automation-id="login-dialog-actions-perform"
|
||||||
|
[disabled]="!isFormValid()"
|
||||||
|
(click)="submitForm()">
|
||||||
|
{{ buttonActionName | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.adf-login-dialog-content adf-login .adf-login-content .adf-login-card-wide {
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, Inject, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
|
import { LoginDialogPanelComponent } from '../login-dialog-panel/login-dialog-panel.component';
|
||||||
|
import { LoginDialogComponentData } from './login-dialog-component-data.interface';
|
||||||
|
|
||||||
|
/** @deprecated this component will be removed because it's unused */
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-login-dialog',
|
||||||
|
templateUrl: './login-dialog.component.html',
|
||||||
|
styleUrls: ['./login-dialog.component.scss'],
|
||||||
|
imports: [MatDialogModule, LoginDialogPanelComponent, TranslatePipe, MatButtonModule],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class LoginDialogComponent {
|
||||||
|
@ViewChild('adfLoginPanel', { static: true })
|
||||||
|
loginPanel: LoginDialogPanelComponent;
|
||||||
|
|
||||||
|
buttonActionName = '';
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: LoginDialogComponentData) {
|
||||||
|
this.buttonActionName = data.actionName ? `LOGIN.DIALOG.${data.actionName.toUpperCase()}` : 'LOGIN.DIALOG.CHOOSE';
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.data.logged.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm(): void {
|
||||||
|
this.loginPanel.submitForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginSuccess(event: any) {
|
||||||
|
this.data.logged.next(event);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
isFormValid() {
|
||||||
|
return this.loginPanel ? this.loginPanel.isValid() : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,12 +17,19 @@
|
|||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { LoginDialogPanelComponent } from './components/login-dialog-panel/login-dialog-panel.component';
|
import { LoginDialogPanelComponent } from './components/login-dialog-panel/login-dialog-panel.component';
|
||||||
|
import { LoginDialogComponent } from './components/login-dialog/login-dialog.component';
|
||||||
|
|
||||||
import { LoginComponent } from './components/login/login.component';
|
import { LoginComponent } from './components/login/login.component';
|
||||||
import { LoginFooterDirective } from './directives/login-footer.directive';
|
import { LoginFooterDirective } from './directives/login-footer.directive';
|
||||||
import { LoginHeaderDirective } from './directives/login-header.directive';
|
import { LoginHeaderDirective } from './directives/login-header.directive';
|
||||||
|
|
||||||
export const LOGIN_DIRECTIVES = [LoginComponent, LoginFooterDirective, LoginHeaderDirective, LoginDialogPanelComponent] as const;
|
export const LOGIN_DIRECTIVES = [
|
||||||
|
LoginComponent,
|
||||||
|
LoginFooterDirective,
|
||||||
|
LoginHeaderDirective,
|
||||||
|
LoginDialogComponent,
|
||||||
|
LoginDialogPanelComponent
|
||||||
|
] as const;
|
||||||
|
|
||||||
/** @deprecated use `...LOGIN_DIRECTIVES` or import the standalone directives directly */
|
/** @deprecated use `...LOGIN_DIRECTIVES` or import the standalone directives directly */
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="adf-app-listgrid">
|
||||||
|
<div class="adf-app-listgrid-item">
|
||||||
|
<mat-card
|
||||||
|
appearance="outlined"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
class="adf-app-listgrid-item-card"
|
||||||
|
title="{{applicationInstance.name}}"
|
||||||
|
[ngClass]="getTheme()"
|
||||||
|
(click)="onSelectApp(applicationInstance)"
|
||||||
|
(keyup.enter)="onSelectApp(applicationInstance)">
|
||||||
|
<div class="adf-app-listgrid-item-card-logo">
|
||||||
|
<mat-icon class="adf-app-listgrid-item-card-logo-icon">{{ getIcon() }}</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div mat-card-title class="adf-app-listgrid-item-card-title">
|
||||||
|
<h1 class="adf-app-listgrid-item-card-title-text">{{applicationInstance.name}}</h1>
|
||||||
|
</div>
|
||||||
|
<mat-card-subtitle class="adf-app-listgrid-item-card-subtitle">
|
||||||
|
<div class="adf-line-clamp">{{applicationInstance.description}}</div>
|
||||||
|
</mat-card-subtitle>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
/* stylelint-disable scss/no-global-function-names */
|
||||||
|
@mixin adf-line-clamp($line-height: 1.25, $lines: 3) {
|
||||||
|
position: relative;
|
||||||
|
line-height: $line-height;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
/* stylelint-disable */
|
||||||
|
@supports (-webkit-line-clamp: 1) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: $lines;
|
||||||
|
height: calc(0.99em * #{$line-height} * #{$lines});
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (-webkit-line-clamp: 1) {
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
/* stylelint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
$tile-themes: (
|
||||||
|
theme-1: (
|
||||||
|
bg: #269abc,
|
||||||
|
color: #168aac
|
||||||
|
),
|
||||||
|
theme-2: (
|
||||||
|
bg: #7da9b0,
|
||||||
|
color: #6d99a0
|
||||||
|
),
|
||||||
|
theme-3: (
|
||||||
|
bg: #7689ab,
|
||||||
|
color: #66799b
|
||||||
|
),
|
||||||
|
theme-4: (
|
||||||
|
bg: #c74e3e,
|
||||||
|
color: #b73e2e
|
||||||
|
),
|
||||||
|
theme-5: (
|
||||||
|
bg: #fab96c,
|
||||||
|
color: #eaa95c
|
||||||
|
),
|
||||||
|
theme-6: (
|
||||||
|
bg: #759d4c,
|
||||||
|
color: #658d3c
|
||||||
|
),
|
||||||
|
theme-7: (
|
||||||
|
bg: #b1b489,
|
||||||
|
color: #a1a479
|
||||||
|
),
|
||||||
|
theme-8: (
|
||||||
|
bg: #a17299,
|
||||||
|
color: #916289
|
||||||
|
),
|
||||||
|
theme-9: (
|
||||||
|
bg: #696c67,
|
||||||
|
color: #595c57
|
||||||
|
),
|
||||||
|
theme-10: (
|
||||||
|
bg: #cabb33,
|
||||||
|
color: #baab23
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
adf-cloud-app-details {
|
||||||
|
.adf-app-listgrid {
|
||||||
|
padding: 8px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.adf-app-listgrid-item {
|
||||||
|
outline: none;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: unset;
|
||||||
|
max-width: unset;
|
||||||
|
|
||||||
|
&-card {
|
||||||
|
@for $i from 1 through 10 {
|
||||||
|
&.theme-#{$i} {
|
||||||
|
$tile-theme: map-get($tile-themes, theme-#{$i});
|
||||||
|
|
||||||
|
background-color: map-get($tile-theme, bg);
|
||||||
|
|
||||||
|
.adf-app-listgrid-item-card-logo-icon {
|
||||||
|
color: map-get($tile-theme, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
transition:
|
||||||
|
transform 280ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
min-height: 200px;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
max-width: unset;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||||
|
0 3px 14px 2px rgba(0, 0, 0, 0.12),
|
||||||
|
0 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
transform: scale(1.015);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-logo {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
z-index: 9;
|
||||||
|
|
||||||
|
.adf-app-listgrid-item-card-logo-icon {
|
||||||
|
font-size: 70px;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title:has(.adf-app-listgrid-item-card-title-text) {
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
width: 80%;
|
||||||
|
font-size: var(--theme-headline-font-size);
|
||||||
|
margin: 0;
|
||||||
|
line-height: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-subtitle:has(.adf-line-clamp) {
|
||||||
|
color: white;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 16px;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
|
||||||
|
.adf-line-clamp {
|
||||||
|
@include adf-line-clamp(1.25, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-actions {
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
min-height: 48px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
color: #e9f1f3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { fakeApplicationInstance } from '../../mock/app-model.mock';
|
||||||
|
import { AppDetailsCloudComponent } from './app-details-cloud.component';
|
||||||
|
import { DEFAULT_APP_INSTANCE_THEME } from '../../models/application-instance.model';
|
||||||
|
import { NoopTranslateModule } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
describe('AppDetailsCloudComponent', () => {
|
||||||
|
let component: AppDetailsCloudComponent;
|
||||||
|
let fixture: ComponentFixture<AppDetailsCloudComponent>;
|
||||||
|
let host: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopTranslateModule, AppDetailsCloudComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(AppDetailsCloudComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
host = fixture.nativeElement as HTMLElement;
|
||||||
|
component.applicationInstance = fakeApplicationInstance[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAppCard = () => host.querySelector<HTMLElement>('.adf-app-listgrid-item-card');
|
||||||
|
|
||||||
|
it('should display application name', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const appName = host.querySelector<HTMLDivElement>('.adf-app-listgrid-item-card-title');
|
||||||
|
expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit a click event when app selected', () => {
|
||||||
|
spyOn(component.selectedApp, 'emit');
|
||||||
|
fixture.detectChanges();
|
||||||
|
const app = getAppCard();
|
||||||
|
app.click();
|
||||||
|
expect(component.selectedApp.emit).toHaveBeenCalledWith(fakeApplicationInstance[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render card with default icon and theme when are not provided', () => {
|
||||||
|
component.applicationInstance = fakeApplicationInstance[2];
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const card = getAppCard();
|
||||||
|
expect(card.classList.contains(DEFAULT_APP_INSTANCE_THEME));
|
||||||
|
|
||||||
|
const icon = host.querySelector('.adf-app-listgrid-item-card-logo-icon');
|
||||||
|
expect(icon).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render card with a non ApplicationInstanceModel input object', () => {
|
||||||
|
component.applicationInstance = { name: 'application-new-3', createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending' };
|
||||||
|
fixture.detectChanges();
|
||||||
|
const app = getAppCard();
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { ApplicationInstanceModel, DEFAULT_APP_INSTANCE_ICON, DEFAULT_APP_INSTANCE_THEME } from '../../models/application-instance.model';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
|
||||||
|
/** @deprecated this component will be removed because it's unused */
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-cloud-app-details',
|
||||||
|
imports: [CommonModule, MatIconModule, MatCardModule],
|
||||||
|
templateUrl: './app-details-cloud.component.html',
|
||||||
|
styleUrls: ['./app-details-cloud.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class AppDetailsCloudComponent {
|
||||||
|
@Input({ required: true })
|
||||||
|
applicationInstance: ApplicationInstanceModel;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
selectedApp = new EventEmitter<ApplicationInstanceModel>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass the selected app as next
|
||||||
|
*
|
||||||
|
* @param app application model
|
||||||
|
*/
|
||||||
|
onSelectApp(app: ApplicationInstanceModel): void {
|
||||||
|
this.selectedApp.emit(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get application instance theme
|
||||||
|
*
|
||||||
|
* @returns the name of the theme
|
||||||
|
*/
|
||||||
|
getTheme(): string {
|
||||||
|
return this.applicationInstance.theme || DEFAULT_APP_INSTANCE_THEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get application instance icon
|
||||||
|
*
|
||||||
|
* @returns the name of the icon
|
||||||
|
*/
|
||||||
|
getIcon(): string {
|
||||||
|
return this.applicationInstance.icon || DEFAULT_APP_INSTANCE_ICON;
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="menu-container" *ngIf="apps$ | async as appsList; else loadingOrError">
|
||||||
|
<ng-container *ngIf="appsList.length > 0; else noApps">
|
||||||
|
<div
|
||||||
|
*ngIf="isGrid(); else appList"
|
||||||
|
class="adf-app-apps-grid">
|
||||||
|
<adf-cloud-app-details
|
||||||
|
*ngFor="let app of appsList"
|
||||||
|
[applicationInstance]="app"
|
||||||
|
(selectedApp)="onSelectApp($event)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #appList>
|
||||||
|
<mat-list class="adf-app-list">
|
||||||
|
<mat-list-item class="adf-app-list-item" (click)="onSelectApp(app)" (keyup.enter)="onSelectApp(app)"
|
||||||
|
*ngFor="let app of appsList" tabindex="0" role="button" title="{{app.name}}">
|
||||||
|
<mat-icon matListItemIcon>touch_app</mat-icon>
|
||||||
|
<span class="adf-app-list-item-text" matLine>{{app.name}}</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<ng-template #noApps>
|
||||||
|
<div class="adf-app-list-empty">
|
||||||
|
<ng-content select="adf-custom-empty-content-template" *ngIf="hasEmptyCustomContentTemplate; else defaultEmptyTemplate"
|
||||||
|
class="adf-custom-empty-template" />
|
||||||
|
|
||||||
|
<ng-template #defaultEmptyTemplate>
|
||||||
|
<adf-empty-content icon="apps" [title]="'ADF_CLOUD_TASK_LIST.APPS.NO_APPS.TITLE' | translate"
|
||||||
|
[subtitle]="'ADF_CLOUD_TASK_LIST.APPS.NO_APPS.SUBTITLE' | translate" />
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #loadingOrError>
|
||||||
|
<div *ngIf="loadingError$ | async; else loading" class="adf-app-list-error">
|
||||||
|
<adf-empty-content icon="error_outline" [title]="'ADF_CLOUD_TASK_LIST.APPS.ERROR.TITLE' | translate"
|
||||||
|
[subtitle]="'ADF_CLOUD_TASK_LIST.APPS.ERROR.SUBTITLE' | translate" />
|
||||||
|
</div>
|
||||||
|
<ng-template #loading>
|
||||||
|
<ng-container>
|
||||||
|
<div class="adf-app-list-spinner">
|
||||||
|
<mat-spinner class="adf-app-list-cloud--spinner" />
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</ng-template>
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
@use '../../../flex' as flex;
|
||||||
|
|
||||||
|
adf-cloud-app-list {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.adf-app-list-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-app-list-spinner,
|
||||||
|
.adf-app-list-empty,
|
||||||
|
.adf-app-list-error {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 85vh;
|
||||||
|
|
||||||
|
.adf-app-list-cloud-spinner {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-app-apps-grid {
|
||||||
|
flex-flow: row wrap;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
adf-cloud-app-details {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
max-width: 33.3333%;
|
||||||
|
|
||||||
|
@include flex.layout-bp(lt-md) {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include flex.layout-bp(lt-sm) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { AlfrescoApiService } from '@alfresco/adf-content-services';
|
||||||
|
import { of, throwError } from 'rxjs';
|
||||||
|
import { fakeApplicationInstance } from '../../mock/app-model.mock';
|
||||||
|
import { AppListCloudComponent, LAYOUT_GRID, LAYOUT_LIST } from './app-list-cloud.component';
|
||||||
|
import { AppsProcessCloudService } from '../../services/apps-process-cloud.service';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { CustomEmptyContentTemplateDirective, NoopTranslateModule } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
describe('AppListCloudComponent', () => {
|
||||||
|
let component: AppListCloudComponent;
|
||||||
|
let fixture: ComponentFixture<AppListCloudComponent>;
|
||||||
|
let appsProcessCloudService: AppsProcessCloudService;
|
||||||
|
let getAppsSpy: jasmine.Spy;
|
||||||
|
let alfrescoApiService: AlfrescoApiService;
|
||||||
|
|
||||||
|
const mock: any = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: () => Promise.resolve(fakeApplicationInstance)
|
||||||
|
},
|
||||||
|
isLoggedIn: () => false,
|
||||||
|
reply: jasmine.createSpy('reply')
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
imports: [MatIconModule, CustomEmptyContentTemplateDirective, AppListCloudComponent],
|
||||||
|
template: `
|
||||||
|
<adf-cloud-app-list>
|
||||||
|
<adf-custom-empty-content-template>
|
||||||
|
<mat-icon>apps</mat-icon>
|
||||||
|
<p id="custom-id">No Apps Found</p>
|
||||||
|
</adf-custom-empty-content-template>
|
||||||
|
</adf-cloud-app-list>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class CustomEmptyAppListCloudTemplateComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopTranslateModule, AppListCloudComponent, CustomEmptyAppListCloudTemplateComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(AppListCloudComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||||
|
appsProcessCloudService = TestBed.inject(AppsProcessCloudService);
|
||||||
|
|
||||||
|
spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock);
|
||||||
|
getAppsSpy = spyOn(appsProcessCloudService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should define layoutType with the default value', () => {
|
||||||
|
component.layoutType = '';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.isGrid()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should fetch deployed apps', (done) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
component.apps$.subscribe((response: any[]) => {
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.length).toEqual(3);
|
||||||
|
expect(response[0].name).toEqual('application-new-1');
|
||||||
|
expect(response[0].status).toEqual('Deployed');
|
||||||
|
expect(response[0].icon).toEqual('favorite_border');
|
||||||
|
expect(response[0].theme).toEqual('theme-2');
|
||||||
|
expect(response[1].name).toEqual('application-new-2');
|
||||||
|
expect(response[1].status).toEqual('Pending');
|
||||||
|
expect(response[1].icon).toEqual('favorite_border');
|
||||||
|
expect(response[1].theme).toEqual('theme-2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display default adf-empty-content template when response empty', () => {
|
||||||
|
getAppsSpy.and.returnValue(of([]));
|
||||||
|
fixture.detectChanges();
|
||||||
|
const defaultEmptyTemplate = fixture.nativeElement.querySelector('.adf-app-list-empty');
|
||||||
|
const emptyContent = fixture.debugElement.nativeElement.querySelector('.adf-empty-content');
|
||||||
|
const emptyTitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title');
|
||||||
|
const emptySubtitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle');
|
||||||
|
expect(defaultEmptyTemplate).toBeDefined();
|
||||||
|
expect(defaultEmptyTemplate).not.toBeNull();
|
||||||
|
expect(emptyContent).not.toBeNull();
|
||||||
|
expect(emptyTitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.NO_APPS.TITLE');
|
||||||
|
expect(emptySubtitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.NO_APPS.SUBTITLE');
|
||||||
|
expect(getAppsSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display default no permissions template when response returns exception', (done) => {
|
||||||
|
getAppsSpy.and.returnValue(throwError({}));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
component.loadingError$.next(true);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const errorTemplate = fixture.nativeElement.querySelector('.adf-app-list-error');
|
||||||
|
const errorTitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title');
|
||||||
|
const errorSubtitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle');
|
||||||
|
expect(errorTemplate).not.toBeNull();
|
||||||
|
expect(errorTitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.ERROR.TITLE');
|
||||||
|
expect(errorSubtitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.ERROR.SUBTITLE');
|
||||||
|
expect(getAppsSpy).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Grid Layout ', () => {
|
||||||
|
it('should display a grid by default', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.isGrid()).toBe(true);
|
||||||
|
expect(component.isList()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should defined adf-cloud-app-details when layout type is grid', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const adfCloudDetailsElement = fixture.nativeElement.querySelectorAll('adf-cloud-app-details');
|
||||||
|
const appName = fixture.nativeElement.querySelector('.adf-app-listgrid-item-card-title');
|
||||||
|
expect(adfCloudDetailsElement).toBeDefined();
|
||||||
|
expect(adfCloudDetailsElement).not.toBeNull();
|
||||||
|
|
||||||
|
expect(adfCloudDetailsElement.length).toEqual(3);
|
||||||
|
expect(component.isGrid()).toBe(true);
|
||||||
|
expect(component.isList()).toBe(false);
|
||||||
|
|
||||||
|
expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a grid when configured to', () => {
|
||||||
|
component.layoutType = LAYOUT_GRID;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.isGrid()).toBe(true);
|
||||||
|
expect(component.isList()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception on init if unknown type configured', () => {
|
||||||
|
component.layoutType = 'unknown';
|
||||||
|
expect(component.ngOnInit).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('List Layout ', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.layoutType = LAYOUT_LIST;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a LIST when configured to', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.isGrid()).toBe(false);
|
||||||
|
expect(component.isList()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display list when layout type is LIST', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const appListElement = fixture.nativeElement.querySelectorAll('.adf-app-list');
|
||||||
|
const appListItemElement = fixture.nativeElement.querySelectorAll('.adf-app-list-item');
|
||||||
|
const appName = fixture.nativeElement.querySelector('.adf-app-list-item-text');
|
||||||
|
expect(appListElement).toBeDefined();
|
||||||
|
expect(appListElement).not.toBeNull();
|
||||||
|
|
||||||
|
expect(appListItemElement.length).toEqual(3);
|
||||||
|
expect(component.isGrid()).toBe(false);
|
||||||
|
expect(component.isList()).toBe(true);
|
||||||
|
|
||||||
|
expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception on init if unknown type configured', () => {
|
||||||
|
component.layoutType = 'unknown';
|
||||||
|
expect(component.ngOnInit).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit a click event when app selected', () => {
|
||||||
|
spyOn(component.appClick, 'emit');
|
||||||
|
fixture.detectChanges();
|
||||||
|
const onAppClick = fixture.nativeElement.querySelector('.adf-app-listgrid-item-card');
|
||||||
|
onAppClick.click();
|
||||||
|
expect(component.appClick.emit).toHaveBeenCalledWith(fakeApplicationInstance[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Custom CustomEmptyAppListCloudTemplateComponent', () => {
|
||||||
|
let customFixture: ComponentFixture<CustomEmptyAppListCloudTemplateComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getAppsSpy.and.returnValue(of([]));
|
||||||
|
customFixture = TestBed.createComponent(CustomEmptyAppListCloudTemplateComponent);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
customFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the custom empty template', async () => {
|
||||||
|
customFixture.detectChanges();
|
||||||
|
await customFixture.whenStable();
|
||||||
|
|
||||||
|
const title = customFixture.nativeElement.querySelector('#custom-id');
|
||||||
|
expect(title.innerText).toBe('No Apps Found');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { CustomEmptyContentTemplateDirective, EmptyContentComponent } from '@alfresco/adf-core';
|
||||||
|
import { AfterContentInit, Component, ContentChild, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { Observable, of, Subject } from 'rxjs';
|
||||||
|
import { AppsProcessCloudService } from '../../services/apps-process-cloud.service';
|
||||||
|
import { ApplicationInstanceModel } from '../../models/application-instance.model';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
|
import { AppDetailsCloudComponent } from '../app-details-cloud/app-details-cloud.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatLineModule } from '@angular/material/core';
|
||||||
|
import { MatListModule } from '@angular/material/list';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
|
||||||
|
export const LAYOUT_LIST: string = 'LIST';
|
||||||
|
export const LAYOUT_GRID: string = 'GRID';
|
||||||
|
export const DEPLOYED_STATUS: string = 'DEPLOYED';
|
||||||
|
|
||||||
|
/** @deprecated this component will be removed because it's unused */
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-cloud-app-list',
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslatePipe,
|
||||||
|
AppDetailsCloudComponent,
|
||||||
|
MatIconModule,
|
||||||
|
MatLineModule,
|
||||||
|
MatListModule,
|
||||||
|
EmptyContentComponent,
|
||||||
|
MatProgressSpinnerModule
|
||||||
|
],
|
||||||
|
templateUrl: './app-list-cloud.component.html',
|
||||||
|
styleUrls: ['./app-list-cloud.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class AppListCloudComponent implements OnInit, AfterContentInit {
|
||||||
|
@ContentChild(CustomEmptyContentTemplateDirective)
|
||||||
|
emptyCustomContent: CustomEmptyContentTemplateDirective;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the layout of the apps. There are two possible
|
||||||
|
* values, "GRID" and "LIST".
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
layoutType: string = LAYOUT_GRID;
|
||||||
|
|
||||||
|
/** Emitted when an app entry is clicked. */
|
||||||
|
@Output()
|
||||||
|
appClick = new EventEmitter<ApplicationInstanceModel>();
|
||||||
|
|
||||||
|
apps$: Observable<any>;
|
||||||
|
loadingError$ = new Subject<boolean>();
|
||||||
|
hasEmptyCustomContentTemplate: boolean = false;
|
||||||
|
|
||||||
|
constructor(private appsProcessCloudService: AppsProcessCloudService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (!this.isValidType()) {
|
||||||
|
this.setDefaultLayoutType();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apps$ = this.appsProcessCloudService.getDeployedApplicationsByStatus(DEPLOYED_STATUS).pipe(
|
||||||
|
catchError(() => {
|
||||||
|
this.loadingError$.next(true);
|
||||||
|
return of();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterContentInit() {
|
||||||
|
if (this.emptyCustomContent) {
|
||||||
|
this.hasEmptyCustomContentTemplate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectApp(app: ApplicationInstanceModel): void {
|
||||||
|
this.appClick.emit(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the value of the layoutType property is an allowed value
|
||||||
|
*
|
||||||
|
* @returns `true` if layout type is valid, otherwise `false`
|
||||||
|
*/
|
||||||
|
isValidType(): boolean {
|
||||||
|
if (this.layoutType && (this.layoutType === LAYOUT_LIST || this.layoutType === LAYOUT_GRID)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign the default value to LayoutType
|
||||||
|
*/
|
||||||
|
setDefaultLayoutType(): void {
|
||||||
|
this.layoutType = LAYOUT_GRID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the layout type is LIST
|
||||||
|
*
|
||||||
|
* @returns `true` if the layout is list, otherwise `false`
|
||||||
|
*/
|
||||||
|
isList(): boolean {
|
||||||
|
return this.layoutType === LAYOUT_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the layout type is GRID
|
||||||
|
*
|
||||||
|
* @returns `true` if layout is grid, otherwise `false`
|
||||||
|
*/
|
||||||
|
isGrid(): boolean {
|
||||||
|
return this.layoutType === LAYOUT_GRID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export const DEFAULT_APP_INSTANCE_THEME = 'theme-2';
|
||||||
|
export const DEFAULT_APP_INSTANCE_ICON = 'favorite_border';
|
||||||
|
|
||||||
export interface ApplicationInstanceModel {
|
export interface ApplicationInstanceModel {
|
||||||
name?: string;
|
name?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<mat-form-field class="adf-form-definition-selector">
|
||||||
|
<mat-label>{{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.FORM' | translate}}</mat-label>
|
||||||
|
<mat-select class="adf-form-selector-dropdown" (selectionChange)="onSelect($event)">
|
||||||
|
<mat-option [value]="''">{{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.NONE' | translate}}</mat-option>
|
||||||
|
<mat-option *ngFor="let form of forms$ | async" [value]="form.id">{{ form.name }}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
.adf {
|
||||||
|
&-form-definition-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { FormDefinitionSelectorCloudComponent } from './form-definition-selector-cloud.component';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { FormDefinitionSelectorCloudService } from '../services/form-definition-selector-cloud.service';
|
||||||
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
|
import { MatSelectHarness } from '@angular/material/select/testing';
|
||||||
|
import { NoopTranslateModule } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
describe('FormDefinitionCloudComponent', () => {
|
||||||
|
let fixture: ComponentFixture<FormDefinitionSelectorCloudComponent>;
|
||||||
|
let service: FormDefinitionSelectorCloudService;
|
||||||
|
let getFormsSpy: jasmine.Spy;
|
||||||
|
let loader: HarnessLoader;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopTranslateModule, FormDefinitionSelectorCloudComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(FormDefinitionSelectorCloudComponent);
|
||||||
|
service = TestBed.inject(FormDefinitionSelectorCloudService);
|
||||||
|
getFormsSpy = spyOn(service, 'getStandAloneTaskForms').and.returnValue(of([{ id: 'fake-form', name: 'fakeForm' } as any]));
|
||||||
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load the forms by default', async () => {
|
||||||
|
const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' }));
|
||||||
|
await selectElement.open();
|
||||||
|
const options = await selectElement.getOptions();
|
||||||
|
|
||||||
|
expect(options.length).toBe(2);
|
||||||
|
expect(await options[0].getText()).toBe('ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.NONE');
|
||||||
|
expect(await options[1].getText()).toBe('fakeForm');
|
||||||
|
expect(getFormsSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load only None option when no forms exist', async () => {
|
||||||
|
getFormsSpy.and.returnValue(of([]));
|
||||||
|
const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' }));
|
||||||
|
await selectElement.open();
|
||||||
|
|
||||||
|
const options = await selectElement.getOptions();
|
||||||
|
|
||||||
|
expect(options.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not preselect any form by default', async () => {
|
||||||
|
const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' }));
|
||||||
|
|
||||||
|
expect(await selectElement.getValueText()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the name of the form that is selected', async () => {
|
||||||
|
const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' }));
|
||||||
|
await selectElement.open();
|
||||||
|
const options = await selectElement.getOptions();
|
||||||
|
|
||||||
|
await options[1].click();
|
||||||
|
|
||||||
|
expect(await selectElement.getValueText()).toBe('fakeForm');
|
||||||
|
});
|
||||||
|
});
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { FormDefinitionSelectorCloudService } from '../services/form-definition-selector-cloud.service';
|
||||||
|
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
|
||||||
|
import { FormRepresentation } from '../../services/form-fields.interfaces';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
/** @deprecated this component will be removed because it's unused */
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-cloud-form-definition-selector',
|
||||||
|
imports: [CommonModule, TranslatePipe, MatSelectModule],
|
||||||
|
templateUrl: './form-definition-selector-cloud.component.html',
|
||||||
|
styleUrls: ['./form-definition-selector-cloud.component.scss']
|
||||||
|
})
|
||||||
|
export class FormDefinitionSelectorCloudComponent implements OnInit {
|
||||||
|
/** Name of the application. If specified, this shows the users who have access to the app. */
|
||||||
|
@Input()
|
||||||
|
appName: string = '';
|
||||||
|
|
||||||
|
/** Emitted when a form is selected. */
|
||||||
|
@Output()
|
||||||
|
selectForm: EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
|
||||||
|
forms$: Observable<FormRepresentation[]>;
|
||||||
|
|
||||||
|
constructor(private formDefinitionCloudService: FormDefinitionSelectorCloudService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.forms$ = this.formDefinitionCloudService.getStandAloneTaskForms(this.appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(event: MatSelectChange) {
|
||||||
|
this.selectForm.emit(event.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*!
|
||||||
|
* @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 const mockFormRepresentations = [
|
||||||
|
{
|
||||||
|
formRepresentation: {
|
||||||
|
id: 'form-de8895be-d0d7-4434-beef-559b15305d72',
|
||||||
|
name: 'Form 1',
|
||||||
|
description: '',
|
||||||
|
version: 0,
|
||||||
|
standalone: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formRepresentation: {
|
||||||
|
id: 'form-de8895be-d0d7-4434-beef-fgr34ttgrtgd',
|
||||||
|
name: 'Form 2',
|
||||||
|
description: '',
|
||||||
|
version: 0,
|
||||||
|
standalone: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formRepresentation: {
|
||||||
|
id: 'form-de8895be-d0d7-4434-beef-53453453452',
|
||||||
|
name: 'Form 3',
|
||||||
|
description: '',
|
||||||
|
version: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { TestBed } from '@angular/core/testing';
|
||||||
|
import { FormDefinitionSelectorCloudService } from './form-definition-selector-cloud.service';
|
||||||
|
import { mockFormRepresentations } from '../mocks/form-representation.mock';
|
||||||
|
import { AdfHttpClient } from '@alfresco/adf-core/api';
|
||||||
|
import { NoopTranslateModule } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
describe('Form Definition Selector Cloud Service', () => {
|
||||||
|
let service: FormDefinitionSelectorCloudService;
|
||||||
|
let adfHttpClient: AdfHttpClient;
|
||||||
|
const appName = 'app-name';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopTranslateModule]
|
||||||
|
});
|
||||||
|
service = TestBed.inject(FormDefinitionSelectorCloudService);
|
||||||
|
adfHttpClient = TestBed.inject(AdfHttpClient);
|
||||||
|
spyOn(adfHttpClient, 'request').and.returnValue(Promise.resolve(mockFormRepresentations));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch all the forms when getForms is called', (done) => {
|
||||||
|
service.getForms(appName).subscribe((result) => {
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.length).toBe(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch only standalone enabled forms when getStandaloneTaskForms is called', (done) => {
|
||||||
|
service.getStandAloneTaskForms(appName).subscribe((result) => {
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.length).toBe(2);
|
||||||
|
expect(result[0].name).toBe('Form 1');
|
||||||
|
expect(result[1].name).toBe('Form 3');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { from, Observable } from 'rxjs';
|
||||||
|
import { BaseCloudService } from '../../services/base-cloud.service';
|
||||||
|
import { FormRepresentation } from '../../services/form-fields.interfaces';
|
||||||
|
import { FormDefinitionSelectorCloudServiceInterface } from './form-definition-selector-cloud.service.interface';
|
||||||
|
|
||||||
|
/** @deprecated this service will be removed because it's component is unused */
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class FormDefinitionSelectorCloudService extends BaseCloudService implements FormDefinitionSelectorCloudServiceInterface {
|
||||||
|
/**
|
||||||
|
* Get all forms of an app.
|
||||||
|
*
|
||||||
|
* @param appName Name of the application
|
||||||
|
* @returns Details of the forms
|
||||||
|
*/
|
||||||
|
getForms(appName: string): Observable<FormRepresentation[]> {
|
||||||
|
const url = `${this.getBasePath(appName)}/form/v1/forms`;
|
||||||
|
|
||||||
|
return this.get(url).pipe(map((data: any) => data.map((formData: any) => formData.formRepresentation)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all forms of an app.
|
||||||
|
*
|
||||||
|
* @param appName Name of the application
|
||||||
|
* @returns Details of the forms
|
||||||
|
*/
|
||||||
|
getStandAloneTaskForms(appName: string): Observable<FormRepresentation[]> {
|
||||||
|
return from(this.getForms(appName)).pipe(
|
||||||
|
map((data: any) => data.filter((formData: any) => formData.standalone || formData.standalone === undefined))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||||
import { provideTranslations } from '@alfresco/adf-core';
|
import { provideTranslations } from '@alfresco/adf-core';
|
||||||
|
import { APP_LIST_CLOUD_DIRECTIVES } from './app/app-list-cloud.module';
|
||||||
import { TaskCloudModule } from './task/task-cloud.module';
|
import { TaskCloudModule } from './task/task-cloud.module';
|
||||||
import { ProcessCloudModule } from './process/process-cloud.module';
|
import { ProcessCloudModule } from './process/process-cloud.module';
|
||||||
import { FORM_CLOUD_DIRECTIVES } from './form/form-cloud.module';
|
import { FORM_CLOUD_DIRECTIVES } from './form/form-cloud.module';
|
||||||
@@ -27,7 +28,12 @@ import { PeopleCloudComponent } from './people/components/people-cloud.component
|
|||||||
import { provideCloudFormRenderer, provideCloudPreferences } from './providers';
|
import { provideCloudFormRenderer, provideCloudPreferences } from './providers';
|
||||||
import { TaskListCloudService } from './task/task-list/services/task-list-cloud.service';
|
import { TaskListCloudService } from './task/task-list/services/task-list-cloud.service';
|
||||||
|
|
||||||
export const PROCESS_SERVICES_CLOUD_DIRECTIVES = [...FORM_CLOUD_DIRECTIVES, ...TASK_FORM_CLOUD_DIRECTIVES, PeopleCloudComponent] as const;
|
export const PROCESS_SERVICES_CLOUD_DIRECTIVES = [
|
||||||
|
...APP_LIST_CLOUD_DIRECTIVES,
|
||||||
|
...FORM_CLOUD_DIRECTIVES,
|
||||||
|
...TASK_FORM_CLOUD_DIRECTIVES,
|
||||||
|
PeopleCloudComponent
|
||||||
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated this module is deprecated and will be removed in the future versions
|
* @deprecated this module is deprecated and will be removed in the future versions
|
||||||
|
|||||||
Reference in New Issue
Block a user