Compare commits

...
41 Commits
Author SHA1 Message Date
Denys Vuika 502837ca5c [ADF-3292] fix reset to defaults (#3566)
* fix reset to defaults

* code fixes

* restore providers

* fix category filtering
2018-07-06 10:03:32 +01:00
Denys Vuika 19bb9dac8b fix title translation (#3565) 2018-07-06 10:01:43 +01:00
Popovics András 602e5330c0 [ADF_NOISSUE] Header image fix (#3563)
* Header image fix

* fix css
2018-07-05 23:23:46 +01:00
Vito 5be8f40ca0 [ADF-3255] fixed remember me behaviour (#3564) 2018-07-05 23:22:16 +01:00
Alex Bolboșenco 9cae48e984 CardViewItem in demo-shell (#3562) 2018-07-05 14:46:38 +01:00
davidcanonieto 487392085f [ADF-2765] Fix for the gallery view in datatable component (#3470)
* [ADF-2765] Fix for the gallery view in datatable component

* [ADF-2765] Fixed lint errors

* [ADF-2765] Changed maximum columns per row number

* [ADF-2765] Removed trailing whitespace
2018-07-04 23:36:21 +01:00
Vito 3603cb1e67 [ADF-3263] added extra options to upload file for upload service (#3546) 2018-07-03 20:47:27 +01:00
davidcanonieto 73937d9c67 [ADF-2599] Share button disabled after clicking it fixed (#3525)
* [ADF-2599] Share button disabled after clicking it fixed

* [ADF-2599] Share button disabling attribute changed

* [ADF-2599] Trailing whitespace removed
2018-07-03 20:05:10 +01:00
Alex Bolboșenco c83f2a57de [ADF-3298] Select CardView item label removed (#3555)
* placeholder removed

* test added
2018-07-03 20:04:11 +01:00
Georgiana Roman a0f94aa5dc [ADF-3305] change logo image directory (#3556) 2018-07-03 20:03:20 +01:00
Georgiana Roman bb3d351e63 [ADF-3177] added general header component (#3537)
* [ADF-3177] added general header component

* [ADF-3177] added documentation and removed user info mobile behaviour

* [ADF-3177] eddited doc file

* [ADF-3177] renamed folder and style changes

* [ADF-3177] style changes

* [ADF-3177] fix tests

* [ADF-3177] fix lint errors and tests

* [APM-3177] css changes

* [ADF-3177] fix pr

* [ADF-3177] updated documentation

* [ADF-3177] expose module with old name

* [ADF-3177] expose module with old name
2018-07-03 12:35:38 +01:00
Vito 9d468bce56 [ADF-3301] - added a check when handler is null (#3554) 2018-07-03 12:31:29 +01:00
Vito e865782333 [ADF-2671] fixed retrieving of error message (#3553) 2018-07-03 12:30:35 +01:00
Vito 15409d8fef [ADF-3283] added minimatch options into the configuration to allow cu… (#3542)
* [ADF-3283] added minimatch options into the configuration to allow customisation

* [ADF-3283] added documentation
2018-07-02 19:21:34 +01:00
Vito e1ad75f829 [ADF-3294] fixed drag and drop folder to onto a folder (#3550) 2018-07-02 19:15:52 +01:00
Eugenio Romano 9c8b058858 Update ADF packages version 2.5.0-beta1 (#3547) 2018-07-02 19:14:10 +01:00
Vito c542cbca6d [ADF-3281] - fixed error when user try to involve people (#3538)
* [ADF-3281] moved current user retrieving in the constructor

* [ADF-3281] moved current user retrieving in the constructor #2
2018-06-29 10:21:27 +01:00
davidcanonieto c8fccccb81 [ADF-3149] New Report Issue page created (#3452)
* [ADF-3149] New Report Issue page created

* [ADF-3149] Added missing comma
2018-06-29 10:19:23 +01:00
Andy Stark d7d838624e [ADF-3279] Initial refactoring of doc tools (#3541)
* [ADF-3279] Added timing option

* [ADF-3279] Updated to keep md files in memory during processing

* [ADF-3279] Changed read phase to use stored Markdown trees

* [ADF-3279] Renamed tool functions and removed obsolete ones

* [ADF-3279] Added progress bar and better error message handling
2018-06-29 10:14:58 +01:00
Vito e5ea283332 [ADF-2348] added actions on start and cancel start process buttons (#3543) 2018-06-29 10:14:30 +01:00
bbcodrin eeb3b9abe3 header separator fix (#3535) 2018-06-28 10:40:16 +01:00
bbcodrin 68735cb88a [ADF-2494] Task Standalone - Provide a way to attach a form (#3459)
* button added for task standalone

* attach form component added

* slide-toggle position fixed

* tests added

* rebase fixed

* review fix

* test added & fixes

* tests fixes

* refresh task details

* tests fixed

* attachFormToATask test added

* formPreview test fixed

* adf-form test fixed
2018-06-28 10:39:15 +01:00
Vito a2afb730fa [ADF-2671] added permission check on permissions (#3521)
* [ADF-2671] start adding permission check for changing permissions

* [ADF-2671] added permission check for inherit button and permission dialog

* [ADF-2671] added permission check for inherit button and permission dialog

* [ADF-2671] start fixing and adding test for new permission check

* ]
[ADF-2671] improved check for node-permission directive

* [ADF-2671] fixed and added more test for permission on permissions

* [ADF-2671] reverting change on node-permission directive

* [ADF-2671] fixing test for permission check
2018-06-27 15:15:57 +01:00
Alex Bolboșenco cb8bd6f814 [ADF-3275] Select CardView item (#3530)
* Select card view item

* package json fix

* package json fix

* console.log removed

* Usage of generic
2018-06-27 14:14:13 +01:00
davidcanonieto 8300e9461b [ADF-2598] Sidenav button only visible when sidenav is enebled (#3526) 2018-06-27 10:08:14 +01:00
Vito 1bf346c5de [ADF-2876] retrieving user from APS api and not from js-api (#3426)
* [ADF-2876] retrieving user from APS api and not from js-api

* [ADF-2876] added support by checking externalId instead of email

* [ADF-2876] fixed auth service after rebase
2018-06-27 10:01:17 +01:00
Maurizio Vitale ffef7688d7 Expose uriLogout in the host setting form page (#3517) 2018-06-27 09:47:10 +01:00
Vito f6a300850c [ADF-3268] added datetime widget for dynamic table (#3529) 2018-06-27 09:45:14 +01:00
Vito aad162399b [ADF-3266] fixed style for amount widget (#3531) 2018-06-27 09:43:30 +01:00
bbcodrin 546928fe86 [ADF-3238] content size min error added (#3532)
* content size min error added

* clean code
2018-06-27 09:38:57 +01:00
Vito 8a8dfffa90 [ADF-3267] changed true/false values showed with a readonly checkbox (#3524) 2018-06-26 10:49:09 +01:00
Vito 570a75478d [ADF-2769] Added example page for content node selector (#3498)
* content node selector test page

* [ADF-2769] start adding more examples to node selector page

* [ADF-2769] added more example for node selector page

* [ADF-2769] added examples on node selector page
2018-06-25 17:11:19 +01:00
bbcodrin 4697a8fc58 [ADF-2962] Login Hide/Show icon is not reachable (#3489)
* hide/show focused & triggered

* keyup.enter method used
2018-06-25 17:07:00 +01:00
Alex Bolboșenco a680b47cf5 [ADF-3193] CardView Variables Item (#3485)
* variables item

* unit tests

* translations fix

* docs added

* change requests

* change requests
2018-06-25 17:04:46 +01:00
davidcanonieto 6610a326a7 [ADF-3199] Datatable layout fix (#3481) 2018-06-25 16:56:56 +01:00
Eugenio Romano de216d86f0 fix test 2018-06-25 14:12:33 +01:00
Eugenio Romano 5710749e9d update lock 2018-06-25 11:49:27 +01:00
Eugenio Romano 9275f22553 bump 2.4.0 version 2018-06-24 14:02:59 +01:00
Eugenio Romano 8f7383d9d0 Merge branch 'master' into development
* master:
  [ADF-2825] - Form visibility fix on Master (#3237)
2018-06-22 21:56:01 +01:00
Maurizio Vitale c3db1c2ff8 [ADF-2825] - Form visibility fix on Master (#3237)
* Fix form visibility related to the process variables (#3232)

* bump 2.3.1 version
2018-04-25 18:28:06 +01:00
Eugenio Romano afa298060a Merge pull request #3206 from Alfresco/development
2.3.0
2018-04-17 17:43:50 +01:00
147 changed files with 8768 additions and 6171 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Alfresco-ADF-Angular-Demo",
"description": "Demo shell for Alfresco Angular components",
"version": "2.4.0-beta14",
"version": "2.5.0-beta1",
"author": "Alfresco Software, Ltd.",
"repository": {
"type": "git",
+2
View File
@@ -52,8 +52,10 @@
"APP_LAYOUT": {
"APP_NAME": "ADF Demo Application",
"HOME": "Home",
"NODE-SELECTOR": "Node Selector",
"CONTENT_SERVICES": "Content Services",
"BREADCRUMB": "Breadcrumb",
"CARD_VIEW": "CardView",
"PROCESS_SERVICES": "Process Services",
"LOGIN": "Login",
"CUSTOM_SOURCES": "Custom Sources",
+4 -1
View File
@@ -198,7 +198,10 @@
"supportedPageSizes": [ 5, 10, 15, 20 ]
},
"files": {
"excluded": [".DS_Store", "desktop.ini", ".git"]
"excluded": [".DS_Store", "desktop.ini", ".git"],
"match-options": {
"nocase": true
}
},
"logLevel": "trace",
"activiti": {
+11 -2
View File
@@ -52,7 +52,10 @@ import { SharedLinkViewComponent } from './components/shared-link-view/shared-li
import { DemoPermissionComponent } from './components/permissions/demo-permissions.component';
import { PreviewService } from './services/preview.service';
import { BreadcrumbDemoComponent } from './components/breadcrumb-demo/breadcrumb-demo.component';
import { ContentNodeSelectorComponent } from './components/content-node-selector/content-node-selector.component';
import { NotificationsComponent } from './components/notifications/notifications.component';
import { ReportIssueComponent } from './components/report-issue/report-issue.component';
import { CardViewComponent } from './components/card-view/card-view.component';
@NgModule({
imports: [
@@ -106,7 +109,10 @@ import { NotificationsComponent } from './components/notifications/notifications
FormLoadingComponent,
BlobPreviewComponent,
BreadcrumbDemoComponent,
NotificationsComponent
NotificationsComponent,
CardViewComponent,
ContentNodeSelectorComponent,
ReportIssueComponent
],
providers: [
{ provide: AppConfigService, useClass: DebugAppConfigService }, // not use this service in production
@@ -128,7 +134,10 @@ import { NotificationsComponent } from './components/notifications/notifications
},
PreviewService
],
entryComponents: [VersionManagerDialogAdapterComponent, MetadataDialogAdapterComponent],
entryComponents: [
VersionManagerDialogAdapterComponent,
MetadataDialogAdapterComponent
],
bootstrap: [AppComponent]
})
export class AppModule {}
+21
View File
@@ -50,6 +50,9 @@ import { DemoPermissionComponent } from './components/permissions/demo-permissio
import { BlobPreviewComponent } from './components/blob-preview/blob-preview.component';
import { BreadcrumbDemoComponent } from './components/breadcrumb-demo/breadcrumb-demo.component';
import { NotificationsComponent } from './components/notifications/notifications.component';
import { CardViewComponent } from './components/card-view/card-view.component';
import { ContentNodeSelectorComponent } from './components/content-node-selector/content-node-selector.component';
import { ReportIssueComponent } from './components/report-issue/report-issue.component';
export const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
@@ -73,6 +76,16 @@ export const appRoutes: Routes = [
}
]
},
{
path: 'card-view',
component: AppLayoutComponent ,
children: [
{
path: '',
component: CardViewComponent
}
]
},
{
path: '',
component: AppLayoutComponent,
@@ -86,6 +99,10 @@ export const appRoutes: Routes = [
path: 'home',
component: HomeComponent
},
{
path: 'node-selector',
component: ContentNodeSelectorComponent
},
{
path: 'settings-layout',
component: SettingsComponent
@@ -216,6 +233,10 @@ export const appRoutes: Routes = [
component: OverlayViewerComponent,
canActivate: [AuthGuardEcm]
},
{
path: 'report-issue',
component: ReportIssueComponent
},
{
path: 'datatable-lazy',
loadChildren: 'app/components/lazy-loading/lazy-loading.module#LazyLoadingModule'
@@ -1,32 +1,30 @@
<adf-sidenav-layout [sidenavMin]="70" [sidenavMax]="220" [stepOver]="780" [hideSidenav]="false"[expandedSidenav]= "expandedSidenav" (expanded)="setState($event)">
<adf-sidenav-layout [sidenavMin]="70" [sidenavMax]="220" [stepOver]="780" [hideSidenav]="hideSidenav" [expandedSidenav]= "expandedSidenav" (expanded)="setState($event)">
<adf-sidenav-layout-header>
<ng-template let-toggleMenu="toggleMenu">
<mat-toolbar color="primary" class="adf-app-layout-toolbar mat-elevation-z6">
<button mat-icon-button (click)="toggleMenu()">
<mat-icon>menu</mat-icon>
</button>
<adf-layout-header title="{{ 'APP_LAYOUT.APP_NAME' | translate}}" color="primary" (clicked)=toggleMenu($event)>
<span fxFlex="1 1 auto" fxShow fxHide.lt-sm="true">{{'APP_LAYOUT.APP_NAME' | translate }}</span>
<div class="adf-app-layout-menu-spacer"></div>
<div class="adf-app-layout-menu-spacer"></div>
<app-search-bar fxFlex="0 1 auto"></app-search-bar>
<app-search-bar fxFlex="0 1 auto"></app-search-bar>
<div class="adf-header-delimiter"></div>
<adf-userinfo
class="adf-app-layout-user-profile"
[menuPositionX]="'before'"
[menuPositionY]="'above'">
</adf-userinfo>
<adf-userinfo
class="adf-app-layout-user-profile"
[menuPositionX]="'before'"
[menuPositionY]="'above'">
</adf-userinfo>
<app-theme-picker></app-theme-picker>
<button mat-icon-button [matMenuTriggerFor]="langMenu">
<mat-icon>language</mat-icon>
</button>
<mat-menu #langMenu="matMenu" class="adf-menu">
<adf-language-menu></adf-language-menu>
</mat-menu>
</mat-toolbar>
<app-theme-picker></app-theme-picker>
<button mat-icon-button [matMenuTriggerFor]="langMenu">
<mat-icon>language</mat-icon>
</button>
<mat-menu #langMenu="matMenu" class="adf-menu">
<adf-language-menu></adf-language-menu>
</mat-menu>
</adf-layout-header>
</ng-template>
</adf-sidenav-layout-header>
@@ -34,6 +34,8 @@ export class AppLayoutComponent implements OnInit {
{ href: '/files', icon: 'folder_open', title: 'APP_LAYOUT.CONTENT_SERVICES' },
{ href: '/breadcrumb', icon: 'label', title: 'APP_LAYOUT.BREADCRUMB' },
{ href: '/notifications', icon: 'alarm', title: 'APP_LAYOUT.NOTIFICATIONS'},
{ href: '/card-view', icon: 'view_headline', title: 'APP_LAYOUT.CARD_VIEW'},
{ href: '/node-selector', icon: 'attachment', title: 'APP_LAYOUT.NODE-SELECTOR' },
{ href: '/activiti', icon: 'device_hub', title: 'APP_LAYOUT.PROCESS_SERVICES' },
{ href: '/login', icon: 'vpn_key', title: 'APP_LAYOUT.LOGIN' },
{ href: '/trashcan', icon: 'delete', title: 'APP_LAYOUT.TRASHCAN' },
@@ -54,6 +56,8 @@ export class AppLayoutComponent implements OnInit {
expandedSidenav = false;
hideSidenav = false;
enabelRedirect = true;
ngOnInit() {
@@ -19,7 +19,7 @@ import { Component } from '@angular/core';
@Component({
templateUrl: './breadcrumb-demo.component.html',
styleUrls: [`./breadcrumb-demo.component.scss`],
styleUrls: [`./breadcrumb-demo.component.scss`]
})
export class BreadcrumbDemoComponent {
@@ -0,0 +1,16 @@
<div class="main-content">
<h1>CardView Component</h1>
<mat-card class="card-view">
<adf-card-view
[properties]="properties"
[editable]="true">
</adf-card-view>
</mat-card>
<div class="console" #console>
<h3>Changes log:</h3>
<p *ngFor="let log of logs">{{ log }}</p>
</div>
</div>
@@ -0,0 +1,28 @@
.main-content {
padding: 0 15px;
}
.card-view {
width: 30%;
display: inline-block;
}
.console {
width: 60%;
display: inline-block;
vertical-align: top;
margin-left: 10px;
height: 500px;
overflow: scroll;
padding-bottom: 30px;
h3 {
margin-top: 0;
}
p {
display: block;
font-family: monospace, monospace;
margin: 0;
}
}
@@ -0,0 +1,123 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, OnInit, ElementRef, ViewChild } from '@angular/core';
import {
CardViewTextItemModel,
CardViewDateItemModel,
CardViewDatetimeItemModel,
CardViewBoolItemModel,
CardViewIntItemModel,
CardViewFloatItemModel,
CardViewKeyValuePairsItemModel,
CardViewSelectItemModel,
CardViewUpdateService,
UpdateNotification
} from '@alfresco/adf-core';
import { of } from 'rxjs/observable/of';
@Component({
templateUrl: './card-view.component.html',
styleUrls: ['./card-view.component.scss']
})
export class CardViewComponent implements OnInit {
@ViewChild('console') console: ElementRef;
properties: any;
logs: string[];
constructor(private cardViewUpdateService: CardViewUpdateService) {
this.logs = [];
this.properties = [
new CardViewTextItemModel({
label: 'CardView Text Item',
value: 'Spock',
key: 'name',
default: 'default bar' ,
multiline: false,
icon: 'icon',
editable: true
}),
new CardViewDateItemModel({
label: 'CardView Date Item',
value: new Date(),
key: 'date-of-birth',
default: new Date(),
format: 'DD.MM.YYYY',
editable: true
}),
new CardViewDatetimeItemModel({
label: 'CardView Datetime Item',
value: new Date(),
key: 'datetime-of-birth',
default: new Date(),
format: 'DD.MM.YYYY',
editable: true
}),
new CardViewBoolItemModel({
label: 'CardView Boolean Item',
value: true,
key: 'vulcanian',
default: false,
editable: true
}),
new CardViewIntItemModel({
label: 'CardView Int Item',
value: 213,
key: 'intelligence',
default: 1,
editable: true
}),
new CardViewFloatItemModel({
label: 'CardView Float Item',
value: 9.9,
key: 'mental-stability',
default: 0.0,
editable: true
}),
new CardViewKeyValuePairsItemModel({
label: 'CardView Key-Value Pairs Item',
value: [],
key: 'key-value-pairs',
editable: true
}),
new CardViewSelectItemModel({
label: 'CardView Select Item',
value: 'one',
options$: of([{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }]),
key: 'select',
editable: true
})
];
}
ngOnInit() {
this.cardViewUpdateService.itemUpdated$.subscribe(this.onItemChange.bind(this));
}
onItemChange(notification: UpdateNotification) {
let value = notification.changed[notification.target.key];
if (notification.target.type === 'keyvaluepairs') {
value = JSON.stringify(value);
}
this.logs.push(`[${notification.target.label}] - ${value}`);
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
}
}
@@ -0,0 +1,198 @@
<main>
<h1>Object picker</h1>
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Plain picker</h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
dropdownHideMyFiles: {{dropdownHideMyFiles}}
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[currentFolderId]="'-root-'">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Hide My files</h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
My Files site will be hided from dropdown
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[currentFolderId]="'-root-'"
[dropdownHideMyFiles]="true">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Custom Site List</h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
Adding a custom site list
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<div>
<mat-list>
<h3 mat-subheader>Add Site</h3>
<form class="content-node-selector-demo-example-form">
<mat-form-field>
<label>Site Guid</label>
<input matInput [(ngModel)]="customSideGuid" [ngModelOptions]="{standalone: true}">
</mat-form-field>
<mat-form-field>
<label>Site Title</label>
<input matInput [(ngModel)]="customSideTitle" [ngModelOptions]="{standalone: true}">
</mat-form-field>
</form>
<button mat-button color="primary" (click)="onClickAddSite()">Add</button>
<button mat-button color="primary" (click)="onClickResetSite()">Reset</button>
<mat-divider></mat-divider>
<h3 mat-subheader>Custom Sites Added</h3>
<mat-list-item *ngFor="let customSite of customSites.list.entries">
<mat-icon mat-list-icon>shopping_basket</mat-icon>
<h4 mat-line>{{customSite.entry.title}}</h4>
<p mat-line> {{customSite.entry.guid}} </p>
</mat-list-item>
</mat-list>
</div>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[currentFolderId]="'-root-'"
[dropdownSiteList]="customSites">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Row Filtering</h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
Will automatically filter the row on the list
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-slide-toggle color="primary" [(ngModel)]="showFiles" (click)="recreateRowFilterFunction()">
Show Files
</mat-slide-toggle>
<mat-slide-toggle color="primary" [(ngModel)]="showFolders" (click)="recreateRowFilterFunction()">
Slide Folders
</mat-slide-toggle>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[rowFilter]="rowFilterFunction"
[currentFolderId]="'-root-'">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Custom Image Resolving</h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
A function to manage the way folder/file icons and thumbnails are resolved
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-slide-toggle color="primary" (click)="recreateImageResolverFunction()">
Add Custom Images Resolver
</mat-slide-toggle>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[imageResolver]="customImageResolver"
[currentFolderId]="'-root-'">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Set Page Size</h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
Number of items shown per page in the list. actual page {{actualPageSize}}
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[currentFolderId]="'-root-'"
[pageSize]="actualPageSize"
>
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>Is Valid Selection Function </h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
Function used to decide if the selected node has permission to be selected
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<label>Only folder starting with the letter 'a' or 'A' are valid selections</label>
<label>Actual Selection is : {{validSelection}}</label>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
(select)="onNodeSelect($event)"
[isSelectionValid]="customIsValidFunction"
[currentFolderId]="'-root-'">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<h2>BreadCrumb Transform Function </h2>
</mat-panel-title>
<mat-panel-description>
<label class="content-node-selector-demo-basic-label">
Transformation to be performed on the chosen/folder node before building the breadcrumb UI
</label>
</mat-panel-description>
</mat-expansion-panel-header>
<label>Folder starting with letter 'd' or 'D' won't be displayed in the breadcrumb dropdown</label>
<div class="content-node-selector-demo-basic-table">
<adf-content-node-selector-panel
[breadcrumbTransform]="customBreadcrumbFunction"
[currentFolderId]="'-root-'">
</adf-content-node-selector-panel>
</div>
</mat-expansion-panel>
</mat-accordion>
</main>
@@ -0,0 +1,27 @@
.content-node-selector-demo {
&-main {
display: flex;
flex-direction: column;
justify-content: space-around;
align-content: center;
align-self: center;
align-items: center;
}
&-basic-table {
height:400px;
width:700px
}
&-basic-label {
display: flex;
align-self: center;
}
&-example-form {
display: flex;
flex-direction: column;
align-items: flex-start;
}
}
@@ -0,0 +1,115 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, ViewEncapsulation } from '@angular/core';
import { SitePaging, SiteEntry, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ShareDataRow } from '@alfresco/adf-content-services';
import { DataRow, DataColumn, ThumbnailService } from '@alfresco/adf-core';
@Component({
templateUrl: './content-node-selector.component.html',
styleUrls: [`./content-node-selector.component.scss`],
encapsulation: ViewEncapsulation.None
})
export class ContentNodeSelectorComponent {
constructor(private thumbnailService: ThumbnailService) {
}
dropdownHideMyFiles = false;
showFiles = false;
showFolders = false;
enableImageResolver = false;
validSelection = false;
customSideGuid = '';
customSideTitle = '';
actualPageSize = 2;
rowFilterFunction: any = null;
customImageResolver: any = null;
defaultSites: SiteEntry[] = [
{ entry: { title: 'MINE', guid: '-my-' } },
{ entry: { title: 'ROOTY', guid: '-root-' } }];
customSites: SitePaging = {
list: {
entries: [
{ entry: { title: 'MINE', guid: '-my-' } },
{ entry: { title: 'ROOTY', guid: '-root-' } }],
pagination: {}
}
};
onClickAddSite() {
const newSiteEntry: SiteEntry = { entry: { title: this.customSideTitle, guid: this.customSideGuid } };
this.customSites.list.entries.push(newSiteEntry);
this.customSideGuid = '';
this.customSideTitle = '';
}
onClickResetSite() {
this.customSites.list.entries = this.defaultSites;
this.customSideGuid = '';
this.customSideTitle = '';
}
recreateRowFilterFunction() {
this.rowFilterFunction = this.rowFilteringExample.bind(this);
}
recreateImageResolverFunction() {
this.enableImageResolver = !this.enableImageResolver;
if (this.enableImageResolver) {
this.customImageResolver = this.customImageResolverExample.bind(this);
} else {
this.customImageResolver = null;
}
}
rowFilteringExample(row: ShareDataRow) {
let showNode = true;
const node: MinimalNodeEntryEntity = row.node.entry;
if (this.showFiles) {
showNode = node.isFile;
}
if (this.showFolders) {
showNode = node.isFolder;
}
return showNode;
}
customImageResolverExample(row: DataRow, col: DataColumn) {
return this.thumbnailService.getMimeTypeIcon('video/quicktime');
}
onNodeSelect(node: MinimalNodeEntryEntity) {
this.validSelection = !!node;
}
customIsValidFunction(entry: MinimalNodeEntryEntity): boolean {
return entry.name.startsWith('a') || entry.name.startsWith('A');
}
customBreadcrumbFunction(node: MinimalNodeEntryEntity) {
if (node && node.path && node.path.elements) {
node.path.elements = node.path.elements.filter((element) => !element.name.toLocaleLowerCase().startsWith('d') ? element : null );
}
return node;
}
}
@@ -116,7 +116,7 @@
</mat-icon>
</button>
<button mat-icon-button
[disabled]="!documentList.selection.length"
disabled
[baseShareUrl]="baseShareUrl"
[adf-share]="documentList.selection[0]"
matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.SHARE' | translate }}">
@@ -428,7 +428,9 @@
<div *ngIf="processId">
<adf-start-process
[values]="formValues"
[appId]="processId">
[appId]="processId"
(start)="closeStartProcess()"
(cancel)="closeStartProcess()">
</adf-start-process>
</div>
@@ -474,6 +474,10 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
}
closeStartProcess() {
this.processId = null;
}
onChangePageSize(event: Pagination): void {
this.preference.paginationSize = event.maxItems;
this.changedPageSize.emit(event);
@@ -67,7 +67,7 @@ export class DemoPermissionComponent implements OnInit {
this.displayPermissionComponent.reload();
},
(error) => {
this.showErrorMessage(error);
this.showErrorMessage(error.message);
});
}
@@ -0,0 +1,9 @@
<div class="adf-report-issue">
<h1>Report Issue</h1>
<form class="adf-report-issue-form">
<mat-form-field>
<textarea matInput placeholder="Leave a comment" class="adf-report-issue-form-textarea"></textarea>
</mat-form-field>
</form>
<button mat-raised-button>Submit</button>
</div>
@@ -0,0 +1,24 @@
.adf-report-issue {
margin: 50px 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&-form {
width: 50%;
min-width: 250px;
margin-bottom: 50px;
& mat-form-field {
width: 100%;
}
&-textarea {
background-color: white;
width: 100%;
height: 250px;
}
}
}
@@ -0,0 +1,26 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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';
@Component({
templateUrl: './report-issue.component.html',
styleUrls: [`./report-issue.component.scss`],
})
export class ReportIssueComponent {
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

+47
View File
@@ -74,6 +74,17 @@ Defining properties from Typescript:
key: 'mental-stability',
default: 0.0
}),
new CardViewKeyValuePairsItemModel({
label: 'Variables',
value: [],
key: 'key-value-pairs'
}),
new CardViewSelectItemModel({
label: 'Select box',
value: 'one',
options$: of([{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }]),
key: 'select'
}),
...
]
```
@@ -99,6 +110,8 @@ You define the property list, the [`CardViewComponent`](../core/card-view.compon
- [**CardViewBoolItemModel**](#card-bool-item) - _for bool items (checkbox)_
- [**CardViewIntItemModel**](#card-int-item) - _for integer items_
- [**CardViewFloatItemModel**](#card-float-item) - _for float items_
- [**CardViewKeyValuePairsItemModel**](#card-key-values-pairs-item) - _for key-value-pairs items_
- [**CardViewSelectItemModel**](#card-select-item) - _for select items_
Each of these types implements the [Card View Item interface](card-view-item.interface.md):
@@ -287,6 +300,40 @@ const floatItemProperty = new CardViewFloatItemModel(options);
| displayValue\* | float | | The value to display |
| editable | boolean | false | Toggles whether the item is editable |
#### Card Key Value Pairs Item
[`CardViewKeyValuePairsItemModel`](../../lib/core/card-view/models/card-view-keyvaluepairs.model.ts) is a property type for key-value properties.
```ts
const keyValuePairsItemProperty = new CardViewKeyValuePairsItemModel(options);
```
| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| label\* | string | | Item label |
| key\* | string | | Identifying key (important when editing the item) |
| editable | boolean | false | Toggles whether the item is editable |
| value\* | `[{ name: '', value: '' }, ...]` | | The original data value for the item |
#### Card Select Item
[`CardViewSelectItemModel`](../../lib/core/card-view/models/card-view-selectitem.model.ts) is a property type for select properties.
```ts
const selectItemProperty = new CardViewSelectItemModel(options);
```
| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| label\* | string | | Item label |
| key\* | string | | Identifying key (important when editing the item) |
| editable | boolean | false | Toggles whether the item is editable |
| value | string | | The original data value for the item |
| options$\* | Observable<CardViewSelectItemOption[]> | | The original data value for the item |
## See also
- [Card View Update service](card-view-update.service.md)
+42
View File
@@ -0,0 +1,42 @@
---
Added: v2.4.0
Status: Experimental
---
## Header component
Reuseble header for Alfresco applications
## Basic usage
```html
<adf-layout-header
title="title"
logo="logo.png"
color="primary"
(toggled)=toggleMenu($event)>
<app-search-bar fxFlex="0 1 auto"></app-search-bar>
<app-theme-picker></app-theme-picker>
<adf-layout>
```
## Class members
### Properties
| Name | Type | Description |
| -- | -- | -- |
| title | string | Title of the application
| logo | string| Path to an image file for the application logo.
| color | string | Primary color for the header
| showSidenavToggle | boolean | Signals if the sidenav button will be displayed in the header or not. By default is true.
### Events
| Name | Type | Description |
| -- | -- | -- |
| clicked | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<boolean>` | Emitted when click on sidenav button
## Details
This component displays a customizable header which can be reused. The left side of the header (title, button) and the primary color for the header can be configured via input parameters.
The right part of the header are existing components which are transcluded in the header component.
+7 -1
View File
@@ -61,6 +61,9 @@ The configuration of this service is saved in the `app.config.json` file
The example below shows how to filter out the : '.git', '.DS_Store' and 'desktop.ini' files.
Each element of the ignore list is a glob pattern string, so you could exclude all the `.txt`
files, for example, by adding a `*.txt` pattern to the list.
There is also the possibility to add some more option to how perform the check via the `match-options` parameter.
For example in this case we have added the ignore case so `*.TXT` will match all the txt files ignoring the case for the extension.
For more information about the options available please check [minimatch](https://www.npmjs.com/package/minimatch#options) documentation.
**app.config.json**
@@ -72,7 +75,10 @@ files, for example, by adding a `*.txt` pattern to the list.
"name": "Alfresco"
},
"files": {
"excluded": [".DS_Store", "desktop.ini", ".git", "*.txt"]
"excluded": [".DS_Store", "desktop.ini", ".git", "*.txt"],
"match-options": {
"nocase": true
}
}
}
```
@@ -0,0 +1,31 @@
---
Added: v2.0.0
Status: Active
---
# Attach Form component
This component can be used when there is no form attached to a task and we want to add one.
## Basic Usage
```html
<adf-attach-form
[taskId]="taskid">
</adf-attach-form>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| -- | -- | -- | -- |
| taskId | `string` | | Id of the task. |
### Events
| Name | Type | Description |
| -- | -- | -- |
| cancelAttachForm | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<void>` | Emitted when the "Cancel" button is clicked. |
| completeAttachForm | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<void>` | Emitted when the form associated with the task is completed. |
@@ -25,6 +25,7 @@ This component can be used when there is no form attached to a task.
| hideCancelButton | `boolean` | true | Toggles rendering of the `Cancel` button. |
| isCompleted | `boolean` | false | If true then Task completed message is shown and `Complete` and `Cancel` buttons are hidden. |
| taskName | `string` | | Name of the task. |
| taskId | `number` | | Id of the task. |
### Events
@@ -153,7 +153,7 @@
}
& .adf-name-location-cell-name {
padding: 18px 0 2px 0;
padding: 5px 0 2px 0;
}
&.adf-content-selector-modified-cell {
@@ -45,7 +45,6 @@ export class NodeSharedDirective implements OnChanges {
shareNode(node: MinimalNodeEntity) {
if (node.entry && node.entry.isFile) {
this.setDisableAttribute(true);
this.dialog.open(ShareDialogComponent, {
width: '600px',
disableClose: true,
@@ -416,7 +416,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
if (changes.node && changes.node.currentValue) {
this.data.loadPage(changes.node.currentValue);
this.onDataReady(changes.node.currentValue);
} else if (changes.rowFilter) {
} else if (changes.rowFilter && changes.rowFilter.currentValue !== changes.rowFilter.previousValue) {
this.data.setFilter(changes.rowFilter.currentValue);
if (this.currentFolderId) {
this.loadFolderNodesByFolderNodeId(this.currentFolderId, this.pagination.getValue()).catch(err => this.error.emit(err));
@@ -562,7 +562,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
handlerSub = Observable.of(true);
}
if (typeof action.execute === 'function') {
if (typeof action.execute === 'function' && handlerSub) {
handlerSub.subscribe(() => {
action.execute(node);
});
+2 -1
View File
@@ -283,7 +283,8 @@
"EVERYONE" : "EVERYONE"
},
"ERROR": {
"DUPLICATE-PERMISSION": "One or more of the permissions you have set is already present : {{list}}"
"DUPLICATE-PERMISSION": "One or more of the permissions you have set is already present : {{list}}",
"NOT-ALLOWED": "You are not allowed to change permissions"
}
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@alfresco/adf-content-services",
"description": "Alfresco ADF content services",
"version": "2.4.0-beta14",
"version": "2.5.0-beta1",
"author": "Alfresco Software, Ltd.",
"main": "bundles/adf-content-services.js",
"repository": {
@@ -26,8 +26,8 @@
"@angular/platform-browser-dynamic": "5.1.1",
"@angular/router": "5.1.1",
"@ngx-translate/core": "9.1.1",
"alfresco-js-api": "2.4.0-beta14",
"@alfresco/adf-core": "2.4.0-beta14",
"alfresco-js-api": "2.5.0-beta1",
"@alfresco/adf-core": "2.5.0-beta1",
"chart.js": "2.5.0",
"core-js": "2.4.1",
"hammerjs": "2.0.8",
@@ -5,7 +5,7 @@
<button mat-button
id="adf-add-permission-action-button"
class="adf-permission-action"
[disabled]="selectedItems?.length === 0"
[disabled]="!isAddEnabled()"
(click)="applySelection()">
{{'PERMISSION_MANAGER.ADD-PERMISSION.ADD-ACTION' | translate}}
</button>
@@ -19,7 +19,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddPermissionComponent } from './add-permission.component';
import { AddPermissionPanelComponent } from './add-permission-panel.component';
import { By } from '@angular/platform-browser';
import { setupTestBed } from '@alfresco/adf-core';
import { setupTestBed, NodesApiService } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { fakeAuthorityResults } from '../../../mock/add-permission.component.mock';
import { ContentTestingModule } from '../../../testing/content.testing.module';
@@ -30,6 +30,7 @@ describe('AddPermissionComponent', () => {
let fixture: ComponentFixture<AddPermissionComponent>;
let element: HTMLElement;
let nodePermissionService: NodePermissionService;
let nodeApiService: NodesApiService;
setupTestBed({
imports: [
@@ -38,6 +39,8 @@ describe('AddPermissionComponent', () => {
});
beforeEach(() => {
nodeApiService = TestBed.get(NodesApiService);
spyOn(nodeApiService, 'getNode').and.returnValue(Observable.of({ id: 'fake-node', allowableOperations: ['updatePermissions']}));
fixture = TestBed.createComponent(AddPermissionComponent);
element = fixture.nativeElement;
nodePermissionService = TestBed.get(NodePermissionService);
@@ -67,12 +70,24 @@ describe('AddPermissionComponent', () => {
});
}));
it('should emit a success event when the node is updated', async(() => {
it('should NOT enable the ADD button when a selection is sent but the user does not have the permissions', async(() => {
const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance;
addPermissionPanelComponent.select.emit(fakeAuthorityResults);
fixture.componentInstance.currentNode = {id: 'fake-node-id'};
fixture.whenStable().then(() => {
fixture.detectChanges();
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
expect(addButton.disabled).toBeTruthy();
});
}));
it('should emit a success event when the node is updated', (done) => {
fixture.componentInstance.selectedItems = fakeAuthorityResults;
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.of({ id: 'fake-node-id'}));
fixture.componentInstance.success.subscribe((node) => {
expect(node.id).toBe('fake-node-id');
done();
});
fixture.detectChanges();
@@ -81,14 +96,25 @@ describe('AddPermissionComponent', () => {
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
}));
});
it('should emit an error event when the node update fail', async(() => {
it('should NOT emit a success event when the user does not have permission to update the node', () => {
fixture.componentInstance.selectedItems = fakeAuthorityResults;
fixture.componentInstance.currentNode = { id: 'fake-node-id' };
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.of({ id: 'fake-node-id' }));
let spySuccess = spyOn(fixture.componentInstance, 'success');
fixture.componentInstance.applySelection();
expect(spySuccess).not.toHaveBeenCalled();
});
it('should emit an error event when the node update fail', (done) => {
fixture.componentInstance.selectedItems = fakeAuthorityResults;
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.throw({ error: 'errored'}));
fixture.componentInstance.error.subscribe((error) => {
expect(error.error).toBe('errored');
done();
});
fixture.detectChanges();
@@ -97,6 +123,6 @@ describe('AddPermissionComponent', () => {
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
}));
});
});
@@ -18,6 +18,7 @@
import { Component, ViewEncapsulation, EventEmitter, Input, Output } from '@angular/core';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { NodePermissionService } from '../../services/node-permission.service';
import { NodesApiService, ContentService, PermissionsEnum } from '@alfresco/adf-core';
@Component({
selector: 'adf-add-permission',
@@ -43,22 +44,32 @@ export class AddPermissionComponent {
currentNode: MinimalNodeEntryEntity;
currentNodeRoles: string[];
constructor(private nodePermissionService: NodePermissionService) {
constructor(private nodePermissionService: NodePermissionService,
private nodeApiService: NodesApiService,
private contentService: ContentService) {
this.nodeApiService.getNode(this.nodeId).subscribe((node) => this.currentNode = node);
}
onSelect(selection: MinimalNodeEntity[]) {
this.selectedItems = selection;
}
isAddEnabled(): boolean {
return this.contentService.hasPermission(this.currentNode, PermissionsEnum.UPDATEPERMISSIONS) &&
this.selectedItems.length !== 0;
}
applySelection() {
this.nodePermissionService.updateNodePermissions(this.nodeId, this.selectedItems)
.subscribe(
(node) => {
this.success.emit(node);
},
(error) => {
this.error.emit(error);
});
if (this.contentService.hasPermission(this.currentNode, PermissionsEnum.UPDATEPERMISSIONS)) {
this.nodePermissionService.updateNodePermissions(this.nodeId, this.selectedItems)
.subscribe(
(node) => {
this.success.emit(node);
},
(error) => {
this.error.emit(error);
});
}
}
}
@@ -21,8 +21,9 @@ import { InheritPermissionDirective } from './inherited-button.directive';
import { NodesApiService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
const fakeNodeWithInherit: any = { id: 'fake-id', permissions : {isInheritanceEnabled : true}};
const fakeNodeNoInherit: any = { id: 'fake-id', permissions : {isInheritanceEnabled : false}};
const fakeNodeWithInherit: any = { id: 'fake-id', permissions : {isInheritanceEnabled : true}, allowableOperations: ['updatePermissions']};
const fakeNodeNoInherit: any = { id: 'fake-id', permissions : {isInheritanceEnabled : false}, allowableOperations: ['updatePermissions']};
const fakeNodeWithInheritNoPermission: any = { id: 'fake-id', permissions : {isInheritanceEnabled : true}};
describe('InheritPermissionDirective', () => {
@@ -95,4 +96,19 @@ describe('InheritPermissionDirective', () => {
});
}));
it('should not update the node when node has no permission', async(() => {
spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeNodeWithInheritNoPermission));
let spyUpdateNode = spyOn(nodeService, 'updateNode');
component.updatedNode = true;
fixture.detectChanges();
const buttonPermission: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#sample-button-permission');
expect(buttonPermission).not.toBeNull();
expect(element.querySelector('#update-notification')).not.toBeNull();
buttonPermission.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(spyUpdateNode).not.toHaveBeenCalled();
});
}));
});
@@ -17,7 +17,7 @@
/* tslint:disable:no-input-rename */
import { Directive, Input, Output, EventEmitter } from '@angular/core';
import { NodesApiService } from '@alfresco/adf-core';
import { NodesApiService, ContentService, PermissionsEnum } from '@alfresco/adf-core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
@Directive({
@@ -38,15 +38,20 @@ export class InheritPermissionDirective {
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
constructor(private nodeService: NodesApiService) {
constructor(private nodeService: NodesApiService,
private contentService: ContentService) {
}
onInheritPermissionClicked() {
this.nodeService.getNode(this.nodeId).subscribe((node: MinimalNodeEntryEntity) => {
const nodeBody = { permissions: { isInheritanceEnabled: !node.permissions.isInheritanceEnabled } };
this.nodeService.updateNode(this.nodeId, nodeBody, { include: ['permissions'] }).subscribe((nodeUpdated: MinimalNodeEntryEntity) => {
this.updated.emit(nodeUpdated);
}, (error) => this.error.emit(error));
if (this.contentService.hasPermission(node, PermissionsEnum.UPDATEPERMISSIONS)) {
const nodeBody = { permissions: { isInheritanceEnabled: !node.permissions.isInheritanceEnabled } };
this.nodeService.updateNode(this.nodeId, nodeBody, { include: ['permissions'] }).subscribe((nodeUpdated: MinimalNodeEntryEntity) => {
this.updated.emit(nodeUpdated);
}, (error) => this.error.emit(error));
} else {
this.error.emit('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
}
});
}
@@ -16,13 +16,14 @@
*/
import { TestBed, async } from '@angular/core/testing';
import { AppConfigService, setupTestBed } from '@alfresco/adf-core';
import { AppConfigService, setupTestBed, ContentService } from '@alfresco/adf-core';
import { NodePermissionDialogService } from './node-permission-dialog.service';
import { MatDialog } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { NodePermissionService } from './node-permission.service';
import { Node } from 'alfresco-js-api';
describe('NodePermissionDialogService', () => {
@@ -31,6 +32,7 @@ describe('NodePermissionDialogService', () => {
let spyOnDialogOpen: jasmine.Spy;
let afterOpenObservable: Subject<any>;
let nodePermissionService: NodePermissionService;
let contentService: ContentService;
setupTestBed({
imports: [ContentTestingModule],
@@ -44,6 +46,7 @@ describe('NodePermissionDialogService', () => {
materialDialog = TestBed.get(MatDialog);
afterOpenObservable = new Subject<any>();
nodePermissionService = TestBed.get(NodePermissionService);
contentService = TestBed.get(ContentService);
spyOnDialogOpen = spyOn(materialDialog, 'open').and.returnValue({
afterOpen: () => afterOpenObservable,
afterClosed: () => Observable.of({}),
@@ -57,27 +60,65 @@ describe('NodePermissionDialogService', () => {
expect(service).not.toBeNull();
});
it('should be able to open the dialog showing node permissions', () => {
service.openAddPermissionDialog('fake-node-id', 'fake-title');
expect(spyOnDialogOpen).toHaveBeenCalled();
describe('when node has permission to update permissions', () => {
let fakePermissionNode = {};
beforeEach(() => {
fakePermissionNode = <Node> { id: 'fake-permission-node', allowableOperations: ['updatePermissions']};
});
it('should be able to open the dialog showing node permissions', () => {
service.openAddPermissionDialog(fakePermissionNode, 'fake-title');
expect(spyOnDialogOpen).toHaveBeenCalled();
});
it('should return the updated node', (done) => {
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.of({id : 'fake-node-updated'}));
spyOn(service, 'openAddPermissionDialog').and.returnValue(Observable.of({}));
spyOn(contentService, 'getNode').and.returnValue(Observable.of(fakePermissionNode));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe((node) => {
expect(node.id).toBe('fake-node-updated');
done();
});
});
it('should throw an error if the update of the node fails', (done) => {
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.throw({error : 'error'}));
spyOn(service, 'openAddPermissionDialog').and.returnValue(Observable.of({}));
spyOn(contentService, 'getNode').and.returnValue(Observable.of(fakePermissionNode));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe(() => {
Observable.throw('This call should fail');
}, (error) => {
expect(error.error).toBe('error');
done();
});
});
});
it('should throw an error if the update of the node fails', async(() => {
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.throw({error : 'error'}));
spyOn(service, 'openAddPermissionDialog').and.returnValue(Observable.of({}));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe(() => {
Observable.throw('This call should fail');
}, (error) => {
expect(error.error).toBe('error');
});
}));
describe('when node does not have permission to update permissions', () => {
it('should return the updated node', async(() => {
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(Observable.of({id : 'fake-node'}));
spyOn(service, 'openAddPermissionDialog').and.returnValue(Observable.of({}));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe((node) => {
expect(node.id).toBe('fake-node');
});
}));
let fakeForbiddenNode = {};
beforeEach(() => {
fakeForbiddenNode = <Node> { id: 'fake-permission-node', allowableOperations: ['update']};
});
it('should not be able to open the dialog showing node permissions', () => {
service.openAddPermissionDialog(fakeForbiddenNode, 'fake-title');
expect(spyOnDialogOpen).not.toHaveBeenCalled();
});
it('should return the updated node', (done) => {
spyOn(contentService, 'getNode').and.returnValue(Observable.of(fakeForbiddenNode));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe((node) => {
Observable.throw('This call should fail');
},
(error) => {
expect(error.message).toBe('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
done();
});
});
});
});
@@ -21,14 +21,16 @@ import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { AddPermissionDialogComponent } from '../components/add-permission/add-permission-dialog.component';
import { AddPermissionDialogData } from '../components/add-permission/add-permission-dialog-data.interface';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { MinimalNodeEntity, MinimalNodeEntryEntity, Node } from 'alfresco-js-api';
import { NodePermissionService } from './node-permission.service';
import { ContentService, PermissionsEnum } from '@alfresco/adf-core';
@Injectable()
export class NodePermissionDialogService {
constructor(private dialog: MatDialog,
private nodePermissionService: NodePermissionService) {
private nodePermissionService: NodePermissionService,
private contentService: ContentService) {
}
/**
@@ -37,21 +39,27 @@ export class NodePermissionDialogService {
* @param title Dialog title
* @returns Node with updated permissions
*/
openAddPermissionDialog(nodeId: string, title?: string): Observable<MinimalNodeEntity[]> {
const confirm = new Subject<MinimalNodeEntity[]>();
openAddPermissionDialog(node: Node, title?: string): Observable<MinimalNodeEntity[]> {
if (this.contentService.hasPermission(node, PermissionsEnum.UPDATEPERMISSIONS)) {
const confirm = new Subject<MinimalNodeEntity[]>();
confirm.subscribe({
complete: this.close.bind(this)
});
confirm.subscribe({
complete: this.close.bind(this)
});
const data: AddPermissionDialogData = {
nodeId: nodeId,
title: title,
confirm : confirm
};
const data: AddPermissionDialogData = {
nodeId: node.id,
title: title,
confirm: confirm
};
this.openDialog(data, 'adf-add-permission-dialog', '630px');
return confirm;
this.openDialog(data, 'adf-add-permission-dialog', '630px');
return confirm;
} else {
let errors = new Error(JSON.stringify({ error: { statusCode: 403 } }));
errors.message = 'PERMISSION_MANAGER.ERROR.NOT-ALLOWED';
return Observable.throw(errors);
}
}
private openDialog(data: any, currentPanelClass: string, chosenWidth: string) {
@@ -72,8 +80,10 @@ export class NodePermissionDialogService {
* @returns Node with updated permissions
*/
updateNodePermissionByDialog(nodeId?: string, title?: string): Observable<MinimalNodeEntryEntity> {
return this.openAddPermissionDialog(nodeId, title).switchMap((selection) => {
return this.nodePermissionService.updateNodePermissions(nodeId, selection);
return this.contentService.getNode(nodeId, { include: ['allowableOperations'] }).switchMap((node) => {
return this.openAddPermissionDialog(node.entry, title).switchMap((selection) => {
return this.nodePermissionService.updateNodePermissions(nodeId, selection);
});
});
}
}
@@ -5,7 +5,7 @@
matInput [formControl]="from" [errorStateMatcher]="matcher"
placeholder="{{ 'SEARCH.FILTER.RANGE.FROM' | translate }}"
autocomplete="off">
<mat-error *ngIf="from.hasError('pattern')">
<mat-error *ngIf="from.hasError('pattern') || from.hasError('min')">
{{ 'SEARCH.FILTER.VALIDATION.INVALID-FORMAT' | translate }}
</mat-error>
<mat-error *ngIf="from.hasError('required')">
@@ -18,7 +18,7 @@
matInput [formControl]="to" [errorStateMatcher]="matcher"
placeholder="{{ 'SEARCH.FILTER.RANGE.TO' | translate }}"
autocomplete="off">
<mat-error *ngIf="to.hasError('pattern')">
<mat-error *ngIf="to.hasError('pattern') || to.hasError('min')">
{{ 'SEARCH.FILTER.VALIDATION.INVALID-FORMAT' | translate }}
</mat-error>
<mat-error *ngIf="to.hasError('required')">
@@ -160,4 +160,10 @@ describe('SearchNumberRangeComponent', () => {
component.from = new FormControl(123, component.validators);
expect(component.from.hasError('required')).toBe(false);
});
it('should throw error if "from" value is a negative value', () => {
component.ngOnInit();
component.from = new FormControl(-100, component.validators);
expect(component.from.hasError('min')).toBe(true);
});
});
@@ -55,7 +55,8 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
this.validators = Validators.compose([
Validators.required,
Validators.pattern(/^-?(0|[1-9]\d*)?$/)
Validators.pattern(/^-?(0|[1-9]\d*)?$/),
Validators.min(0)
]);
this.from = new FormControl('', this.validators);
@@ -78,9 +79,8 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
map.set('TO', model.to);
const value = this.formatString(this.format, map);
const query = `${this.field}:${value}`;
this.context.queryFragments[this.id] = query;
this.context.queryFragments[this.id] = `${this.field}:${value}`;
this.context.update();
}
}
@@ -55,26 +55,18 @@ export class SearchQueryBuilderService {
// TODO: to be supported in future iterations
ranges: { [id: string]: SearchRange } = {};
constructor(appConfig: AppConfigService, private alfrescoApiService: AlfrescoApiService) {
this.config = appConfig.get<SearchConfiguration>('search');
constructor(private appConfig: AppConfigService, private alfrescoApiService: AlfrescoApiService) {
this.resetToDefaults();
}
resetToDefaults() {
if (this.config) {
this.categories =
(this.config.categories || [])
.filter(category => category.enabled)
.map(category => { return { ...category }; });
this.filterQueries =
(this.config.filterQueries || [])
.map(query => { return {...query}; });
const template = this.appConfig.get<SearchConfiguration>('search');
if (template) {
this.config = JSON.parse(JSON.stringify(template));
this.categories = (this.config.categories || []).filter(category => category.enabled);
this.filterQueries = this.config.filterQueries || [];
if (this.config.sorting) {
this.sorting =
(this.config.sorting.defaults || [])
.map(sorting => { return { ...sorting }; });
this.sorting = this.config.sorting.defaults || [];
}
}
}
@@ -367,7 +367,36 @@ describe('UploadDragAreaComponent', () => {
addToQueueSpy.and.callFake((fileList) => {
expect(fileList.name).toBe('file');
expect(fileList.options.path).toBe('/pippo');
expect(fileList.options.path).toBe('pippo/');
});
let fakeCustomEvent: CustomEvent = new CustomEvent('CustomEvent', {
detail: {
data: getFakeShareDataRow(),
files: [fakeItem]
}
});
component.onUploadFiles(fakeCustomEvent);
}));
it('should upload a folder to a specific target folder when dropped onto one', async(() => {
let fakeItem = {
fullPath: '/folder-fake/file-fake.png',
isDirectory: false,
isFile: true,
name: 'file-fake.png',
relativeFolder: '/super',
file: (callbackFile) => {
let fileFake = new File(['fakefake'], 'file-fake.png', { type: 'image/png' });
callbackFile(fileFake);
}
};
addToQueueSpy.and.callFake((fileList) => {
expect(fileList.name).toBe('file');
expect(fileList.options.path).toBe('pippo/super');
});
let fakeCustomEvent: CustomEvent = new CustomEvent('CustomEvent', {
@@ -121,7 +121,7 @@ export class UploadDragAreaComponent extends UploadBase implements NodePermissio
let fileInfo: FileInfo[] = event.detail.files;
if (this.isTargetNodeFolder(event)) {
const destinationFolderName = event.detail.data.obj.entry.name;
fileInfo.map((file) => file.relativeFolder = file.relativeFolder.concat(destinationFolderName));
fileInfo.map((file) => file.relativeFolder = destinationFolderName ? destinationFolderName.concat(file.relativeFolder) : file.relativeFolder);
}
if (fileInfo && fileInfo.length > 0) {
this.uploadFilesInfo(fileInfo);
+50
View File
@@ -318,6 +318,56 @@
"description": "File exclusions",
"type": "array",
"items": { "type": "string" }
},
"match-options": {
"description": "Minimatch plugin option that will be applied for the check. By default all the options are false",
"type": "object",
"properties": {
"debug": {
"description": "Dump a ton of stuff to stderr",
"type": ["boolean", "null"]
},
"nobrace": {
"description": "Do not expand {a,b} and {1..3} brace sets.",
"type": ["boolean", "null"]
},
"noglobstar": {
"description": "Disable ** matching against multiple folder names.",
"type": ["boolean", "null"]
},
"dot": {
"description": "Allow patterns to match filenames starting with a period, even if the pattern does not explicitly have a period in that spot.",
"type": ["boolean", "null"]
},
"noext": {
"description": "Disable 'extglob' style patterns like +(a|b).",
"type": ["boolean", "null"]
},
"nocase": {
"description": "Perform a case-insensitive match.",
"type": ["boolean", "null"]
},
"nonull": {
"description": "When a match is not found by minimatch.match, return a list containing the pattern itself if this option is set. When not set, an empty list is returned if there are no matches.",
"type": ["boolean", "null"]
},
"matchBase": {
"description": "If set, then patterns without slashes will be matched against the basename of the path if it contains slashes.",
"type": ["boolean", "null"]
},
"nocomment": {
"description": "Suppress the behavior of treating # at the start of a pattern as a comment.",
"type": ["boolean", "null"]
},
"nonegate": {
"description": "Suppress the behavior of treating a leading ! character as negation.",
"type": ["boolean", "null"]
},
"flipNegate": {
"description": "Returns from negate expressions the same as if they were not negated.",
"type": ["boolean", "null"]
}
}
}
}
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

+15 -3
View File
@@ -21,10 +21,12 @@ import { FormsModule } from '@angular/forms';
import {
MatButtonModule,
MatDatepickerModule,
MatTableModule,
MatIconModule,
MatInputModule,
MatCheckboxModule,
MatNativeDateModule
MatNativeDateModule,
MatSelectModule
} from '@angular/material';
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
import { FlexLayoutModule } from '@angular/flex-layout';
@@ -37,6 +39,8 @@ import { CardViewDateItemComponent } from './components/card-view-dateitem/card-
import { CardViewItemDispatcherComponent } from './components/card-view-item-dispatcher/card-view-item-dispatcher.component';
import { CardViewMapItemComponent } from './components/card-view-mapitem/card-view-mapitem.component';
import { CardViewTextItemComponent } from './components/card-view-textitem/card-view-textitem.component';
import { CardViewKeyValuePairsItemComponent } from './components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
import { CardViewSelectItemComponent } from './components/card-view-selectitem/card-view-selectitem.component';
@NgModule({
imports: [
@@ -48,7 +52,9 @@ import { CardViewTextItemComponent } from './components/card-view-textitem/card-
MatNativeDateModule,
MatCheckboxModule,
MatInputModule,
MatTableModule,
MatIconModule,
MatSelectModule,
MatButtonModule,
MatDatetimepickerModule,
MatNativeDatetimeModule
@@ -59,6 +65,8 @@ import { CardViewTextItemComponent } from './components/card-view-textitem/card-
CardViewDateItemComponent,
CardViewMapItemComponent,
CardViewTextItemComponent,
CardViewKeyValuePairsItemComponent,
CardViewSelectItemComponent,
CardViewItemDispatcherComponent,
CardViewContentProxyDirective
],
@@ -66,14 +74,18 @@ import { CardViewTextItemComponent } from './components/card-view-textitem/card-
CardViewBoolItemComponent,
CardViewDateItemComponent,
CardViewMapItemComponent,
CardViewTextItemComponent
CardViewTextItemComponent,
CardViewSelectItemComponent,
CardViewKeyValuePairsItemComponent
],
exports: [
CardViewComponent,
CardViewBoolItemComponent,
CardViewDateItemComponent,
CardViewMapItemComponent,
CardViewTextItemComponent
CardViewTextItemComponent,
CardViewSelectItemComponent,
CardViewKeyValuePairsItemComponent
]
})
export class CardViewModule {}
@@ -0,0 +1,58 @@
<div class="adf-property-label">{{ property.label | translate }}</div>
<div class="adf-property-value">
<div *ngIf="isEditable()">
{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.ADD' | translate }}
<button (click)="add()" mat-icon-button class="card-view__key-value-pairs__add-btn">
<mat-icon>add</mat-icon>
</button>
</div>
<div *ngIf="!isEditable()" class="card-view__key-value-pairs__read-only">
<mat-table #table [dataSource]="matTableValues" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.NAME' | translate }}</mat-header-cell>
<mat-cell *matCellDef="let item">{{item.name}}</mat-cell>
</ng-container>
<ng-container matColumnDef="value">
<mat-header-cell *matHeaderCellDef>{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.VALUE' | translate }}</mat-header-cell>
<mat-cell *matCellDef="let item">{{item.value}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="['name', 'value']"></mat-header-row>
<mat-row *matRowDef="let row; columns: ['name', 'value'];"></mat-row>
</mat-table>
</div>
<div class="card-view__key-value-pairs" *ngIf="isEditable() && values && values.length">
<div class="card-view__key-value-pairs__row">
<div class="card-view__key-value-pairs__col">{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.NAME' | translate }}</div>
<div class="card-view__key-value-pairs__col">{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.VALUE' | translate }}</div>
</div>
<div class="card-view__key-value-pairs__row" *ngFor="let item of values; let i = index">
<div class="card-view__key-value-pairs__col">
<mat-form-field class="example-full-width">
<input matInput
placeholder="{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.NAME' | translate }}"
(blur)="onBlur(item.value)"
[attr.data-automation-id]="'card-'+ property.key +'-name-input-' + i"
[(ngModel)]="values[i].name">
</mat-form-field>
</div>
<div class="card-view__key-value-pairs__col">
<mat-form-field class="example-full-width">
<input matInput
placeholder="{{ 'CORE.CARDVIEW.KEYVALUEPAIRS.VALUE' | translate }}"
(blur)="onBlur(item.value)"
[attr.data-automation-id]="'card-'+ property.key +'-value-input-' + i"
[(ngModel)]="values[i].value">
</mat-form-field>
</div>
<button mat-icon-button (click)="remove(i)" class="card-view__key-value-pairs__remove-btn">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
@@ -0,0 +1,22 @@
.card-view {
&__key-value-pairs {
&__col {
display: inline-block;
width: 39%;
.mat-form-field {
width: 100%;
}
}
&__read-only {
.mat-table {
box-shadow: none;
}
.mat-header-row, .mat-row {
padding: 0;
}
}
}
}
@@ -0,0 +1,176 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CardViewKeyValuePairsItemModel } from '../../models/card-view-keyvaluepairs.model';
import { CardViewKeyValuePairsItemComponent } from './card-view-keyvaluepairsitem.component';
import { setupTestBed } from '../../../testing/setupTestBed';
import { CoreTestingModule } from '../../../testing/core.testing.module';
import { CardViewUpdateService } from '../../services/card-view-update.service';
describe('CardViewKeyValuePairsItemComponent', () => {
let fixture: ComponentFixture<CardViewKeyValuePairsItemComponent>;
let component: CardViewKeyValuePairsItemComponent;
let cardViewUpdateService;
const mockEmptyData = [{ name: '', value: '' }];
const mockData = [{ name: 'test-name', value: 'test-value' }];
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(() => {
fixture = TestBed.createComponent(CardViewKeyValuePairsItemComponent);
cardViewUpdateService = TestBed.get(CardViewUpdateService);
component = fixture.componentInstance;
component.property = new CardViewKeyValuePairsItemModel({
label: 'Key Value Pairs',
value: [],
key: 'key-value-pairs',
editable: true
});
component.editable = true;
});
afterEach(() => {
fixture.destroy();
});
describe('Component', () => {
it('should render the label', () => {
fixture.detectChanges();
const labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
expect(labelValue).not.toBeNull();
expect(labelValue.nativeElement.innerText).toBe('Key Value Pairs');
});
it('should render readOnly table is editable property is FALSE', () => {
component.property = new CardViewKeyValuePairsItemModel({
label: 'Key Value Pairs',
value: mockData,
key: 'key-value-pairs'
});
component.ngOnChanges();
fixture.detectChanges();
const table = fixture.debugElement.query(By.css('.card-view__key-value-pairs__read-only'));
const form = fixture.debugElement.query(By.css('.card-view__key-value-pairs'));
expect(table).not.toBeNull();
expect(form).toBeNull();
});
it('should add new item on ADD button click', () => {
component.ngOnChanges();
fixture.detectChanges();
const addButton = fixture.debugElement.query(By.css('.card-view__key-value-pairs__add-btn'));
addButton.triggerEventHandler('click', null);
fixture.detectChanges();
expect(JSON.stringify(component.values)).toBe(JSON.stringify(mockEmptyData));
const content = fixture.debugElement.query(By.css('.card-view__key-value-pairs'));
expect(content).not.toBeNull();
let nameInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-name-input-0"]`));
let valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
expect(nameInput).not.toBeNull();
expect(valueInput).not.toBeNull();
});
it('should remove an item from list on REMOVE button click', () => {
spyOn(cardViewUpdateService, 'update');
component.ngOnChanges();
fixture.detectChanges();
const addButton = fixture.debugElement.query(By.css('.card-view__key-value-pairs__add-btn'));
addButton.triggerEventHandler('click', null);
fixture.detectChanges();
const removeButton = fixture.debugElement.query(By.css('.card-view__key-value-pairs__remove-btn'));
removeButton.triggerEventHandler('click', null);
fixture.detectChanges();
expect(component.values.length).toBe(0);
const content = fixture.debugElement.query(By.css('.card-view__key-value-pairs'));
expect(content).toBeNull();
expect(cardViewUpdateService.update).toHaveBeenCalled();
expect(component.property.value.length).toBe(0);
});
it('should update property on input blur', async(() => {
spyOn(cardViewUpdateService, 'update');
component.ngOnChanges();
fixture.detectChanges();
const addButton = fixture.debugElement.query(By.css('.card-view__key-value-pairs__add-btn'));
addButton.triggerEventHandler('click', null);
fixture.detectChanges();
let nameInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-name-input-0"]`));
let valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
nameInput.nativeElement.value = mockData[0].name;
nameInput.nativeElement.dispatchEvent(new Event('input'));
valueInput.nativeElement.value = mockData[0].value;
valueInput.nativeElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
valueInput.triggerEventHandler('blur', null);
fixture.detectChanges();
expect(cardViewUpdateService.update).toHaveBeenCalled();
expect(JSON.stringify(component.property.value)).toBe(JSON.stringify(mockData));
});
}));
it('should not update property if at least one input is empty on blur', async(() => {
spyOn(cardViewUpdateService, 'update');
component.ngOnChanges();
fixture.detectChanges();
const addButton = fixture.debugElement.query(By.css('.card-view__key-value-pairs__add-btn'));
addButton.triggerEventHandler('click', null);
fixture.detectChanges();
let valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
valueInput.nativeElement.value = mockData[0].value;
valueInput.nativeElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
valueInput.triggerEventHandler('blur', null);
fixture.detectChanges();
expect(cardViewUpdateService.update).not.toHaveBeenCalled();
});
}));
});
});
@@ -0,0 +1,75 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, Input, OnChanges } from '@angular/core';
import { CardViewUpdateService } from '../../services/card-view-update.service';
import { CardViewKeyValuePairsItemModel } from '../../models/card-view.models';
import { CardViewKeyValuePairsItemType } from '../../interfaces/card-view.interfaces';
import { MatTableDataSource } from '@angular/material';
@Component({
selector: 'adf-card-view-boolitem',
templateUrl: './card-view-keyvaluepairsitem.component.html',
styleUrls: ['./card-view-keyvaluepairsitem.component.scss']
})
export class CardViewKeyValuePairsItemComponent implements OnChanges {
@Input()
property: CardViewKeyValuePairsItemModel;
@Input()
editable: boolean = false;
values: CardViewKeyValuePairsItemType[];
matTableValues: MatTableDataSource<CardViewKeyValuePairsItemType>;
constructor(private cardViewUpdateService: CardViewUpdateService) {}
ngOnChanges() {
this.values = this.property.value || [];
this.matTableValues = new MatTableDataSource(this.values);
}
isEditable(): boolean {
return this.editable && this.property.editable;
}
add(): void {
this.values.push({ name: '', value: '' });
}
remove(index: number): void {
this.values.splice(index, 1);
this.save(true);
}
onBlur(value): void {
if (value.length) {
this.save();
}
}
save(remove?: boolean): void {
const validValues = this.values.filter(i => i.name.length && i.value.length);
if (remove || validValues.length) {
this.cardViewUpdateService.update(this.property, validValues);
this.property.value = validValues;
}
}
}
@@ -0,0 +1,13 @@
<div class="adf-property-label">{{ property.label | translate }}</div>
<div class="adf-property-value">
<div *ngIf="!isEditable()" data-automation-class="read-only-value">{{ property.displayValue | async }}</div>
<div *ngIf="isEditable()">
<mat-form-field>
<mat-select [(value)]="value" (change)="onChange($event)" data-automation-class="select-box">
<mat-option *ngFor="let option of getOptions() | async" [value]="option.key">
{{ option.label | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@@ -0,0 +1,3 @@
.mat-form-field-type-mat-select {
width: 100%;
}
@@ -0,0 +1,122 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
import { CardViewUpdateService } from '../../services/card-view-update.service';
import { CardViewSelectItemComponent } from './card-view-selectitem.component';
import { setupTestBed } from '../../../testing/setupTestBed';
import { CoreTestingModule } from '../../../testing/core.testing.module';
import { of } from 'rxjs/observable/of';
describe('CardViewSelectItemComponent', () => {
let fixture: ComponentFixture<CardViewSelectItemComponent>;
let component: CardViewSelectItemComponent;
let cardViewUpdateService;
const mockData = [{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }, { key: 'three', label: 'Three' }];
const mockDefaultProps = {
label: 'Select box label',
value: 'two',
options$: of(mockData),
key: 'key',
editable: true
};
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(() => {
fixture = TestBed.createComponent(CardViewSelectItemComponent);
cardViewUpdateService = TestBed.get(CardViewUpdateService);
component = fixture.componentInstance;
component.property = new CardViewSelectItemModel(mockDefaultProps);
});
afterEach(() => {
fixture.destroy();
});
describe('Rendering', () => {
it('should render the label', () => {
fixture.detectChanges();
const labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
expect(labelValue).not.toBeNull();
expect(labelValue.nativeElement.innerText).toBe('Select box label');
});
it('should render readOnly value is editable property is FALSE', () => {
component.property = new CardViewSelectItemModel({
...mockDefaultProps,
editable: false
});
component.ngOnChanges();
fixture.detectChanges();
const readOnly = fixture.debugElement.query(By.css('[data-automation-class="read-only-value"]'));
const selectBox = fixture.debugElement.query(By.css('[data-automation-class="select-box"]'));
expect(readOnly).not.toBeNull();
expect(selectBox).toBeNull();
});
it('should render select box if editable property is TRUE', () => {
component.ngOnChanges();
component.editable = true;
fixture.detectChanges();
const selectBox = fixture.debugElement.query(By.css('[data-automation-class="select-box"]'));
expect(selectBox).not.toBeNull();
});
it('should not have label twice', () => {
component.ngOnChanges();
component.editable = true;
fixture.detectChanges();
const label = fixture.debugElement.query(By.css('[data-automation-class="select-box"] .mat-form-field-label'));
expect(label).toBeNull();
});
it('should update property on input blur', async(() => {
spyOn(cardViewUpdateService, 'update');
component.ngOnChanges();
component.editable = true;
fixture.detectChanges();
const selectBox = fixture.debugElement.query(By.css('[data-automation-class="select-box"]'));
selectBox.nativeElement.click();
fixture.detectChanges();
const option = fixture.debugElement.query(By.css(`mat-option[ng-reflect-value="${mockData[2].key}"]`));
option.nativeElement.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(cardViewUpdateService.update).toHaveBeenCalled();
expect(component.property.value).toBe(mockData[2].key);
});
}));
});
});
@@ -0,0 +1,57 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, Input, OnChanges } from '@angular/core';
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
import { CardViewUpdateService } from '../../services/card-view-update.service';
import { Observable } from 'rxjs/Observable';
import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces';
import { MatSelectChange } from '@angular/material';
@Component({
selector: 'adf-card-view-selectitem',
templateUrl: './card-view-selectitem.component.html',
styleUrls: ['./card-view-selectitem.component.scss']
})
export class CardViewSelectItemComponent implements OnChanges {
@Input() property: CardViewSelectItemModel<string>;
@Input() editable: boolean = false;
@Input() options$: Observable<CardViewSelectItemOption<string>[]>;
value: string;
constructor(private cardViewUpdateService: CardViewUpdateService) {}
ngOnChanges(): void {
this.value = this.property.value;
}
isEditable(): boolean {
return this.editable && this.property.editable;
}
getOptions(): Observable<CardViewSelectItemOption<string>[]> {
return this.options$ || this.property.options$;
}
onChange(event: MatSelectChange): void {
this.cardViewUpdateService.update(this.property, event.value);
this.property.value = event.value;
}
}
@@ -21,3 +21,5 @@ export * from './card-view-dateitem/card-view-dateitem.component';
export * from './card-view-item-dispatcher/card-view-item-dispatcher.component';
export * from './card-view-mapitem/card-view-mapitem.component';
export * from './card-view-textitem/card-view-textitem.component';
export * from './card-view-selectitem/card-view-selectitem.component';
export * from './card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
@@ -0,0 +1,27 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 { CardViewItemProperties } from './card-view-item-properties.interface';
export interface CardViewKeyValuePairsItemType {
name: string;
value: string;
}
export interface CardViewKeyValuePairsItemProperties extends CardViewItemProperties {
value: CardViewKeyValuePairsItemType[];
}
@@ -0,0 +1,29 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 { CardViewItemProperties } from './card-view-item-properties.interface';
import { Observable } from 'rxjs/Observable';
export interface CardViewSelectItemOption<T> {
label: string;
key: T;
}
export interface CardViewSelectItemProperties<T> extends CardViewItemProperties {
value: string;
options$: Observable<CardViewSelectItemOption<T>[]>;
}
@@ -22,3 +22,5 @@ export * from './card-view-textitem-properties.interface';
export * from './card-view-dateitem-properties.interface';
export * from './card-view-boolitem-properties.interface';
export * from './card-view-textitem-pipe-property.interface';
export * from './card-view-keyvaluepairsitem-properties.interface';
export * from './card-view-selectitem-properties.interface';
@@ -0,0 +1,33 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 { CardViewItem } from '../interfaces/card-view-item.interface';
import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service';
import { CardViewBaseItemModel } from './card-view-baseitem.model';
import { CardViewKeyValuePairsItemProperties } from '../interfaces/card-view.interfaces';
export class CardViewKeyValuePairsItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
type: string = 'keyvaluepairs';
constructor(obj: CardViewKeyValuePairsItemProperties) {
super(obj);
}
get displayValue() {
return this.value;
}
}
@@ -0,0 +1,46 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 { async } from '@angular/core/testing';
import { CardViewSelectItemModel } from './card-view-selectitem.model';
import { CardViewSelectItemProperties } from '../interfaces/card-view.interfaces';
import { of } from 'rxjs/observable/of';
describe('CardViewSelectItemModel', () => {
let properties: CardViewSelectItemProperties<string>;
const mockData = [{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }, { key: 'three', label: 'Three' }];
beforeEach(() => {
properties = {
label: 'Select box label',
value: 'two',
options$: of(mockData),
key: 'key',
editable: true
};
});
describe('displayValue', () => {
it('should return the value if it is present', async(() => {
const itemModel = new CardViewSelectItemModel(properties);
itemModel.displayValue.subscribe(value => {
expect(value).toBe(mockData[1].label);
});
}));
});
});
@@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 { CardViewItem } from '../interfaces/card-view-item.interface';
import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service';
import { CardViewBaseItemModel } from './card-view-baseitem.model';
import { CardViewSelectItemProperties, CardViewSelectItemOption } from '../interfaces/card-view.interfaces';
import { Observable } from 'rxjs/Observable';
import { switchMap } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
type: string = 'select';
options$: Observable<CardViewSelectItemOption<T>[]>;
constructor(obj: CardViewSelectItemProperties<T>) {
super(obj);
this.options$ = obj.options$;
}
get displayValue() {
return this.options$.pipe(
switchMap(options => {
const option = options.find(o => o.key === this.value);
return of(option ? option.label : '');
})
);
}
}
@@ -23,3 +23,5 @@ export * from './card-view-floatitem.model';
export * from './card-view-intitem.model';
export * from './card-view-mapitem.model';
export * from './card-view-textitem.model';
export * from './card-view-keyvaluepairs.model';
export * from './card-view-selectitem.model';
+3 -1
View File
@@ -20,7 +20,9 @@ export {
CardViewBoolItemComponent,
CardViewDateItemComponent,
CardViewMapItemComponent,
CardViewTextItemComponent
CardViewTextItemComponent,
CardViewSelectItemComponent,
CardViewKeyValuePairsItemComponent
} from './components/card-view.components';
export * from './interfaces/card-view.interfaces';
@@ -19,7 +19,9 @@ import { Injectable, Type } from '@angular/core';
import { CardViewDateItemComponent } from '../components/card-view-dateitem/card-view-dateitem.component';
import { CardViewMapItemComponent } from '../components/card-view-mapitem/card-view-mapitem.component';
import { CardViewTextItemComponent } from '../components/card-view-textitem/card-view-textitem.component';
import { CardViewSelectItemComponent } from '../components/card-view-selectitem/card-view-selectitem.component';
import { CardViewBoolItemComponent } from '../components/card-view-boolitem/card-view-boolitem.component';
import { CardViewKeyValuePairsItemComponent } from '../components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
import { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from '../../services/dynamic-component-mapper.service';
@Injectable()
@@ -29,11 +31,13 @@ export class CardItemTypeService extends DynamicComponentMapper {
protected types: { [key: string]: DynamicComponentResolveFunction } = {
'text': DynamicComponentResolver.fromType(CardViewTextItemComponent),
'select': DynamicComponentResolver.fromType(CardViewSelectItemComponent),
'int': DynamicComponentResolver.fromType(CardViewTextItemComponent),
'float': DynamicComponentResolver.fromType(CardViewTextItemComponent),
'date': DynamicComponentResolver.fromType(CardViewDateItemComponent),
'datetime': DynamicComponentResolver.fromType(CardViewDateItemComponent),
'bool': DynamicComponentResolver.fromType(CardViewBoolItemComponent),
'map': DynamicComponentResolver.fromType(CardViewMapItemComponent)
'map': DynamicComponentResolver.fromType(CardViewMapItemComponent),
'keyvaluepairs': DynamicComponentResolver.fromType(CardViewKeyValuePairsItemComponent)
};
}
+1 -1
View File
@@ -39,7 +39,7 @@ import { ToolbarModule } from './toolbar/toolbar.module';
import { UserInfoModule } from './userinfo/userinfo.module';
import { ViewerModule } from './viewer/viewer.module';
import { FormModule } from './form/form.module';
import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module';
import { SidenavLayoutModule } from './layout/layout.module';
import { CommentsModule } from './comments/comments.module';
import { ButtonsMenuModule } from './buttons-menu/buttons-menu.module';
import { TemplateModule } from './templates/template.module';
@@ -42,7 +42,6 @@
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="adf-datatable-body">
@@ -191,6 +190,9 @@
<ng-content select="adf-empty-list"></ng-content>
</div>
</div>
<div *ngFor="let row of fakeRows"
class="adf-datatable-row adf-datatable-row-empty-card">
</div>
</ng-container>
<div *ngIf="!loading && noPermission"
[class.adf-datatable-row]="display === 'list'"
@@ -29,13 +29,10 @@
display: flex;
width: 100%;
flex-direction: row;
justify-content: space-between;
&:after {
content: "";
flex: auto;
}
justify-content: space-evenly;
align-content: flex-start;
align-items: flex-start;
.adf-datatable-row {
transition: all 0.3s ease;
@@ -58,6 +55,14 @@
@include mat-overridable-elevation(2);
}
.adf-datatable-row-empty-card {
height: 0 !important;
padding-top: 0;
padding-bottom: 0;
margin-top: 0;
margin-bottom: 0;
}
.is-selected {
background: mat-color($primary, 100);
padding-bottom: 31px;
@@ -390,8 +395,6 @@
.ellipsis-cell {
.cell-container {
height: 100%;
display: inline-grid;
width: 100%;
}
.cell-container > * {
@@ -407,7 +410,7 @@
/* visible content */
.cell-value {
display: block;
position: relative;
position: absolute;
max-width: calc(100% - 2em);
white-space: nowrap;
overflow: hidden;
@@ -161,6 +161,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
isSelectAllChecked: boolean = false;
selection = new Array<DataRow>();
/** This array of fake rows fix the flex layout for the gallery view */
fakeRows = [];
private clickObserver: Observer<DataRowEvent>;
private click$: Observable<DataRowEvent>;
@@ -188,6 +191,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
})
);
}
this.datatableLayoutFix();
this.setTableSchema();
}
@@ -220,6 +224,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
if (this.isPropertyChanged(changes['sorting'])) {
this.setTableSorting(changes['sorting'].currentValue);
}
if (this.isPropertyChanged(changes['display'])) {
this.datatableLayoutFix();
}
}
ngDoCheck() {
@@ -654,4 +662,16 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
this.dataRowsChanged = null;
}
}
datatableLayoutFix() {
const maxGalleryRows = 25;
if (this.display === 'gallery') {
for (let i = 0; i < maxGalleryRows; i++) {
this.fakeRows.push('');
}
} else {
this.fakeRows = [];
}
}
}
@@ -1,5 +1,5 @@
<div class="adf-amount-widget__container adf-amount-widget {{field.className}}" [class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
<mat-form-field floatPlaceholder="never" class="adf-amount-widget__input">
<mat-form-field class="adf-amount-widget__input">
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<span matPrefix class="adf-amount-widget__prefix-spacing"> {{currency }}</span>
<input matInput
@@ -7,13 +7,12 @@
type="text"
[id]="field.id"
[required]="isRequired()"
[placeholder]="field.placeholder"
[value]="field.value"
[(ngModel)]="field.value"
(ngModelChange)="onFieldChanged(field)"
[disabled]="field.readOnly"
placeholder="{{field.placeholder}}">
[disabled]="field.readOnly">
</mat-form-field>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>
@@ -5,20 +5,27 @@
width: 100%;
vertical-align: baseline !important;
.mat-input-element {
margin-left: 11px;
}
.mat-input-prefix {
position: absolute;
margin-top: 58px;
}
.mat-input-placeholder {
margin-top: 5px;
display: none;
}
.mat-form-field-flex{
position: relative;
padding-top: 18.5px;
}
.mat-form-field-infix{
position: static;
padding-top: 19px;
}
.adf-label{
position: absolute;
top: 18.5px;
left:0;
}
}
&-amount-widget__container {
@@ -8,32 +8,29 @@
display: none;
}
.adf {
&-field-list {
padding: 0;
list-style-type: none;
width: 100%;
height: 100%;
.container-widget__header-text {
border-bottom: 1px solid rgba(0, 0, 0, 0.87);
padding-bottom: 10px;
cursor: default;
user-select: none;
-webkit-user-select: none;
/* Chrome/Safari/Opera */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE/Edge */
-webkit-touch-callout: none;
/* iOS Safari */
&.collapsible {
cursor: pointer;
}
}
.container-widget__header-text {
border-bottom: 1px solid rgba(0, 0, 0, 0.87);
margin-left: 10px;
margin-right: 10px;
cursor: default;
user-select: none;
-webkit-user-select: none;
/* Chrome/Safari/Opera */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE/Edge */
-webkit-touch-callout: none;
/* iOS Safari */
&.collapsible {
cursor: pointer;
}
}
.adf-field-list {
padding: 0;
list-style-type: none;
width: 100%;
height: 100%;
}
container-widget {
@@ -50,7 +47,7 @@
padding-left: 1%;
padding-right: 1%;
}
@media screen and ($mat-small) {
.grid-list-item {
flex: 1 0 100%;
@@ -17,7 +17,13 @@
[class.adf-dynamic-table-widget__row-selected]="row.selected" (keyup)="onKeyPressed($event, row)">
<td *ngFor="let column of content.visibleColumns"
(click)="onRowClicked(row)">
{{ getCellValue(row, column) }}
<span *ngIf="column.type !== 'Boolean' else checkbox">
{{ getCellValue(row, column) }}
</span>
<ng-template #checkbox>
<mat-checkbox disabled [checked]="getCellValue(row, column)">
</mat-checkbox>
</ng-template>
</td>
</tr>
</tbody>
@@ -0,0 +1,20 @@
<div>
<mat-form-field class="adf-date-editor">
<label [attr.for]="column.id">{{column.name}} {{DATE_FORMAT}}</label>
<input matInput
[matDatetimepicker]="datetimePicker"
[(ngModel)]="value"
[id]="column.id"
[required]="column.required"
[disabled]="!column.editable"
(focusout)="onDateChanged($event.srcElement.value)"
(dateChange)="onDateChanged($event)">
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker"
*ngIf="column.editable"
class="adf-date-editor-button"></mat-datetimepicker-toggle>
</mat-form-field>
<mat-datetimepicker #datetimePicker
type="datetime"
openOnFocus="true"
timeInterval="5"></mat-datetimepicker>
</div>
@@ -0,0 +1,10 @@
.adf {
&-date-editor {
width: 100%;
}
&-date-editor-button {
position: relative;
top: 25px;
}
}
@@ -0,0 +1,89 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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 moment from 'moment-es6';
import { FormFieldModel, FormModel } from '../../../index';
import { DynamicTableColumn } from './../../dynamic-table-column.model';
import { DynamicTableRow } from './../../dynamic-table-row.model';
import { DynamicTableModel } from './../../dynamic-table.widget.model';
import { DateTimeEditorComponent } from './datetime.editor';
import { setupTestBed } from '../../../../../../testing/setupTestBed';
import { CoreModule } from '../../../../../../core.module';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('DateTimeEditorComponent', () => {
let component: DateTimeEditorComponent;
let fixture: ComponentFixture<DateTimeEditorComponent>;
let row: DynamicTableRow;
let column: DynamicTableColumn;
let table: DynamicTableModel;
setupTestBed({
imports: [
NoopAnimationsModule,
CoreModule.forRoot()
]
});
beforeEach(() => {
fixture = TestBed.createComponent(DateTimeEditorComponent);
component = fixture.componentInstance;
row = <DynamicTableRow> { value: { date: '1879-03-14T00:00:00.000Z' } };
column = <DynamicTableColumn> { id: 'datetime', type: 'Datetime' };
const field = new FormFieldModel(new FormModel());
table = new DynamicTableModel(field, null);
table.rows.push(row);
table.columns.push(column);
component.table = table;
component.row = row;
component.column = column;
});
it('should create instance of DateTimeEditorComponent', () => {
expect(fixture.componentInstance instanceof DateTimeEditorComponent).toBe(true, 'should create DateTimeEditorComponent');
});
it('should update fow value on change', () => {
component.ngOnInit();
let newDate = moment('22-6-2018 04:20 AM', 'D-M-YYYY hh:mm A');
component.onDateChanged(newDate);
expect(moment(row.value[column.id]).isSame(newDate)).toBeTruthy();
});
it('should update row value upon user input', () => {
const input = '22-6-2018 04:20 AM';
component.ngOnInit();
component.onDateChanged(input);
let actual = row.value[column.id];
expect(actual).toBe('22-6-2018 04:20 AM');
});
it('should flush value on user input', () => {
spyOn(table, 'flushValue').and.callThrough();
const input = '22-6-2018 04:20 AM';
component.ngOnInit();
component.onDateChanged(input);
expect(table.flushValue).toHaveBeenCalled();
});
});
@@ -0,0 +1,92 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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.
*/
/* tslint:disable:component-selector */
import { UserPreferencesService } from '../../../../../../services/user-preferences.service';
import { MomentDateAdapter } from '../../../../../../utils/momentDateAdapter';
import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model';
import { Component, Input, OnInit } from '@angular/core';
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material';
import moment from 'moment-es6';
import { Moment } from 'moment';
import { DynamicTableColumn } from './../../dynamic-table-column.model';
import { DynamicTableRow } from './../../dynamic-table-row.model';
import { DynamicTableModel } from './../../dynamic-table.widget.model';
import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core';
import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment';
@Component({
selector: 'adf-datetime-editor',
templateUrl: './datetime.editor.html',
providers: [
{ provide: DateAdapter, useClass: MomentDateAdapter },
{ provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS },
{ provide: DatetimeAdapter, useClass: MomentDatetimeAdapter },
{ provide: MAT_DATETIME_FORMATS, useValue: MAT_MOMENT_DATETIME_FORMATS }
],
styleUrls: ['./datetime.editor.scss']
})
export class DateTimeEditorComponent implements OnInit {
DATE_FORMAT: string = 'D-M-YYYY hh:mm A';
value: any;
@Input()
table: DynamicTableModel;
@Input()
row: DynamicTableRow;
@Input()
column: DynamicTableColumn;
minDate: Moment;
maxDate: Moment;
constructor(private dateAdapter: DateAdapter<Moment>,
private preferences: UserPreferencesService) {
}
ngOnInit() {
this.preferences.locale$.subscribe((locale) => {
this.dateAdapter.setLocale(locale);
});
let momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
momentDateAdapter.overrideDisplyaFormat = this.DATE_FORMAT;
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT);
}
onDateChanged(newDateValue) {
if (newDateValue && newDateValue.value) {
const newValue = moment(newDateValue.value, this.DATE_FORMAT);
this.row.value[this.column.id] = newDateValue.value.format(this.DATE_FORMAT);
this.value = newValue;
this.table.flushValue();
} else if (newDateValue) {
const newValue = moment(newDateValue, this.DATE_FORMAT);
this.value = newValue;
this.row.value[this.column.id] = newDateValue;
this.table.flushValue();
} else {
this.row.value[this.column.id] = '';
}
}
}
@@ -16,7 +16,13 @@
[column]="column">
</adf-date-editor>
</div>
<div *ngSwitchCase="'Datetime'">
<adf-datetime-editor
[table]="table"
[row]="row"
[column]="column">
</adf-datetime-editor>
</div>
<div *ngSwitchCase="'Boolean'">
<adf-boolean-editor
[table]="table"
+4 -1
View File
@@ -28,6 +28,7 @@ import { DropdownWidgetComponent } from './dropdown/dropdown.widget';
import { DynamicTableWidgetComponent } from './dynamic-table/dynamic-table.widget';
import { BooleanEditorComponent } from './dynamic-table/editors/boolean/boolean.editor';
import { DateEditorComponent } from './dynamic-table/editors/date/date.editor';
import { DateTimeEditorComponent } from './dynamic-table/editors/datetime/datetime.editor';
import { DropdownEditorComponent } from './dynamic-table/editors/dropdown/dropdown.editor';
import { RowEditorComponent } from './dynamic-table/editors/row.editor';
import { TextEditorComponent } from './dynamic-table/editors/text/text.editor';
@@ -80,6 +81,7 @@ export * from './dynamic-table/editors/date/date.editor';
export * from './dynamic-table/editors/dropdown/dropdown.editor';
export * from './dynamic-table/editors/boolean/boolean.editor';
export * from './dynamic-table/editors/text/text.editor';
export * from './dynamic-table/editors/datetime/datetime.editor';
export * from './text/text-mask.component';
export const WIDGET_DIRECTIVES: any[] = [
@@ -108,7 +110,8 @@ export const WIDGET_DIRECTIVES: any[] = [
RowEditorComponent,
ErrorWidgetComponent,
DocumentWidgetComponent,
DateTimeWidgetComponent
DateTimeWidgetComponent,
DateTimeEditorComponent
];
export const MASK_DIRECTIVE: any[] = [
+7 -1
View File
@@ -133,11 +133,17 @@
"APPLY": "APPLY",
"NOT_VALID": "http(s)://host|ip:port(/path) not recognized, try a different URL.",
"REDIRECT": "Redirect Uri",
"REDIRECT_LOGOUT": "Redirect Uri Logout",
"SILENT": "Silent Login",
"SCOPE": "Scope",
"CLIENT": "ClientId"
},
"CARDVIEW": {
"KEYVALUEPAIRS": {
"ADD": "Add new",
"NAME": "Name",
"VALUE": "Value"
},
"VALIDATORS": {
"FLOAT_VALIDATION_ERROR": "Use a number format",
"INT_VALIDATION_ERROR": "Use an integer format"
@@ -301,7 +307,7 @@
"DESCRIPTION": "You're not allowed access to this resource on the server.",
"SECONDARY_BUTTON": {
"TEXT": "Report issue",
"URL": ""
"URL": "report-issue"
},
"RETURN_BUTTON": {
"TEXT": "Back to home",
+1 -1
View File
@@ -31,7 +31,7 @@ export * from './collapsable/index';
export * from './card-view/index';
export * from './app-config/index';
export * from './form/index';
export * from './sidenav-layout/index';
export * from './layout/index';
export * from './comments/index';
export * from './buttons-menu/index';
export * from './sorting-picker/index';
@@ -0,0 +1,11 @@
<mat-toolbar color="{{color}}">
<button *ngIf="showSidenavToggle" class="mat-icon-button adf-menu-icon" mat-icon-button (click)="toggleMenu()">
<mat-icon class="mat-icon material-icon" role="img" aria-hidden="true">menu</mat-icon>
</button>
<img src="{{logo}}" class="adf-app-logo">
<span fxFlex="1 1 auto" fxShow fxHide.lt-sm="true" class="adf-app-title">{{title}}</span>
<ng-content></ng-content>
</mat-toolbar>
@@ -0,0 +1,54 @@
@mixin adf-header-layout-theme($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$background: map-get($theme, background);
adf-layout-header .mat-toolbar-single-row {
color: mat-color($primary, default-contrast) !important;
position: relative;
padding: 0 24px 0 0;
.adf-menu-icon {
position: relative;
margin-left: 14px;
}
.adf-app-logo {
position: relative;
height: 28px;
width: 28px;
margin-right: 6px;
margin-left: 3px;
}
.adf-header-delimiter {
height: 24px;
width: 2px;
background-color: mat-color($primary, default-contrast);
margin-right: 16px;
}
.adf-app-layout-user-profile {
margin-right: 16px;
}
.adf-userinfo-container {
flex-direction: row;
padding: 0;
}
.adf-userinfo-name{
padding-right: 8px;
}
@media screen and ($mat-xsmall) {
.adf-app-logo,
.adf-app-title {
display: none;
}
}
}
}
@@ -0,0 +1,124 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, async } from '@angular/core/testing';
import { HeaderLayoutComponent } from './header.component';
import { setupTestBed } from '../../../testing/setupTestBed';
import { CoreTestingModule } from '../../../testing/core.testing.module';
import { By } from '@angular/platform-browser';
import { LayoutModule } from '../..';
import { Component } from '@angular/core';
import { MaterialModule } from './../../../material.module';
describe('HeaderLayoutComponent', () => {
let fixture: ComponentFixture<HeaderLayoutComponent>;
let component: HeaderLayoutComponent;
describe('Input parameters', () => {
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(() => {
fixture = TestBed.createComponent(HeaderLayoutComponent);
component = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
});
it('should create instance of HeaderLayoutComponent', () => {
expect(fixture.componentInstance instanceof HeaderLayoutComponent).toBe(true, 'should create HeaderLayoutComponent');
});
it('title element should been displayed', () => {
const titleElement = fixture.debugElement.query(By.css('.adf-app-title'));
expect(titleElement === null).toBeFalsy();
});
it('should show TEST TITLE', () => {
component.title = 'TEST TITLE';
fixture.detectChanges();
const titleElement = fixture.nativeElement.querySelector('.adf-app-title');
expect(titleElement.innerText).toEqual('TEST TITLE');
});
it('color attribute should be present on mat-toolbar', () => {
component.color = 'primary';
fixture.detectChanges();
const toolbar = fixture.nativeElement.querySelector('mat-toolbar');
expect(toolbar.getAttribute('ng-reflect-color') === null).toBeFalsy();
expect(toolbar.getAttribute('ng-reflect-color')).toEqual('primary');
});
it('should display the img element with the expected src if a logo path is set', () => {
component.logo = 'logo.png';
fixture.detectChanges();
const logo = fixture.nativeElement.querySelector('.adf-app-logo');
const src = logo.getAttribute('src');
expect(logo === null).toBeFalsy();
expect(src).toEqual('logo.png');
});
it('test click on sidenav button', () => {
component.showSidenavToggle = true;
fixture.detectChanges();
spyOn(component.clicked, 'emit');
const button = fixture.nativeElement.querySelector('.adf-menu-icon');
button.dispatchEvent(new Event('click'));
fixture.detectChanges();
expect(component.clicked.emit).toHaveBeenCalledWith(true);
});
it('if showSidenavToggle is false the button menu should not be displayed', () => {
component.showSidenavToggle = false;
fixture.detectChanges();
const button = fixture.nativeElement.querySelector('.adf-menu-icon');
expect(button === null).toBeTruthy();
});
});
describe('Template tranclusion', () => {
@Component({
selector: 'adf-test-layout-header',
template: `<adf-layout-header title="test" color="primary"><p>Test text<p></adf-layout-header>`
})
class HeaderLayoutTesterComponent {}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [HeaderLayoutTesterComponent],
imports: [ LayoutModule, MaterialModule ]
})
.compileComponents();
}));
it('should transclude the provided nodes into the component', () => {
const hostFixture = TestBed.createComponent(HeaderLayoutTesterComponent);
hostFixture.detectChanges();
const innerText = hostFixture.nativeElement.querySelector('mat-toolbar>p').innerText;
expect(innerText).toEqual('Test text');
});
});
});
@@ -0,0 +1,36 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* 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, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'adf-layout-header',
templateUrl: './header.component.html',
encapsulation: ViewEncapsulation.None,
host: { class: 'adf-layout-header' }
})
export class HeaderLayoutComponent {
@Input() title: string;
@Input() logo: string = './assets/images/logo.png';
@Input() color: string;
@Input() showSidenavToggle: boolean = true;
@Output() clicked = new EventEmitter<any>();
toggleMenu() {
this.clicked.emit(true);
}
}

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