Add ThemeImages API

- Add ThemeImages Entity
- Add ThemeImages DataProvider
- Add ThemeImage Service
-- Retrieves the list of images for a given theme
- Add ThemeImagesFinder
-- looks up for images inside a given path
-- Add configuration to specify theme image paths
-- Add configuration to specify supported image types
- Add unit tests
- Add theme-images angular facade
- Update base metadata resolver to load theme images
-- Move UserPreferences loading ot base metadata resolver
-- Loaded theme images after user preference and config loading
--- uses default_theme, if user preferences weren't loaded
-- Add jasmine tests
- Add scrm-image component
-- Add jasmine tests
- Replace usages of svg-icon with image component
-- Fix jasmine tests
- Adjust UserPreference interface to match the current data
This commit is contained in:
Clemente Raposo 2020-04-03 15:23:54 +01:00 committed by Dillon-Brown
parent 3f133b64db
commit 895ec5b4f7
77 changed files with 2651 additions and 1496 deletions

View file

@ -28,6 +28,8 @@ services:
$menuItemMap: '%legacy.menu_item_map%'
$legacyAssetPaths: '%legacy.asset_paths%'
$exposedUserPreferences: '%legacy.exposed_user_preferences%'
$themeImagePaths: '%themes.image_paths%'
$themeImageSupportedTypes: '%themes.image_supported_types%'
_instanceof:
App\Service\ProcessHandlerInterface:
tags: ['app.process.handler']

View file

@ -5,3 +5,4 @@ parameters:
forgotpasswordON: true
languages: true
default_module: true
default_theme: true

View file

@ -0,0 +1,9 @@
parameters:
themes.image_paths:
# Order matters, lower ones will override the above ones
- 'legacy/themes/default/images'
- 'legacy/custom/themes/default/images'
- 'core/app/themes/default/images'
- 'legacy/themes/<theme>/images'
- 'legacy/custom/themes/<theme>/images'
- 'core/app/themes/<theme>/images'

View file

@ -0,0 +1,8 @@
parameters:
themes.image_supported_types:
# Order matters, top most ones will have priority over the lower ones
- 'svg'
- 'png'
- 'jpg'
- 'jpeg'
- 'gif'

View file

@ -1,10 +1,9 @@
import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {ClassicViewUiComponent} from '@components/classic-view/classic-view.component';
import {ClassicViewResolver} from "@services/classic-view/classic-view.resolver";
import {ClassicViewResolver} from '@services/classic-view/classic-view.resolver';
import {BaseMetadataResolver} from '@services/metadata/base-metadata.resolver';
import {UserPreferenceResolver} from '@base/facades/user-preference/user-preference.resolver';
import {AuthGuard} from '../services/auth/auth-guard.service';
import {AuthGuard} from '@services/auth/auth-guard.service';
import {ListComponent} from '@views/list/list.component';
const routes: Routes = [
@ -12,8 +11,7 @@ const routes: Routes = [
path: 'Listview',
component: ListComponent,
resolve: {
view: BaseMetadataResolver,
userPreference: UserPreferenceResolver
view: BaseMetadataResolver
}
},
{
@ -25,6 +23,7 @@ const routes: Routes = [
data: {
load: {
navigation: false,
preferences: false,
languageStrings: ['appStrings']
}
}
@ -34,7 +33,6 @@ const routes: Routes = [
loadChildren: () => import('../components/home/home.module').then(m => m.HomeUiModule),
resolve: {
metadata: BaseMetadataResolver,
userPreference: UserPreferenceResolver
}
},
{
@ -45,7 +43,6 @@ const routes: Routes = [
resolve: {
metadata: BaseMetadataResolver,
view: ClassicViewResolver,
userPreference: UserPreferenceResolver
},
data: {
reuseRoute: false
@ -59,7 +56,6 @@ const routes: Routes = [
resolve: {
metadata: BaseMetadataResolver,
view: ClassicViewResolver,
userPreference: UserPreferenceResolver
},
data: {
reuseRoute: false
@ -73,7 +69,6 @@ const routes: Routes = [
resolve: {
metadata: BaseMetadataResolver,
view: ClassicViewResolver,
userPreference: UserPreferenceResolver
},
data: {
reuseRoute: false

View file

@ -34,6 +34,7 @@ import {FetchPolicy} from 'apollo-client/core/watchQueryOptions';
import {FullPageSpinnerComponent} from '@components/full-page-spinner/full-page-spinner.component';
import {RouteReuseStrategy} from '@angular/router';
import {AppRouteReuseStrategy} from './app-router-reuse-strategy';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [
@ -60,6 +61,7 @@ import {AppRouteReuseStrategy} from './app-router-reuse-strategy';
ListheaderUiModule,
ListcontainerUiModule,
ColumnchooserUiModule,
ImageModule,
BrowserAnimationsModule,
NgbModule
],

View file

@ -1,145 +1,145 @@
<!-- Start of action bar section -->
<div class="global-action-bar row pb-2 pt-2 mr-0">
<div class="col-6">
<form novalidate class="global-search ng-untouched ng-pristine ng-valid">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search..." aria-label="Search">
<div class="input-group-append">
<button class="btn btn-default search-button" type="submit" aria-label="Search">
<svg-icon class="search-icon sicon" src="public/themes/suite8/images/search.svg"></svg-icon>
</button>
</div>
</div>
</form>
</div>
<div class="col justify-content-right">
<div class="action-group">
<div class="action-alert dropdown">
<button class="alerts-button dropdown-toggle" type="button" aria-label="Toggle Alerts">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/alert.svg"></svg-icon>
</button>
<div class="dropdown-menu dropdown-menu-right">
<li class="alert-list-item">
<h4>ASSIGNED TASK</h4>
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/alert.svg">
</svg-icon>
Create Wireframe
</a>
</li>
<li class="alert-list-item">
<h4>DUE DATE EXPIRED</h4>
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/alert.svg">
</svg-icon>
Call John Doe to discuss...
</a>
</li>
<li class="alert-list-item">
<h4>ASSIGNED LEAD</h4>
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/alert.svg">
</svg-icon>
John Doe
</a>
</li>
<li class="alert-list-item">
<h4>NEW CASE</h4>
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/alert.svg">
</svg-icon>
Johnston &amp; Co
</a>
</li>
<li>
<a class="action-view-all" href="#">
View All >
</a>
</li>
</div>
</div>
<div class="action-favourite dropdown">
<button class="favourites-button dropdown-toggle" type="button" aria-label="Toggle Favourites">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/star.svg"></svg-icon>
</button>
<div class="dropdown-menu dropdown-menu-right">
<li class="favourite-list-item">
<h4>JOHN DOE</h4>
<a href="redirect-to-the-record">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/star.svg">
</svg-icon>
This is a favourited record
</a>
</li>
<li class="favourite-list-item">
<h4>JOHNSTON & CO</h4>
<a href="redirect-to-the-record">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/star.svg">
</svg-icon>
This is a favourited record
</a>
</li>
<li class="favourite-list-item">
<h4>CREATE WIREFRAME</h4>
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/star.svg">
</svg-icon>
This is a favourited record
</a>
</li>
<li>
<a class="action-view-all" href="#">
View All >
</a>
</li>
</div>
</div>
<div class="action-new dropdown">
<button class="quickcreate-button dropdown-toggle" type="button" aria-label="Quick Create">
NEW
</button>
<div class="dropdown-menu dropdown-menu-right">
<li class="new-list-item">
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/plus.svg"></svg-icon>
Accounts
</a>
</li>
<li class="new-list-item">
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/plus.svg"></svg-icon>
Contacts
</a>
</li>
<li class="new-list-item">
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/plus.svg"></svg-icon>
Leads
</a>
</li>
<li class="new-list-item">
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/plus.svg"></svg-icon>
Opportunities
</a>
</li>
<li class="new-list-item">
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/plus.svg"></svg-icon>
Quotes
</a>
</li>
<li class="new-list-item">
<a href="#">
<svg-icon class="action-btn-icon" src="public/themes/suite8/images/plus.svg"></svg-icon>
Contracts
</a>
</li>
</div>
</div>
<div class="col-6">
<form novalidate class="global-search ng-untouched ng-pristine ng-valid">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search..." aria-label="Search">
<div class="input-group-append">
<button class="btn btn-default search-button" type="submit" aria-label="Search">
<scrm-image class="search-icon sicon" image="search"></scrm-image>
</button>
</div>
</div>
</form>
</div>
<div class="col justify-content-right">
<div class="action-group">
<div class="action-alert dropdown">
<button class="alerts-button dropdown-toggle" type="button" aria-label="Toggle Alerts">
<scrm-image class="action-btn-icon" image="alert"></scrm-image>
</button>
<div class="dropdown-menu dropdown-menu-right">
<li class="alert-list-item">
<h4>ASSIGNED TASK</h4>
<a href="#">
<scrm-image class="action-btn-icon" image="alert">
</scrm-image>
Create Wireframe
</a>
</li>
<li class="alert-list-item">
<h4>DUE DATE EXPIRED</h4>
<a href="#">
<scrm-image class="action-btn-icon" image="alert">
</scrm-image>
Call John Doe to discuss...
</a>
</li>
<li class="alert-list-item">
<h4>ASSIGNED LEAD</h4>
<a href="#">
<scrm-image class="action-btn-icon" image="alert">
</scrm-image>
John Doe
</a>
</li>
<li class="alert-list-item">
<h4>NEW CASE</h4>
<a href="#">
<scrm-image class="action-btn-icon" image="alert">
</scrm-image>
Johnston &amp; Co
</a>
</li>
<li>
<a class="action-view-all" href="#">
View All >
</a>
</li>
</div>
</div>
<div class="action-favourite dropdown">
<button class="favourites-button dropdown-toggle" type="button" aria-label="Toggle Favourites">
<scrm-image class="action-btn-icon" image="star"></scrm-image>
</button>
<div class="dropdown-menu dropdown-menu-right">
<li class="favourite-list-item">
<h4>JOHN DOE</h4>
<a href="redirect-to-the-record">
<scrm-image class="action-btn-icon" image="star">
</scrm-image>
This is a favourited record
</a>
</li>
<li class="favourite-list-item">
<h4>JOHNSTON & CO</h4>
<a href="redirect-to-the-record">
<scrm-image class="action-btn-icon" image="star">
</scrm-image>
This is a favourited record
</a>
</li>
<li class="favourite-list-item">
<h4>CREATE WIREFRAME</h4>
<a href="#">
<scrm-image class="action-btn-icon" image="star">
</scrm-image>
This is a favourited record
</a>
</li>
<li>
<a class="action-view-all" href="#">
View All >
</a>
</li>
</div>
</div>
<div class="action-new dropdown">
<button class="quickcreate-button dropdown-toggle" type="button" aria-label="Quick Create">
NEW
</button>
<div class="dropdown-menu dropdown-menu-right">
<li class="new-list-item">
<a href="#">
<scrm-image class="action-btn-icon" image="plus"></scrm-image>
Accounts
</a>
</li>
<li class="new-list-item">
<a href="#">
<scrm-image class="action-btn-icon" image="plus"></scrm-image>
Contacts
</a>
</li>
<li class="new-list-item">
<a href="#">
<scrm-image class="action-btn-icon" image="plus"></scrm-image>
Leads
</a>
</li>
<li class="new-list-item">
<a href="#">
<scrm-image class="action-btn-icon" image="plus"></scrm-image>
Opportunities
</a>
</li>
<li class="new-list-item">
<a href="#">
<scrm-image class="action-btn-icon" image="plus"></scrm-image>
Quotes
</a>
</li>
<li class="new-list-item">
<a href="#">
<scrm-image class="action-btn-icon" image="plus"></scrm-image>
Contracts
</a>
</li>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- End of action bar section -->

View file

@ -1,9 +1,13 @@
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {ActionBarUiComponent} from './action-bar.component';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('ActionBarUiComponent', () => {
let component: ActionBarUiComponent;
@ -13,6 +17,13 @@ describe('ActionBarUiComponent', () => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterTestingModule, HttpClientTestingModule],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [ActionBarUiComponent]
})
.compileComponents();
@ -24,7 +35,7 @@ describe('ActionBarUiComponent', () => {
fixture.detectChanges();
});
it(`should create`, async(inject([HttpTestingController],
it('should create', async(inject([HttpTestingController],
(httpClient: HttpTestingController) => {
expect(component).toBeTruthy();
})));

View file

@ -2,22 +2,22 @@ import {Component, OnInit} from '@angular/core';
import {ActionBarModel} from './action-bar-model';
@Component({
selector: 'scrm-action-bar-ui',
templateUrl: './action-bar.component.html',
styleUrls: []
selector: 'scrm-action-bar-ui',
templateUrl: './action-bar.component.html',
styleUrls: []
})
export class ActionBarUiComponent implements OnInit {
actionBar: ActionBarModel = {
createLinks: [],
favoriteRecords: [],
};
actionBar: ActionBarModel = {
createLinks: [],
favoriteRecords: [],
};
constructor() {
}
constructor() {
}
ngOnInit(): void {
ngOnInit(): void {
}
}
}

View file

@ -1,18 +1,18 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {ActionBarUiComponent} from './action-bar.component';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [ActionBarUiComponent],
exports: [ActionBarUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ActionBarUiComponent),
AngularSvgIconModule
]
declarations: [ActionBarUiComponent],
exports: [ActionBarUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ActionBarUiComponent),
ImageModule
]
})
export class ActionBarUiModule {
}

View file

@ -1,52 +1,49 @@
<div class="card-body">
<div class="chart-create">
<button class="quickcreate-button" type="button">
<svg-icon class="sicon widget-button-icon"
src="public/themes/suite8/images/plus.svg"></svg-icon>
New Chart
</button>
</div>
<div class="container">
<div class="row">
<div class="pipeline-chart col-xs">
<div class="accordion">
<button class="widget-transparent-button btn-block dropdown-toggle"
type="button"
data-toggle="collapse" data-target=".pipeline-chart-content">
Pipeline By Sales Stage
</button>
<div class="collapse show pipeline-chart-content"
data-parent=".pipeline-chart">
<img alt="Pipeline Chart" class="w-100"
src="public/themes/suite8/images/pipeline_chart.png">
</div>
</div>
</div>
<div class="donut-chart col-xs">
<div class="accordion">
<button class="widget-transparent-button btn-block dropdown-toggle"
type="button"
data-toggle="collapse" data-target=".donut-chart-content">
Display as Donut Chart
</button>
<div class="collapse show donut-chart-content" data-parent=".donut-chart">
<img alt="Donut Chart" class="w-100"
src="public/themes/suite8/images/donut_chart.png">
</div>
</div>
</div>
<div class="chart-create">
<button class="quickcreate-button" type="button">
<scrm-image class="sicon widget-button-icon" image="plus"></scrm-image>
New Chart
</button>
</div>
<div class="container">
<div class="row">
<div class="pipeline-chart col-xs">
<div class="accordion">
<button class="widget-transparent-button btn-block dropdown-toggle"
type="button"
data-toggle="collapse" data-target=".pipeline-chart-content">
Pipeline By Sales Stage
</button>
<div class="collapse show pipeline-chart-content"
data-parent=".pipeline-chart">
<img alt="Pipeline Chart" class="w-100"
src="public/themes/suite8/images/pipeline_chart.png">
</div>
</div>
</div>
<div class="donut-chart col-xs">
<div class="accordion">
<button class="widget-transparent-button btn-block dropdown-toggle"
type="button"
data-toggle="collapse" data-target=".donut-chart-content">
Display as Donut Chart
</button>
<div class="collapse show donut-chart-content" data-parent=".donut-chart">
<img alt="Donut Chart" class="w-100"
src="public/themes/suite8/images/donut_chart.png">
</div>
</div>
</div>
</div>
</div>
<div class="chart-create">
<button class="quickcreate-button" type="button">
<scrm-image class="sicon widget-button-icon" image="edit"></scrm-image>
Edit
</button>
<button class="remove-button" type="button">
<scrm-image class="sicon widget-button-icon" image="cross"></scrm-image>
Remove
</button>
</div>
</div>
<div class="chart-create">
<button class="quickcreate-button" type="button">
<svg-icon class="sicon widget-button-icon"
src="public/themes/suite8/images/edit.svg"></svg-icon>
Edit
</button>
<button class="remove-button" type="button">
<svg-icon class="sicon widget-button-icon" src="public/themes/suite8/images/cross.svg">
</svg-icon>
Remove
</button>
</div>
</div>

View file

@ -1,4 +1,4 @@
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {RouterTestingModule} from '@angular/router/testing';
@ -6,6 +6,10 @@ import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {ChartUiComponent} from './chart.component';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('ChartComponent', () => {
let component: ChartUiComponent;
@ -15,6 +19,13 @@ describe('ChartComponent', () => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterTestingModule, HttpClientTestingModule],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [ChartUiComponent]
})
.compileComponents();
@ -26,7 +37,7 @@ describe('ChartComponent', () => {
fixture.detectChanges();
});
it(`should create`, async(inject([HttpTestingController],
it('should create', async(inject([HttpTestingController],
(httpClient: HttpTestingController) => {
expect(component).toBeTruthy();
})));

View file

@ -1,4 +1,4 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'scrm-chart-ui',

View file

@ -1,19 +1,18 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {ChartUiComponent} from './chart.component';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [ChartUiComponent],
exports: [ChartUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ChartUiComponent),
AngularSvgIconModule
]
declarations: [ChartUiComponent],
exports: [ChartUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ChartUiComponent),
ImageModule
]
})
export class ChartUiModule {
}

View file

@ -2,84 +2,85 @@
<ng-template #columnchoosermodal let-modal>
<!-- Start of modal header section -->
<!-- Start of modal header section -->
<div class="column-chooser-modal">
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close Column Chooser Modal"
(click)="modal.dismiss('Cross click')">
<svg-icon class="sicon" src="public/themes/suite8/images/close_modal.svg"></svg-icon>
</button>
</div>
<!-- End of modal header section -->
<!-- Start of modal body section -->
<div class="modal-body">
<div class="column-chooser-container">
<h2 class="column-chooser-title">Displayed</h2>
<div
cdkDropList
#displayedList="cdkDropList"
[cdkDropListData]="displayed"
[cdkDropListConnectedTo]="[hiddenList]"
class="column-chooser-list"
(cdkDropListDropped)="drop($event)">
<div class="column-chooser-item column-displayed" *ngFor="let item of displayed" cdkDrag>{{item}}</div>
</div>
</div>
<div class="column-chooser-container">
<h2 class="column-chooser-title">Hidden</h2>
<div
cdkDropList
#hiddenList="cdkDropList"
[cdkDropListData]="hidden"
[cdkDropListConnectedTo]="[displayedList]"
class="column-chooser-list"
(cdkDropListDropped)="drop($event)">
<div class="column-chooser-item column-hidden" *ngFor="let item of hidden" cdkDrag>{{item}}</div>
</div>
</div>
</div>
<!-- End of modal body section -->
<!-- Start of modal footer section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-6">
</div>
<div class="col-lg-6">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal"
<div class="column-chooser-modal">
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close Column Chooser Modal"
(click)="modal.dismiss('Cross click')">
Close
<scrm-image class="sicon" image="close_modal"></scrm-image>
</button>
<button type="button" class="btn modal-button-save">
Save Changes
</button>
</div>
</div>
</div>
</div>
</div>
<!-- End of modal footer section -->
<!-- End of modal header section -->
<!-- Start of modal body section -->
<div class="modal-body">
<div class="column-chooser-container">
<h2 class="column-chooser-title">Displayed</h2>
<div
cdkDropList
#displayedList="cdkDropList"
[cdkDropListData]="displayed"
[cdkDropListConnectedTo]="[hiddenList]"
class="column-chooser-list"
(cdkDropListDropped)="drop($event)">
<div class="column-chooser-item column-displayed" *ngFor="let item of displayed"
cdkDrag>{{item}}</div>
</div>
</div>
<div class="column-chooser-container">
<h2 class="column-chooser-title">Hidden</h2>
<div
cdkDropList
#hiddenList="cdkDropList"
[cdkDropListData]="hidden"
[cdkDropListConnectedTo]="[displayedList]"
class="column-chooser-list"
(cdkDropListDropped)="drop($event)">
<div class="column-chooser-item column-hidden" *ngFor="let item of hidden" cdkDrag>{{item}}</div>
</div>
</div>
</div>
<!-- End of modal body section -->
<!-- Start of modal footer section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-6">
</div>
<div class="col-lg-6">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal"
(click)="modal.dismiss('Cross click')">
Close
</button>
<button type="button" class="btn modal-button-save">
Save Changes
</button>
</div>
</div>
</div>
</div>
</div>
<!-- End of modal footer section -->
</ng-template>
<button type="button" (click)="open(columnchoosermodal)" class="settings-button">
<svg-icon class="sicon" src="public/themes/suite8/images/column_chooser.svg"></svg-icon>
Columns
<scrm-image class="sicon" image="column_chooser"></scrm-image>
Columns
</button>
<!-- End of modal component section -->

View file

@ -1,79 +1,79 @@
import {Component, OnInit} from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';
import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
import {ModalDismissReasons, NgbModal} from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'scrm-columnchooser-ui',
templateUrl: './columnchooser.component.html',
selector: 'scrm-columnchooser-ui',
templateUrl: './columnchooser.component.html',
})
export class ColumnChooserUiComponent implements OnInit {
modalTitle: string = 'Choose Columns';
closeResult: string;
modalTitle: string = 'Choose Columns';
closeResult: string;
constructor(private modalService: NgbModal) {
}
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg',
windowClass: 'column-chooser-modal'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
constructor(private modalService: NgbModal) {
}
}
displayed = [
'Name',
'City',
'Billing Country',
'Phone',
'User',
'Email Address',
'Date Created',
];
hidden = [
'Annual Revenue',
'Phone Fax',
'Billing Street',
'Billing Post Code',
'Shipping Street',
'Shipping Postcode',
'Rating',
'Website',
'Ownership',
'Employees'
];
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex);
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg',
windowClass: 'column-chooser-modal'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
}
ngOnInit() {
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
}
displayed = [
'Name',
'City',
'Billing Country',
'Phone',
'User',
'Email Address',
'Date Created',
];
hidden = [
'Annual Revenue',
'Phone Fax',
'Billing Street',
'Billing Post Code',
'Shipping Street',
'Shipping Postcode',
'Rating',
'Website',
'Ownership',
'Employees'
];
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex);
}
}
ngOnInit() {
}
}

View file

@ -1,10 +1,10 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {AppManagerModule} from '@base/app-manager/app-manager.module';
import {ColumnChooserUiComponent} from './columnchooser.component';
import {DragDropModule} from '@angular/cdk/drag-drop';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [ColumnChooserUiComponent],
@ -13,8 +13,8 @@ import {AngularSvgIconModule} from 'angular-svg-icon';
CommonModule,
DragDropModule,
AppManagerModule.forChild(ColumnChooserUiComponent),
AngularSvgIconModule
ImageModule
]
})
export class ColumnchooserUiModule {
}
}

View file

@ -2,207 +2,207 @@
<ng-template #filtermodal let-modal>
<!-- Start of modal header section -->
<!-- Start of modal header section -->
<div class="filter-modal">
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close Filter Modal"
(click)="modal.dismiss('Cross click')">
<svg-icon class="sicon" src="public/themes/suite8/images/close_modal.svg"></svg-icon>
</button>
</div>
<!-- End of modal header section -->
<!-- Start of modal body section -->
<div class="modal-body">
<ng-template [ngIf]="quickFilter">
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Opportunity Name</label>
<input class="modal-input" type="text" placeholder="Lorem Ipsum">
<label class="filter-checkbox-container">
<input type="filter-checkbox">
<span class="filter-checkmark"></span>
</label>
<p class="quick-filter-options">
My Items
</p>
<label class="filter-checkbox-container">
<input type="filter-checkbox">
<span class="filter-checkmark"></span>
</label>
<p class="quick-filter-options">
Open Items
</p>
<label class="filter-checkbox-container">
<input type="filter-checkbox">
<span class="filter-checkmark"></span>
</label>
<p class="quick-filter-options">
My Favourites
</p>
</div>
</div>
</ng-template>
<ng-template [ngIf]="advancedFilter">
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Opportunity Name</label>
<input class="modal-input modal-input-large" type="text" placeholder="Lorem Ipsum">
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="relate-input-group">
<label class="modal-label">Account Name</label>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<input class="modal-input modal-input-large" type="text" placeholder="Lorem Ipsum">
</div>
<div class="col-lg-3">
<div class="relate-input-group">
<button type="button" class="create-popup-button create-popup-arrow">
<svg-icon class="sicon" src="public/themes/suite8/images/cursor.svg"></svg-icon>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<svg-icon class="sicon" src="public/themes/suite8/images/cross.svg"></svg-icon>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Opportunity Amount</label>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<div class="select">
<select>
<option>Equals</option>
<option>Greater than</option>
<option>Less than</option>
</select>
</div>
</div>
<div class="col-lg-3">
<input class="modal-input modal-input-small" type="text" placeholder="0">
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Expected Close Date</label>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="select">
<select>
<option>Equals</option>
<option>Greater than</option>
<option>Less than</option>
</select>
</div>
</div>
<div class="col-lg-4">
<input class="modal-input modal-date-input" type="text" placeholder="dd/mm/yy">
<button type="button" class="create-popup-button create-popup-calendar">
<svg-icon class="sicon" src="public/themes/suite8/images/calendar.svg"></svg-icon>
<div class="filter-modal">
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close Filter Modal"
(click)="modal.dismiss('Cross click')">
<scrm-image class="sicon" image="close_modal"></scrm-image>
</button>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Next Step</label>
<input class="modal-input modal-input-large" type="text" placeholder="Lorem Ipsum">
</div>
</div>
<div class="row">
<div class="col-lg-9">
<label class="modal-label">Save Filter as:</label>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<div class="select">
<select>
<option>Lorem Ipsum</option>
<option>Lorem Ipsum</option>
<option>Lorem Ipsum</option>
</select>
</div>
</div>
<div class="col-lg-3">
<button class="save-filter-button">Save</button>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Order by column</label>
<div class="select">
<select>
<option>Name</option>
<option>Date Created</option>
<option>Date Modified</option>
</select>
</div>
</div>
</div>
</ng-template>
</div>
<!-- End of modal body section -->
<!-- End of modal header section -->
<!-- Start of modal footer section -->
<!-- Start of modal body section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-3">
</div>
<div class="col-lg-9">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal">
Clear
</button>
<ng-template [ngIf]="advancedFilter">
<button type="button" class="modal-button-save dropdown-toggle">
My Filters
</button>
</ng-template>
<div class="modal-body">
<ng-template [ngIf]="quickFilter">
<button type="button" class="btn modal-button-save" (click)="toggleAdvancedFilter()">
Advanced
</button>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Opportunity Name</label>
<input class="modal-input" type="text" placeholder="Lorem Ipsum">
<label class="filter-checkbox-container">
<input type="filter-checkbox">
<span class="filter-checkmark"></span>
</label>
<p class="quick-filter-options">
My Items
</p>
<label class="filter-checkbox-container">
<input type="filter-checkbox">
<span class="filter-checkmark"></span>
</label>
<p class="quick-filter-options">
Open Items
</p>
<label class="filter-checkbox-container">
<input type="filter-checkbox">
<span class="filter-checkmark"></span>
</label>
<p class="quick-filter-options">
My Favourites
</p>
</div>
</div>
</ng-template>
<ng-template [ngIf]="advancedFilter">
<button type="button" class="btn modal-button-save" (click)="toggleQuickFilter()">
Quick
</button>
</ng-template>
<button type="button" class="btn modal-button-save">
Search
</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Opportunity Name</label>
<input class="modal-input modal-input-large" type="text" placeholder="Lorem Ipsum">
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="relate-input-group">
<label class="modal-label">Account Name</label>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<input class="modal-input modal-input-large" type="text" placeholder="Lorem Ipsum">
</div>
<div class="col-lg-3">
<div class="relate-input-group">
<button type="button" class="create-popup-button create-popup-arrow">
<scrm-image class="sicon" image="cursor"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Opportunity Amount</label>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<div class="select">
<select>
<option>Equals</option>
<option>Greater than</option>
<option>Less than</option>
</select>
</div>
</div>
<div class="col-lg-3">
<input class="modal-input modal-input-small" type="text" placeholder="0">
</div>
</div>
<!-- End of modal footer section -->
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Expected Close Date</label>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="select">
<select>
<option>Equals</option>
<option>Greater than</option>
<option>Less than</option>
</select>
</div>
</div>
<div class="col-lg-4">
<input class="modal-input modal-date-input" type="text" placeholder="dd/mm/yy">
<button type="button" class="create-popup-button create-popup-calendar">
<scrm-image class="sicon" image="calendar"></scrm-image>
</button>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Next Step</label>
<input class="modal-input modal-input-large" type="text" placeholder="Lorem Ipsum">
</div>
</div>
<div class="row">
<div class="col-lg-9">
<label class="modal-label">Save Filter as:</label>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<div class="select">
<select>
<option>Lorem Ipsum</option>
<option>Lorem Ipsum</option>
<option>Lorem Ipsum</option>
</select>
</div>
</div>
<div class="col-lg-3">
<button class="save-filter-button">Save</button>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="modal-label">Order by column</label>
<div class="select">
<select>
<option>Name</option>
<option>Date Created</option>
<option>Date Modified</option>
</select>
</div>
</div>
</div>
</ng-template>
</div>
<!-- End of modal body section -->
<!-- Start of modal footer section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-3">
</div>
<div class="col-lg-9">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal">
Clear
</button>
<ng-template [ngIf]="advancedFilter">
<button type="button" class="modal-button-save dropdown-toggle">
My Filters
</button>
</ng-template>
<ng-template [ngIf]="quickFilter">
<button type="button" class="btn modal-button-save" (click)="toggleAdvancedFilter()">
Advanced
</button>
</ng-template>
<ng-template [ngIf]="advancedFilter">
<button type="button" class="btn modal-button-save" (click)="toggleQuickFilter()">
Quick
</button>
</ng-template>
<button type="button" class="btn modal-button-save">
Search
</button>
</div>
</div>
</div>
</div>
</div>
<!-- End of modal footer section -->
</ng-template>
<button type="button" (click)="open(filtermodal)" class="settings-button">
<svg-icon class="sicon" src="public/themes/suite8/images/filter.svg"></svg-icon>
Filter
<scrm-image class="sicon" image="filter"></scrm-image>
Filter
</button>
<!-- End of modal component section -->

View file

@ -1,6 +1,10 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {FilterUiComponent} from './filter.component';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('FilterUiComponent', () => {
let component: FilterUiComponent;
@ -8,7 +12,14 @@ describe('FilterUiComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FilterUiComponent]
declarations: [FilterUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
})
.compileComponents();
}));

View file

@ -1,61 +1,61 @@
import {Component, OnInit} from '@angular/core';
import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
import {ModalDismissReasons, NgbModal} from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'scrm-filter-ui',
templateUrl: './filter.component.html',
selector: 'scrm-filter-ui',
templateUrl: './filter.component.html',
})
export class FilterUiComponent implements OnInit {
filterModal: boolean = true;
modalTitle: string = 'Quick Filter';
closeResult: string;
quickFilter: boolean = true;
advancedFilter: boolean = false;
filterModal: boolean = true;
modalTitle: string = 'Quick Filter';
closeResult: string;
quickFilter: boolean = true;
advancedFilter: boolean = false;
constructor(private modalService: NgbModal) {
}
toggleQuickFilter() {
this.modalTitle = 'Quick Filter';
this.advancedFilter = !this.advancedFilter;
this.quickFilter = !this.quickFilter;
}
toggleAdvancedFilter() {
this.modalTitle = 'Advanced Filter';
this.advancedFilter = !this.advancedFilter;
this.quickFilter = !this.quickFilter;
}
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg',
windowClass: 'filter-modal'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
constructor(private modalService: NgbModal) {
}
}
ngOnInit() {
toggleQuickFilter() {
this.modalTitle = 'Quick Filter';
this.advancedFilter = !this.advancedFilter;
this.quickFilter = !this.quickFilter;
}
}
toggleAdvancedFilter() {
this.modalTitle = 'Advanced Filter';
this.advancedFilter = !this.advancedFilter;
this.quickFilter = !this.quickFilter;
}
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg',
windowClass: 'filter-modal'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
ngOnInit() {
}
}

View file

@ -1,9 +1,9 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {FilterUiComponent} from './filter.component';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [FilterUiComponent],
@ -11,8 +11,8 @@ import {AngularSvgIconModule} from 'angular-svg-icon';
imports: [
CommonModule,
AppManagerModule.forChild(FilterUiComponent),
AngularSvgIconModule
ImageModule
]
})
export class FilterUiModule {
}
}

View file

@ -1,24 +1,24 @@
<!-- Start of footer section -->
<div class="footer">
<div class="copyright-links">
<a (click)="openSuiteCopyright(suitecopyright)" class="footer-link" data-toggle="modal"
data-target=".copyright-suitecrm">
&copy; Supercharged by SuiteCRM
</a>
<a (click)="openSugarCopyright(sugarcopyright)" class="footer-link" data-toggle="modal"
data-target=".copyright-sugarcrm">
&copy; Powered By SugarCRM
</a>
</div>
<div class="back-to-top">
<a (click)="backToTop()" class="footer-link">
<div class="copyright-links">
<a (click)="openSuiteCopyright(suitecopyright)" class="footer-link" data-toggle="modal"
data-target=".copyright-suitecrm">
&copy; Supercharged by SuiteCRM
</a>
<a (click)="openSugarCopyright(sugarcopyright)" class="footer-link" data-toggle="modal"
data-target=".copyright-sugarcrm">
&copy; Powered By SugarCRM
</a>
</div>
<div class="back-to-top">
<a (click)="backToTop()" class="footer-link">
<span>
Back To Top
<svg-icon class="sicon back-top-icon" src="public/themes/suite8/images/arrow_up_filled.svg"></svg-icon>
<scrm-image class="sicon back-top-icon" image="arrow_up_filled"></scrm-image>
</span>
</a>
</div>
</a>
</div>
</div>
<!-- End of footer section -->
@ -27,75 +27,75 @@
<div class="copyright">
<!-- Start of SugarCRM Copyright notice modal -->
<!-- Start of SugarCRM Copyright notice modal -->
<ng-template #sugarcopyright let-modal>
<ng-template #sugarcopyright let-modal>
<div class="copyright-sugarcrm" role="dialog" aria-hidden="true">
<div class="modal-header">
<h5 class="modal-title">&copy; Powered By SugarCRM</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<svg-icon class="sicon" src="public/themes/suite8/images/icon_modal_close.svg"></svg-icon>
</button>
</div>
<div class="modal-body">
<p>
&copy; 2004-2013 SugarCRM Inc. The Program is provided AS IS, without
warranty. Licensed under AGPLv3.
</p>
<p>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version
3 as published by the Free Software Foundation, including the
additional permission set forth in the source code header.
</p>
<p>
SugarCRM is a trademark of SugarCRM, Inc. All other company and
product names may be trademarks of the respective companies with
which they are associated.
</p>
</div>
</div>
<div class="copyright-sugarcrm" role="dialog" aria-hidden="true">
<div class="modal-header">
<h5 class="modal-title">&copy; Powered By SugarCRM</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<scrm-image class="sicon" image="icon_modal_close"></scrm-image>
</button>
</div>
<div class="modal-body">
<p>
&copy; 2004-2013 SugarCRM Inc. The Program is provided AS IS, without
warranty. Licensed under AGPLv3.
</p>
<p>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version
3 as published by the Free Software Foundation, including the
additional permission set forth in the source code header.
</p>
<p>
SugarCRM is a trademark of SugarCRM, Inc. All other company and
product names may be trademarks of the respective companies with
which they are associated.
</p>
</div>
</div>
</ng-template>
</ng-template>
<!-- End of SugarCRM Copyright notice modal -->
<!-- End of SugarCRM Copyright notice modal -->
<!-- Start of SuiteCRM Copyright notice modal -->
<!-- Start of SuiteCRM Copyright notice modal -->
<ng-template #suitecopyright let-modal>
<ng-template #suitecopyright let-modal>
<div class="copyright-suitecrm" role="dialog" aria-hidden="true">
<div class="modal-header">
<h5 class="modal-title">&copy; Supercharged by SuiteCRM</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<svg-icon class="sicon" src="public/themes/suite8/images/icon_modal_close.svg"></svg-icon>
</button>
</div>
<div class="modal-body">
<p>
SuiteCRM has been written and assembled by SalesAgility. The Program
is provided AS IS, without warranty. Licensed under AGPLv3.
</p>
<p>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version
3 as published by the Free Software Foundation, including the
additional permission set forth in the source code header.
</p>
<p>
SuiteCRM is a trademark of SalesAgility Ltd. All other company and
product names may be trademarks of the respective companies with
which they are associated.
</p>
</div>
</div>
<div class="copyright-suitecrm" role="dialog" aria-hidden="true">
<div class="modal-header">
<h5 class="modal-title">&copy; Supercharged by SuiteCRM</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<scrm-image class="sicon" image="icon_modal_close"></scrm-image>
</button>
</div>
<div class="modal-body">
<p>
SuiteCRM has been written and assembled by SalesAgility. The Program
is provided AS IS, without warranty. Licensed under AGPLv3.
</p>
<p>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version
3 as published by the Free Software Foundation, including the
additional permission set forth in the source code header.
</p>
<p>
SuiteCRM is a trademark of SalesAgility Ltd. All other company and
product names may be trademarks of the respective companies with
which they are associated.
</p>
</div>
</div>
</ng-template>
</ng-template>
<!-- End of SuiteCRM Copyright notice modal -->
<!-- End of SuiteCRM Copyright notice modal -->
</div>
<!-- End of copyright modal section -->

View file

@ -1,8 +1,8 @@
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {FooterUiComponent} from './footer.component';

View file

@ -1,6 +1,6 @@
import {Component, OnInit} from '@angular/core';
import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
import {ModalDismissReasons, NgbModal} from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'scrm-footer-ui',

View file

@ -1,9 +1,9 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {FooterUiComponent} from './footer.component';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
@ -12,7 +12,7 @@ import {AngularSvgIconModule} from 'angular-svg-icon';
imports: [
CommonModule,
AppManagerModule.forChild(FooterUiComponent),
AngularSvgIconModule
ImageModule
]
})
export class FooterUiModule {

View file

@ -0,0 +1,9 @@
<ng-container *ngIf="(vm$ | async) as vm">
<ng-container *ngIf="getImage(vm, image) as img">
<svg-icon *ngIf="img.type === 'svg'" class="sicon" src="{{img.path}}"></svg-icon>
<img *ngIf="img.type !=='svg'" src="{{img.path}}">
</ng-container>
</ng-container>

View file

@ -0,0 +1,73 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ImageComponent} from './image.component';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {of} from 'rxjs';
import {take} from 'rxjs/operators';
import {Component} from '@angular/core';
@Component({
selector: 'host-component',
template: '<scrm-image [image]="image"></scrm-image>'
})
class TestHostComponent {
private image = 'line';
setImage(newImage: string): void {
this.image = newImage;
}
}
describe('ImageComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ImageComponent, TestHostComponent],
imports: [
AngularSvgIconModule,
HttpClientTestingModule
],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
})
.compileComponents();
});
beforeEach(() => {
testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
});
it('should create', () => {
expect(testHostComponent).toBeTruthy();
});
it('should have image with src', () => {
expect(testHostComponent).toBeTruthy();
const img = testHostFixture.nativeElement.querySelector('img');
expect(img).toBeTruthy();
expect(img.src).toContain(themeImagesMockData.line.path);
});
it('should have svg', () => {
testHostComponent.setImage('download');
testHostFixture.detectChanges();
expect(testHostFixture.nativeElement.querySelector('svg-icon')).toBeTruthy();
});
});

View file

@ -0,0 +1,45 @@
import {Component, Input} from '@angular/core';
import {combineLatest, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {ThemeImage, ThemeImageMap, ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
@Component({
selector: 'scrm-image',
templateUrl: './image.component.html',
styleUrls: []
})
export class ImageComponent {
images$: Observable<ThemeImageMap> = this.themeImagesFacade.images$;
vm$ = combineLatest([this.images$]).pipe(
map(([images]) => ({
images
})));
@Input() image: string;
constructor(protected themeImagesFacade: ThemeImagesFacade) {
}
/**
* Get image from current view model and log if not existent
*
* @param vm
* @param image name
* @returns ThemeImage
*/
getImage(vm: { images: ThemeImageMap }, image: string): ThemeImage {
if (!vm || !vm.images || Object.keys(vm.images).length < 1) {
return null;
}
const img = vm.images[image];
if (!img) {
console.warn(`Image with name '${image}' not found`);
}
return img;
}
}

View file

@ -0,0 +1,19 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageComponent} from '@components/image/image.component';
@NgModule({
declarations: [
ImageComponent
],
exports: [
ImageComponent
],
imports: [
CommonModule,
AngularSvgIconModule
]
})
export class ImageModule {
}

View file

@ -1,15 +1,15 @@
<!-- Start List View Container Section -->
<div class="list-view-container container-fluid">
<div class="row">
<div class="col-lg-9" [ngClass]="{ 'col-lg-12': !widgetService.displayWidgets }">
<scrm-table-ui></scrm-table-ui>
</div>
<div class="row">
<div class="col-lg-9" [ngClass]="{ 'col-lg-12': !widgetService.displayWidgets }">
<scrm-table-ui></scrm-table-ui>
</div>
<div class="col-lg-3" *ngIf="widgetService.displayWidgets">
<scrm-widget-ui></scrm-widget-ui>
<div class="col-lg-3" *ngIf="widgetService.displayWidgets">
<scrm-widget-ui></scrm-widget-ui>
</div>
</div>
</div>
</div>
<!-- End List View Container Section -->

View file

@ -1,11 +1,16 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ListcontainerUiComponent} from './list-container.component';
import {TableUiModule} from '@components/table/table.module';
import {WidgetUiModule} from '@components/widget/widget.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {of} from 'rxjs';
import {take} from 'rxjs/operators';
describe('ListcontainerUiComponent', () => {
@ -19,7 +24,15 @@ describe('ListcontainerUiComponent', () => {
WidgetUiModule,
AngularSvgIconModule,
HttpClientTestingModule,
BrowserAnimationsModule
BrowserAnimationsModule,
ApolloTestingModule
],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [ListcontainerUiComponent]
})
@ -33,6 +46,6 @@ describe('ListcontainerUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -1,55 +1,53 @@
import {Component, HostListener, OnInit, ViewChild} from '@angular/core';
import {Component, HostListener, OnInit} from '@angular/core';
import {WidgetService} from '../widget/widget.service';
@Component({
selector: 'scrm-list-container-ui',
templateUrl: 'list-container.component.html'
selector: 'scrm-list-container-ui',
templateUrl: 'list-container.component.html'
})
export class ListcontainerUiComponent implements OnInit {
displayResponsiveTable: boolean = false;
showCollapsed: boolean = false;
tableToggleIcon: string = "public/themes/suite8/images/mobile_expand_icon.svg";
listViewIconUnsorted: string = "public/themes/suite8/images/sort.svg";
listViewIconSorted: string = "public/themes/suite8/images/sort_descend.svg";
displayResponsiveTable = false;
showCollapsed = false;
tableToggleIcon = 'public/themes/suite8/images/mobile_expand_icon.svg';
listViewIconUnsorted = 'public/themes/suite8/images/sort.svg';
listViewIconSorted = 'public/themes/suite8/images/sort_descend.svg';
allSelected: boolean = false;
allSelected = false;
@HostListener("window:resize", ["$event"])
onResize(event: any) {
event.target.innerWidth;
if (innerWidth <= 768) {
this.displayResponsiveTable = true;
} else {
this.displayResponsiveTable = false;
constructor(private widgetService: WidgetService) {
}
}
constructor(private widgetService: WidgetService) {
}
@HostListener('window:resize', ['$event'])
onResize(event: any): void {
event.target.innerWidth;
if (innerWidth <= 768) {
this.displayResponsiveTable = true;
} else {
this.displayResponsiveTable = false;
}
}
toggleWidgets() {
this.widgetService.emitData();
}
toggleWidgets(): void {
this.widgetService.emitData();
}
expandRow(row) {
row.expanded = !row.expanded;
}
expandRow(row): void {
row.expanded = !row.expanded;
}
ngOnInit() {
window.dispatchEvent(new Event("resize"));
ngOnInit(): void {
window.dispatchEvent(new Event('resize'));
}
selectAll() {
this.allSelected = true;
}
deselectAll() {
this.allSelected = false;
}
}
selectAll(): void {
this.allSelected = true;
}
deselectAll(): void {
this.allSelected = false;
}
}

View file

@ -1,4 +1,4 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
@ -9,15 +9,15 @@ import {WidgetUiModule} from '../widget/widget.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
@NgModule({
declarations: [ListcontainerUiComponent],
exports: [ListcontainerUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ListcontainerUiComponent),
TableUiModule,
WidgetUiModule,
AngularSvgIconModule
]
declarations: [ListcontainerUiComponent],
exports: [ListcontainerUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ListcontainerUiComponent),
TableUiModule,
WidgetUiModule,
AngularSvgIconModule
]
})
export class ListcontainerUiModule {
}
}

View file

@ -4,8 +4,13 @@ import {ListheaderUiComponent} from './list-header.component';
import {ModuletitleUiModule} from '@components/module-title/module-title.module';
import {ActionmenuUiModule} from '@components/action-menu/action-menu.module';
import {SettingsmenuUiModule} from '@components/settings-menu/settings-menu.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ImageModule} from '@components/image/image.module';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('ListheaderUiComponent', () => {
let component: ListheaderUiComponent;
@ -17,10 +22,18 @@ describe('ListheaderUiComponent', () => {
ModuletitleUiModule,
ActionmenuUiModule,
SettingsmenuUiModule,
AngularSvgIconModule,
HttpClientTestingModule
ApolloTestingModule,
HttpClientTestingModule,
ImageModule
],
declarations: [ListheaderUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [ListheaderUiComponent]
})
.compileComponents();
}));
@ -32,6 +45,6 @@ describe('ListheaderUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -24,7 +24,7 @@
[@fade]>
<div class="inner-addon left-addon">
<svg-icon class="sicon" src="public/themes/suite8/images/login_user.svg"></svg-icon>
<scrm-image image="login_user"></scrm-image>
<input [(ngModel)]="uname"
type="text"
name="username"
@ -39,7 +39,7 @@
</div>
<div class="inner-addon left-addon">
<svg-icon class="sicon" src="public/themes/suite8/images/login_password.svg"></svg-icon>
<scrm-image image="login_password"></scrm-image>
<input [(ngModel)]="passw"
type="password"
name="password"
@ -71,7 +71,7 @@
*ngIf="cardState==='back'"
[@fade]>
<div class="inner-addon left-addon">
<svg-icon class="sicon" src="public/themes/suite8/images/login_user.svg"></svg-icon>
<scrm-image image="login_user"></scrm-image>
<input [(ngModel)]="uname"
type="text"
name="username"
@ -86,7 +86,7 @@
</div>
<div class="inner-addon left-addon">
<svg-icon class="sicon" src="public/themes/suite8/images/email.svg"></svg-icon>
<scrm-image image="email"></scrm-image>
<input [(ngModel)]="email"
type="email"
name="email"

View file

@ -1,23 +1,18 @@
import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {LoginUiComponent} from './login.component';
import {ApiService} from '../../services/api/api.service';
import {LanguageFacade} from '@base/facades/language/language.facade';
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
import {systemConfigFacadeMock} from '@base/facades/system-config/system-config.facade.spec.mock';
import {
recoverPasswordMock,
recoverPasswordMockData
} from '@services/process/processes/recover-password/recover-passoword.spec.mock';
import {recoverPasswordMock} from '@services/process/processes/recover-password/recover-passoword.spec.mock';
import {By} from '@angular/platform-browser';
import {Observable, of} from 'rxjs';
describe('LoginComponent', () => {
let component: LoginUiComponent;
@ -49,42 +44,39 @@ describe('LoginComponent', () => {
fixture.detectChanges();
});
it('should create', async(inject([HttpTestingController],
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should flip on forgot password click', () => {
fixture.whenStable().then(() => {
expect(component).toBeTruthy();
})));
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeNull();
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeNull();
expect(fixture.debugElement.query(By.css('.back-link'))).toBeNull();
it('should flip on forgot password click', async(inject([HttpTestingController],
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
fixture.whenStable().then(() => {
expect(component).toBeTruthy();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeNull();
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeNull();
expect(fixture.debugElement.query(By.css('.back-link'))).toBeNull();
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('#login-button'))).toBeTruthy();
component.flipCard();
// same test codmockRecoverPasswordServicee here
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeNull();
expect(fixture.debugElement.query(By.css('#login-button'))).toBeNull();
expect(fixture.debugElement.query(By.css('.forgotten-password'))).toBeNull();
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('#login-button'))).toBeTruthy();
component.flipCard();
// same test codmockRecoverPasswordServicee here
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeNull();
expect(fixture.debugElement.query(By.css('#login-button'))).toBeNull();
expect(fixture.debugElement.query(By.css('.forgotten-password'))).toBeNull();
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('.back-link'))).toBeTruthy();
});
});
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('.back-link'))).toBeTruthy();
});
})));
it('should output login fail status', async(inject([HttpTestingController],
(router: RouterTestingModule, http: HttpTestingController, api: ApiService, caller: LoginUiComponent) => {
fixture.whenStable().then(() => {
expect(component).toBeTruthy();
console.log = jasmine.createSpy("log");
component.onLoginError(component);
expect(console.log).toHaveBeenCalledWith('Login failed');
});
})));
it('should output login fail status', () => {
fixture.whenStable().then(() => {
expect(component).toBeTruthy();
console.log = jasmine.createSpy('log');
component.onLoginError(component);
expect(console.log).toHaveBeenCalledWith('Login failed');
});
});
});

View file

@ -1,12 +1,13 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {FormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {LoginUiComponent} from './login.component';
import {LogoUiModule} from '../logo/logo.module';
import {LoginUiRoutes} from './login.routes';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [
@ -21,7 +22,8 @@ import {AngularSvgIconModule} from 'angular-svg-icon';
AppManagerModule.forChild(LoginUiComponent),
RouterModule.forChild(LoginUiRoutes),
CommonModule,
AngularSvgIconModule
AngularSvgIconModule,
ImageModule
]
})
export class LoginUiModule {

View file

@ -1 +1 @@
<img src="public/themes/suite8/images/suitecrm_logo.png" alt="SuiteCRM Logo">
<scrm-image image="company_logo"></scrm-image>

View file

@ -1,15 +1,18 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { AppManagerModule } from '../../app-manager/app-manager.module';
import { LogoUiComponent } from './logo.component';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {LogoUiComponent} from './logo.component';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [LogoUiComponent],
exports: [LogoUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(LogoUiComponent)
]
declarations: [LogoUiComponent],
exports: [LogoUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(LogoUiComponent),
ImageModule
]
})
export class LogoUiModule {}
export class LogoUiModule {
}

View file

@ -2,160 +2,160 @@
<ng-template #modal let-modal>
<!-- Start of modal header section -->
<!-- Start of modal header section -->
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<svg-icon class="sicon" src="public/themes/suite8/images/close_modal.svg"></svg-icon>
</button>
</div>
<!-- End of modal header section -->
<!-- Start of modal body section -->
<div class="modal-body">
<h5 class="modal-field-header">Overview</h5>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<div class="select">
<select>
<option>Aw yeah.</option>
<option>You should pick me instead.</option>
<option>I think I'm the better option!</option>
</select>
</div>
</div>
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<scrm-image class="sicon" image="close_modal"></scrm-image>
</button>
</div>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<label class="checkbox-container create-popup-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
<div class="col-lg-6">
<div class="relate-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-arrow">
<svg-icon class="sicon" src="public/themes/suite8/images/cursor.svg"></svg-icon>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<svg-icon class="sicon" src="public/themes/suite8/images/cross.svg"></svg-icon>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="calendar-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-calendar">
<svg-icon class="sicon" src="public/themes/suite8/images/calendar.svg"></svg-icon>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<svg-icon class="sicon" src="public/themes/suite8/images/cross.svg"></svg-icon>
</button>
</div>
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
</div>
</div>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<div class="select">
<select>
<option>Aw yeah.</option>
<option>You should pick me instead.</option>
<option>I think I'm the better option!</option>
</select>
</div>
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<label class="checkbox-container create-popup-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="relate-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-arrow">
<svg-icon class="sicon" src="public/themes/suite8/images/cursor.svg"></svg-icon>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<svg-icon class="sicon" src="public/themes/suite8/images/cross.svg"></svg-icon>
</button>
</div>
</div>
<div class="col-lg-6">
<div class="calendar-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-calendar">
<svg-icon class="sicon" src="public/themes/suite8/images/calendar.svg"></svg-icon>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<svg-icon class="sicon" src="public/themes/suite8/images/cross.svg"></svg-icon>
</button>
</div>
</div>
</div>
</div>
<!-- End of modal body section -->
<!-- End of modal header section -->
<!-- Start of modal footer section -->
<!-- Start of modal body section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-6 pr-0 mr-0">
<div class="modal-options">
<label class="modal-checkbox-container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<p class="modal-redirect-text">
Redirect on SAVE
<svg-icon class="sicon info-icon" src="public/themes/suite8/images/info.svg"></svg-icon>
</p>
<div class="modal-body">
<h5 class="modal-field-header">Overview</h5>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<div class="select">
<select>
<option>Aw yeah.</option>
<option>You should pick me instead.</option>
<option>I think I'm the better option!</option>
</select>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal"
(click)="modal.dismiss('Cross click')">
Cancel
</button>
<button type="button" class="btn modal-button-save">
Save and New
</button>
<button type="button" class="btn modal-button-save">
Save
</button>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<label class="checkbox-container create-popup-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
<div class="col-lg-6">
<div class="relate-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-arrow">
<scrm-image class="sicon" image="cursor"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="calendar-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-calendar">
<scrm-image class="sicon" image="calendar"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
</div>
</div>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<div class="select">
<select>
<option>Aw yeah.</option>
<option>You should pick me instead.</option>
<option>I think I'm the better option!</option>
</select>
</div>
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<label class="checkbox-container create-popup-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="relate-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-arrow">
<scrm-image class="sicon" image="cursor"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
<div class="col-lg-6">
<div class="calendar-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-calendar">
<scrm-image class="sicon" image="calendar"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- End of modal footer section -->
<!-- End of modal body section -->
<!-- Start of modal footer section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-6 pr-0 mr-0">
<div class="modal-options">
<label class="modal-checkbox-container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<p class="modal-redirect-text">
Redirect on SAVE
<scrm-image class="sicon info-icon" image="info"></scrm-image>
</p>
</div>
</div>
<div class="col-lg-6">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal"
(click)="modal.dismiss('Cross click')">
Cancel
</button>
<button type="button" class="btn modal-button-save">
Save and New
</button>
<button type="button" class="btn modal-button-save">
Save
</button>
</div>
</div>
</div>
</div>
<!-- End of modal footer section -->
</ng-template>

View file

@ -1,6 +1,10 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ModalUiComponent} from './modal.component';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('ModalUiComponent', () => {
let component: ModalUiComponent;
@ -8,7 +12,14 @@ describe('ModalUiComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ModalUiComponent]
declarations: [ModalUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
})
.compileComponents();
}));

View file

@ -1,61 +1,57 @@
import {Component, OnInit} from '@angular/core';
import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
import {ModalDismissReasons, NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {
trigger,
animate,
transition,
} from '@angular/animations';
import {animate, transition, trigger,} from '@angular/animations';
@Component({
selector: 'scrm-modal-ui',
templateUrl: './modal.component.html',
animations: [
trigger('modalFade', [
transition('void <=> *', [
animate('800ms')
]),
]),
]
selector: 'scrm-modal-ui',
templateUrl: './modal.component.html',
animations: [
trigger('modalFade', [
transition('void <=> *', [
animate('800ms')
]),
]),
]
})
export class ModalUiComponent implements OnInit {
closeResult: string;
modalTitle: string = 'New Account';
createModal: boolean = true;
closeResult: string;
modalTitle: string = 'New Account';
createModal: boolean = true;
constructor(private modalService: NgbModal) {
}
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
constructor(private modalService: NgbModal) {
}
}
newButtonConfig = {
text: 'NEW',
buttonClass: 'action-button'
};
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
ngOnInit() {
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
newButtonConfig = {
text: 'NEW',
buttonClass: 'action-button'
};
ngOnInit() {
}
}

View file

@ -1,4 +1,4 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
@ -6,16 +6,18 @@ import {ModalUiComponent} from './modal.component';
import {ButtonUiModule} from '../button/button.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [ModalUiComponent],
exports: [ModalUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ModalUiComponent),
ButtonUiModule,
AngularSvgIconModule
]
declarations: [ModalUiComponent],
exports: [ModalUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ModalUiComponent),
ButtonUiModule,
AngularSvgIconModule,
ImageModule
]
})
export class ModalUiModule {
}

View file

@ -1,255 +1,259 @@
<!-- Start of main navbar section -->
<div class="top-panel fixed-top" *ngIf="(vm$ | async) as vm">
<!-- Start of empty navbar section until data is loaded -->
<ng-template [ngIf]="!loaded">
<nav class="navbar navbar-expand-lg">
<div class="navbar-collapse collapse order-4 order-md-0 collapsenav">
<ul class="navbar-nav">
<li class="top-nav nav-item">&nbsp;
</li>
</ul>
</div>
</nav>
</ng-template>
<!-- Start of empty navbar section until data is loaded -->
<!-- End of empty section until data is loaded -->
<!-- Start of empty navbar with logo -->
<ng-template [ngIf]="loaded">
<ng-template [ngIf]="!navbar.authenticated">
<nav class="navbar ml-0 pl-0">
<div class="navbar-collapse">
<ul class="navbar-nav">
<li class="pl-0">
<scrm-logo-ui></scrm-logo-ui>
</li>
</ul>
</div>
</nav>
</ng-template>
<!-- End of empty navbar section with logo -->
<!-- Start of mobile navigation section -->
<ng-template [ngIf]="mobileNavbar">
<ul class="navbar mobile-nav-block mobilenavbar">
<div class="position-static" ngbDropdown #myDrop="ngbDropdown" [autoClose]="false">
<button aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation" ngbDropdownToggle
class="navbar-toggler" type="button">
<svg-icon class="responsive-menu-toggler" src="public/themes/suite8/images/hamburger.svg"></svg-icon>
</button>
<div class="mobile-nav-dropdown w-100" ngbDropdownMenu [@mobileNavFade]>
<ng-template [ngIf]="backLink">
<li ngbDropdownItem><a class="mobile-back-link" (click)="navBackLink($event)">< BACK</a></li>
</ng-template>
<ng-template [ngIf]="mainNavLink">
<li class="" *ngFor="let item of navbar.menu" ngbDropdownItem>
<a class="mobile-nav-link">{{ item.link.label }}</a>
<svg-icon class="sicon-xs mobile-nav-arrow" src="public/themes/suite8/images/arrow_right_filled.svg"
(click)="changeSubNav($event, item)"></svg-icon>
</li>
</ng-template>
<ng-template [ngIf]="mobileSubNav">
<li class="" *ngFor="let sub of submenu" ngbDropdownItem>
<a class="mobile-nav-link action-link">
{{ sub.link.label }}
</a>
<ul *ngIf="sub.submenu.length" class="">
<li *ngFor="let subitem of sub.submenu" class="nav-item">
<a class="mobile-nav-link action-link" href="/#{{ subitem.link.route }}">
{{ subitem.link.label }}
</a>
</li>
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
<a class="mobile-nav-link action-link"
href="#/{{ rec.moduleName }}/{{ rec.itemId }}">{{ rec.itemSummary }}</a>
<ng-template [ngIf]="!loaded">
<nav class="navbar navbar-expand-lg">
<div class="navbar-collapse collapse order-4 order-md-0 collapsenav">
<ul class="navbar-nav">
<li class="top-nav nav-item">&nbsp;
</li>
</ng-template>
</ul>
</li>
</ng-template>
<li>
<a class="mobile-nav-close" href="#" (click)="myDrop.close()">
<svg-icon class="sicon mobile-nav-close" src="public/themes/suite8/images/cross_bold.svg"></svg-icon>
<span class="nav-close-text">close menu</span>
</a>
</li>
</div>
</div>
<div class="global-links" ngbDropdown>
<li class="global-link-item">
<a class="nav-link primary-global-link dropdown-toggle" ngbDropdownToggle>
<svg-icon class="global-action-icon sicon-2x" src="public/themes/suite8/images/user.svg"></svg-icon>
{{ navbar.currentUser.name }}
</a>
<div aria-labelledby="navbarDropdownMenuLink"
class="dropdown-menu global-links-dropdown" ngbDropdownMenu>
<a class="dropdown-item global-links-sublink" ngbDropdownItem
href="#/Users/EditView/{{ navbar.currentUser.id }}">Profile</a>
<ng-template [ngIf]="navbar.globalActions">
<a class="dropdown-item global-links-sublink" ngbDropdownItem
*ngFor="let globalAction of navbar.globalActions"
target="{{ globalAction.link.target }}"
href="{{ globalAction.link.url }}">{{ globalAction.link.label }}
</a>
</ng-template>
<scrm-logout-ui></scrm-logout-ui>
</div>
</li>
</div>
</ul>
</nav>
</ng-template>
<!-- End of mobile navigation section-->
<!-- End of empty section until data is loaded -->
<!-- Start of navbar section with data once authenticated -->
<!-- Start of empty navbar with logo -->
<ng-template [ngIf]="navbar.authenticated && !mobileNavbar">
<nav class="navbar navbar-expand-md navbar-1">
<div class="navbar-collapse collapse collapsenav" [ngbCollapse]="mainNavCollapse">
<ul class="navbar-nav home-nav">
<li class="nav-item home-nav-link">
<a class="home-nav-link" href="#/Home">
<svg-icon class="home-icon" src="public/themes/suite8/images/home.svg"></svg-icon>
</a>
</li>
</ul>
<ng-template [ngIf]="loaded">
<ng-template [ngIf]="!navbar.authenticated">
<nav class="navbar ml-0 pl-0">
<div class="navbar-collapse">
<ul class="navbar-nav">
<li class="pl-0">
<scrm-logo-ui></scrm-logo-ui>
</li>
</ul>
</div>
</nav>
</ng-template>
<!-- Navbar with grouped tabs -->
<!-- End of empty navbar section with logo -->
<ul *ngIf="vm.userPreferences['navigation_paradigm'] == 'gm'" class="navbar-nav grouped">
<li class="top-nav nav-item dropdown main-grouped" *ngFor="let item of navbar.menu">
<!-- Start of mobile navigation section -->
<ng-template [ngIf]="mobileNavbar">
<ul class="navbar mobile-nav-block mobilenavbar">
<div class="position-static" ngbDropdown #myDrop="ngbDropdown" [autoClose]="false">
<button aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"
ngbDropdownToggle
class="navbar-toggler" type="button">
<scrm-image class="responsive-menu-toggler" image="hamburger"></scrm-image>
</button>
<div class="mobile-nav-dropdown w-100" ngbDropdownMenu [@mobileNavFade]>
<ng-template [ngIf]="backLink">
<li ngbDropdownItem><a class="mobile-back-link" (click)="navBackLink($event)">< BACK</a>
</li>
</ng-template>
<ng-template [ngIf]="mainNavLink">
<li class="" *ngFor="let item of navbar.menu" ngbDropdownItem>
<a class="mobile-nav-link">{{ item.link.label }}</a>
<scrm-image class="sicon-xs mobile-nav-arrow" image="arrow_right_filled"
(click)="changeSubNav($event, item)"></scrm-image>
</li>
</ng-template>
<ng-template [ngIf]="mobileSubNav">
<li class="" *ngFor="let sub of submenu" ngbDropdownItem>
<a class="mobile-nav-link action-link">
{{ sub.link.label }}
</a>
<ul *ngIf="sub.submenu.length" class="">
<li *ngFor="let subitem of sub.submenu" class="nav-item">
<a class="mobile-nav-link action-link" href="/#{{ subitem.link.route }}">
{{ subitem.link.label }}
</a>
</li>
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
<a class="mobile-nav-link action-link"
href="#/{{ rec.moduleName }}/{{ rec.itemId }}">{{ rec.itemSummary }}</a>
</li>
</ng-template>
</ul>
</li>
</ng-template>
<li>
<a class="mobile-nav-close" href="#" (click)="myDrop.close()">
<scrm-image class="sicon mobile-nav-close" image="cross_bold"></scrm-image>
<span class="nav-close-text">close menu</span>
</a>
</li>
</div>
</div>
<div class="global-links" ngbDropdown>
<li class="global-link-item">
<a class="nav-link primary-global-link dropdown-toggle" ngbDropdownToggle>
<scrm-image class="global-action-icon sicon-2x" image="user"></scrm-image>
{{ navbar.currentUser.name }}
</a>
<div aria-labelledby="navbarDropdownMenuLink"
class="dropdown-menu global-links-dropdown" ngbDropdownMenu>
<a class="dropdown-item global-links-sublink" ngbDropdownItem
href="#/Users/EditView/{{ navbar.currentUser.id }}">Profile</a>
<ng-template [ngIf]="navbar.globalActions">
<a class="dropdown-item global-links-sublink" ngbDropdownItem
*ngFor="let globalAction of navbar.globalActions"
target="{{ globalAction.link.target }}"
href="{{ globalAction.link.url }}">{{ globalAction.link.label }}
</a>
</ng-template>
<scrm-logout-ui></scrm-logout-ui>
</div>
</li>
</div>
</ul>
</ng-template>
<!-- End of mobile navigation section-->
<!-- Start of navbar section with data once authenticated -->
<ng-template [ngIf]="navbar.authenticated && !mobileNavbar">
<nav class="navbar navbar-expand-md navbar-1">
<div class="navbar-collapse collapse collapsenav" [ngbCollapse]="mainNavCollapse">
<ul class="navbar-nav home-nav">
<li class="nav-item home-nav-link">
<a class="home-nav-link" href="#/Home">
<scrm-image class="home-icon" image="home"></scrm-image>
</a>
</li>
</ul>
<!-- Navbar with grouped tabs -->
<ul *ngIf="vm.userPreferences['navigation_paradigm'] == 'gm'" class="navbar-nav grouped">
<li class="top-nav nav-item dropdown main-grouped" *ngFor="let item of navbar.menu">
<span data-toggle="collapse" data-target=".navbar-collapse">
<a class="nav-link-grouped dropdown-toggle" data-toggle="dropdown" href="{{ item.link.route }}">
{{ item.link.label }}
</a>
</span>
<ul class="dropdown-menu main" aria-labelledby="navbarDropdownMenuLink" [ngbCollapse]="subNavCollapse">
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
<a class="nav-link action-link" *ngIf="!sub.submenu.length" href="{{sub.link.route}}">
{{ sub.link.label }}
</a>
<a class="nav-link action-link dropdown-item dropdown-toggle" *ngIf="sub.submenu.length"
(click)="subItemCollapse = !subItemCollapse">
{{ sub.link.label }}
</a>
<ul *ngIf="sub.submenu.length" class="dropdown-menu submenu">
<li *ngFor="let subitem of sub.submenu" class="nav-item">
<a class="nav-link action-link" href="#{{ subitem.link.route }}">
<svg-icon *ngIf="subitem.icon" src="public/themes/suite8/images/{{ subitem.icon }}.svg">
</svg-icon>
{{ subitem.link.label }}
</a>
</li>
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
<a class="nav-link action-link"
href="{{ rec.url }}">{{ rec.summary }}</a>
</li>
</ng-template>
</ul>
</li>
</ul>
</li>
</ul>
<ul class="dropdown-menu main" aria-labelledby="navbarDropdownMenuLink"
[ngbCollapse]="subNavCollapse">
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
<a class="nav-link action-link" *ngIf="!sub.submenu.length"
href="{{sub.link.route}}">
{{ sub.link.label }}
</a>
<a class="nav-link action-link dropdown-item dropdown-toggle"
*ngIf="sub.submenu.length"
(click)="subItemCollapse = !subItemCollapse">
{{ sub.link.label }}
</a>
<ul *ngIf="sub.submenu.length" class="dropdown-menu submenu">
<li *ngFor="let subitem of sub.submenu" class="nav-item">
<a class="nav-link action-link" href="#{{ subitem.link.route }}">
<scrm-image *ngIf="subitem.icon" image="{{ subitem.icon }}">
</scrm-image>
{{ subitem.link.label }}
</a>
</li>
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
<a class="nav-link action-link"
href="{{ rec.url }}">{{ rec.summary }}</a>
</li>
</ng-template>
</ul>
</li>
</ul>
</li>
</ul>
<!-- Navbar with non-grouped tabs -->
<!-- Navbar with non-grouped tabs -->
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm'" class="navbar-nav">
<li class="top-nav nav-item dropdown non-grouped" *ngFor="let item of navbar.menu">
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm'" class="navbar-nav">
<li class="top-nav nav-item dropdown non-grouped" *ngFor="let item of navbar.menu">
<!-- TODO: Implement a cleaner solution than hard coded ngIf -->
<!-- TODO: Implement a cleaner solution than hard coded ngIf -->
<ng-template [ngIf]="item.link.label !== 'Home'">
<ng-template [ngIf]="item.link.label !== 'Home'">
<span data-toggle="collapse" data-target=".navbar-collapse">
<a class="nav-link-nongrouped dropdown-toggle"
[href]="item.link.url"
[routerLink]="item.link.route"
[queryParams]="item.link.params"
routerLinkActive="active">
[href]="item.link.url"
[routerLink]="item.link.route"
[queryParams]="item.link.params"
routerLinkActive="active">
{{ item.link.label }}
</a>
</span>
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu submenu">
<div class="nav-item" *ngFor="let sub of item.submenu">
<a class="nav-link action-link"
[href]="sub.link.url"
[routerLink]="sub.link.route"
[queryParams]="sub.link.params">
<svg-icon *ngIf="sub.icon"
src="public/themes/suite8/images/{{ sub.icon }}.svg"></svg-icon>
{{ sub.link.label }}
</a>
</div>
<ng-template [ngIf]="item.recentRecords && item.recentRecords.length">
<h4 class="recently-viewed-header">{{vm.appStrings['LBL_LAST_VIEWED']}}</h4>
<div *ngFor="let recentRecord of item.recentRecords" class="nav-item">
<a class="nav-link action-link" href="#">{{ recentRecord.summary }}</a>
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu submenu">
<div class="nav-item" *ngFor="let sub of item.submenu">
<a class="nav-link action-link"
[href]="sub.link.url"
[routerLink]="sub.link.route"
[queryParams]="sub.link.params">
<scrm-image *ngIf="sub.icon"
image="{{ sub.icon }}"></scrm-image>
{{ sub.link.label }}
</a>
</div>
<ng-template [ngIf]="item.recentRecords && item.recentRecords.length">
<h4 class="recently-viewed-header">{{vm.appStrings['LBL_LAST_VIEWED']}}</h4>
<div *ngFor="let recentRecord of item.recentRecords" class="nav-item">
<a class="nav-link action-link" href="#">{{ recentRecord.summary }}</a>
</div>
</ng-template>
</div>
</ng-template>
</div>
</ng-template>
</li>
</ul>
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm' && navbar.all.modules && navbar.all.modules.length > 0"
class="navbar-nav">
<li class="top-nav nav-item dropdown non-grouped">
<a class="nav-link-nongrouped dropdown-toggle">More</a>
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu more-menu submenu">
<div class="nav-item" *ngFor="let item of navbar.all.modules">
<a class="nav-link action-link" [href]="item.link.url"
[routerLink]="item.link.route">{{ item.link.label }}</a>
</div>
<div class="nav-item" *ngFor="let item of navbar.all.extra">
<a class="nav-link action-link" [href]="item.link.url"
[routerLink]="item.link.route">{{ item.link.label }}</a>
</div>
</div>
</li>
</ul>
</div>
<div class="global-links" ngbDropdown>
<ul class="navbar-nav">
<li class="global-link-item">
<a class="nav-link primary-global-link dropdown-toggle" ngbDropdownToggle>
<svg-icon class="global-action-icon sicon-2x" src="public/themes/suite8/images/user.svg"></svg-icon>
{{ navbar.currentUser.name }}
</a>
<div aria-labelledby="navbarDropdownMenuLink"
class="dropdown-menu global-links-dropdown dropdown-menu-right" ngbDropdownMenu>
<a class="dropdown-item global-links-sublink" ngbDropdownItem
href="#/Users/EditView/{{ navbar.currentUser.id }}">Profile</a>
<ng-template [ngIf]="navbar.globalActions">
<a class="dropdown-item global-links-sublink" ngbDropdownItem
*ngFor="let globalAction of navbar.globalActions"
target="{{ globalAction.link.target }}"
href="{{ globalAction.link.url }}">{{ globalAction.link.label }}
</a>
</li>
</ul>
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm' && navbar.all.modules && navbar.all.modules.length > 0"
class="navbar-nav">
<li class="top-nav nav-item dropdown non-grouped">
<a class="nav-link-nongrouped dropdown-toggle">More</a>
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu more-menu submenu">
<div class="nav-item" *ngFor="let item of navbar.all.modules">
<a class="nav-link action-link" [href]="item.link.url"
[routerLink]="item.link.route">{{ item.link.label }}</a>
</div>
<div class="nav-item" *ngFor="let item of navbar.all.extra">
<a class="nav-link action-link" [href]="item.link.url"
[routerLink]="item.link.route">{{ item.link.label }}</a>
</div>
</div>
</li>
</ul>
</div>
<div class="global-links" ngbDropdown>
<ul class="navbar-nav">
<li class="global-link-item">
<a class="nav-link primary-global-link dropdown-toggle" ngbDropdownToggle>
<scrm-image class="global-action-icon sicon-2x" image="user"></scrm-image>
{{ navbar.currentUser.name }}
</a>
<div aria-labelledby="navbarDropdownMenuLink"
class="dropdown-menu global-links-dropdown dropdown-menu-right" ngbDropdownMenu>
<a class="dropdown-item global-links-sublink" ngbDropdownItem
href="#/Users/EditView/{{ navbar.currentUser.id }}">Profile</a>
<ng-template [ngIf]="navbar.globalActions">
<a class="dropdown-item global-links-sublink" ngbDropdownItem
*ngFor="let globalAction of navbar.globalActions"
target="{{ globalAction.link.target }}"
href="{{ globalAction.link.url }}">{{ globalAction.link.label }}
</a>
</ng-template>
<scrm-logout-ui></scrm-logout-ui>
</div>
</li>
</ul>
</div>
</nav>
<!-- End of navbar section with data once authenticated -->
<scrm-action-bar-ui></scrm-action-bar-ui>
</ng-template>
<scrm-logout-ui></scrm-logout-ui>
</div>
</li>
</ul>
</div>
</nav>
<!-- End of navbar section with data once authenticated -->
<scrm-action-bar-ui></scrm-action-bar-ui>
</ng-template>
</ng-template>
</ng-template>
</div>
<!-- End of main navbar section -->

View file

@ -1,7 +1,7 @@
import {async, ComponentFixture, TestBed, inject, fakeAsync, tick} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
@ -10,8 +10,8 @@ import {NavigationFacade} from '@base/facades/navigation/navigation.facade';
import {LanguageFacade} from '@base/facades/language/language.facade';
import {navigationMock} from '@base/facades/navigation/navigation.facade.spec.mock';
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
import {UserPreferenceFacade} from "@base/facades/user-preference/user-preference.facade";
import {userPreferenceFacadeMock} from "@base/facades/user-preference/user-preference.facade.spec.mock";
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
import {userPreferenceFacadeMock} from '@base/facades/user-preference/user-preference.facade.spec.mock';
describe('NavbarUiComponent', () => {

View file

@ -1,4 +1,4 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
@ -8,8 +8,8 @@ import {LogoUiModule} from '../logo/logo.module';
import {LogoutUiModule} from '../logout/logout.module';
import {ActionBarUiModule} from '../action-bar/action-bar.module';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {RouterModule} from '@angular/router';
import {ImageModule} from '@components/image/image.module';
@NgModule({
@ -22,8 +22,8 @@ import {RouterModule} from '@angular/router';
LogoutUiModule,
ActionBarUiModule,
NgbModule,
AngularSvgIconModule,
RouterModule
RouterModule,
ImageModule
]
})
export class NavbarUiModule {

View file

@ -1,23 +1,25 @@
<div class="bulk-action float-right">
<button class="pagination-button" aria-label="Navigate to first page">
<svg-icon class="sicon-2x pagination-icons" src="public/themes/suite8/images/paginate_previous.svg">
</svg-icon>
</button>
<button class="pagination-button" aria-label="Previous page">
<svg-icon class="sicon-2x pagination-icons" src="public/themes/suite8/images/paginate_first.svg">
</svg-icon>
</button>
<span class="pagination-count" [ngClass]="{
'hide-pagination-count': displayResponsiveTable
}">
(1- 20 of 200)
</span>
<button class="pagination-button" aria-label="Next page">
<svg-icon class="sicon-2x pagination-icons" src="public/themes/suite8/images/paginate_last.svg">
</svg-icon>
</button>
<button class="pagination-button" aria-label="Navigate to last page">
<svg-icon class="sicon-2x pagination-icons" src="public/themes/suite8/images/paginate_next.svg">
</svg-icon>
</button>
<button class="pagination-button" aria-label="Navigate to first page">
<scrm-image class="sicon-2x pagination-icons" image="paginate_previous">
</scrm-image>
</button>
<button class="pagination-button" aria-label="Previous page">
<scrm-image class="sicon-2x pagination-icons" image="paginate_first">
</scrm-image>
</button>
<span class="pagination-count"
[ngClass]="{
'hide-pagination-count': displayResponsiveTable
}"
>
(1- 20 of 200)
</span>
<button class="pagination-button" aria-label="Next page">
<scrm-image class="sicon-2x pagination-icons" image="paginate_last">
</scrm-image>
</button>
<button class="pagination-button" aria-label="Navigate to last page">
<scrm-image class="sicon-2x pagination-icons" image="paginate_next">
</scrm-image>
</button>
</div>

View file

@ -3,6 +3,11 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {PaginationUiComponent} from './pagination.component';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
import {ImageModule} from '@components/image/image.module';
describe('PaginationUiComponent', () => {
let component: PaginationUiComponent;
@ -12,9 +17,17 @@ describe('PaginationUiComponent', () => {
TestBed.configureTestingModule({
imports: [
AngularSvgIconModule,
HttpClientTestingModule
HttpClientTestingModule,
ImageModule
],
declarations: [PaginationUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [PaginationUiComponent, ]
})
.compileComponents();
}));
@ -26,6 +39,6 @@ describe('PaginationUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -1,14 +1,14 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'scrm-pagination-ui',
templateUrl: 'pagination.component.html'
selector: 'scrm-pagination-ui',
templateUrl: 'pagination.component.html'
})
export class PaginationUiComponent implements OnInit {
ngOnInit() {
ngOnInit() {
}
}
}

View file

@ -1,9 +1,9 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
import {PaginationUiComponent} from './pagination.component';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [PaginationUiComponent],
@ -11,8 +11,8 @@ import {AngularSvgIconModule} from 'angular-svg-icon';
imports: [
CommonModule,
AppManagerModule.forChild(PaginationUiComponent),
AngularSvgIconModule
ImageModule
]
})
export class PaginationUiModule {
}
}

View file

@ -1,19 +1,19 @@
<div class="list-view-settings float-right">
<button type="button" class="settings-button dropdown-toggle">
Display As
</button>
<button type="button" class="settings-button dropdown-toggle">
My Filters
</button>
<scrm-filter-ui></scrm-filter-ui>
<button type="button" class="settings-button">
<svg-icon class="sicon" src="public/themes/suite8/images/reset.svg"></svg-icon>
Reset
</button>
<scrm-columnchooser-ui></scrm-columnchooser-ui>
<button type="button" class="settings-button" (click)="toggleWidgets()"
[ngClass]="{ 'charts-active': widgetService.displayWidgets }">
<svg-icon class="sicon" src="public/themes/suite8/images/pie.svg"></svg-icon>
Charts
</button>
<button type="button" class="settings-button dropdown-toggle">
Display As
</button>
<button type="button" class="settings-button dropdown-toggle">
My Filters
</button>
<scrm-filter-ui></scrm-filter-ui>
<button type="button" class="settings-button">
<scrm-image class="sicon" image="reset"></scrm-image>
Reset
</button>
<scrm-columnchooser-ui></scrm-columnchooser-ui>
<button type="button" class="settings-button" (click)="toggleWidgets()"
[ngClass]="{ 'charts-active': widgetService.displayWidgets }">
<scrm-image class="sicon" image="pie"></scrm-image>
Charts
</button>
</div>

View file

@ -3,8 +3,13 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {SettingsmenuUiComponent} from './settings-menu.component';
import {ColumnchooserUiModule} from '@components/columnchooser/columnchooser.module';
import {FilterUiModule} from '@components/filter/filter.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
import {ImageModule} from '@components/image/image.module';
describe('SettingsmenuUiComponent', () => {
let component: SettingsmenuUiComponent;
@ -15,10 +20,18 @@ describe('SettingsmenuUiComponent', () => {
imports: [
ColumnchooserUiModule,
FilterUiModule,
AngularSvgIconModule,
HttpClientTestingModule
HttpClientTestingModule,
ApolloTestingModule,
ImageModule
],
declarations: [SettingsmenuUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [SettingsmenuUiComponent]
})
.compileComponents();
}));
@ -30,6 +43,6 @@ describe('SettingsmenuUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -1,25 +1,24 @@
import {Component, OnInit} from '@angular/core';
import { WidgetService } from '../widget/widget.service';
import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
import {WidgetService} from '../widget/widget.service';
@Component({
selector: 'scrm-settings-menu-ui',
templateUrl: 'settings-menu.component.html',
selector: 'scrm-settings-menu-ui',
templateUrl: 'settings-menu.component.html',
})
export class SettingsmenuUiComponent implements OnInit {
constructor(private widgetService: WidgetService) {}
constructor(private widgetService: WidgetService) {
}
toggleWidgets() {
this.widgetService.emitData();
}
toggleWidgets(): void {
this.widgetService.emitData();
}
ngOnInit(): void {
ngOnInit() {
}
}
}

View file

@ -1,4 +1,4 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '../../app-manager/app-manager.module';
@ -6,18 +6,18 @@ import {SettingsmenuUiComponent} from './settings-menu.component';
import {ColumnchooserUiModule} from '../columnchooser/columnchooser.module';
import {FilterUiModule} from '../filter/filter.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [SettingsmenuUiComponent],
exports: [SettingsmenuUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(SettingsmenuUiComponent),
ColumnchooserUiModule,
FilterUiModule,
AngularSvgIconModule
]
declarations: [SettingsmenuUiComponent],
exports: [SettingsmenuUiComponent],
imports: [
CommonModule,
AppManagerModule.forChild(SettingsmenuUiComponent),
ColumnchooserUiModule,
FilterUiModule,
ImageModule
]
})
export class SettingsmenuUiModule {
}
}

View file

@ -5,6 +5,12 @@ import {PaginationUiModule} from '@components/pagination/pagination.module';
import {BulkactionmenuUiModule} from '@components/bulk-action-menu/bulk-action-menu.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ImageModule} from '@components/image/image.module';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('TablefooterUiComponent', () => {
let component: TablefooterUiComponent;
@ -16,9 +22,18 @@ describe('TablefooterUiComponent', () => {
PaginationUiModule,
BulkactionmenuUiModule,
AngularSvgIconModule,
HttpClientTestingModule
HttpClientTestingModule,
ApolloTestingModule,
ImageModule
],
declarations: [TablefooterUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [TablefooterUiComponent]
})
.compileComponents();
}));
@ -30,6 +45,6 @@ describe('TablefooterUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -5,6 +5,12 @@ import {PaginationUiModule} from '@components/pagination/pagination.module';
import {BulkactionmenuUiModule} from '@components/bulk-action-menu/bulk-action-menu.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ImageModule} from '@components/image/image.module';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('TableheaderUiComponent', () => {
let component: TableheaderUiComponent;
@ -16,9 +22,18 @@ describe('TableheaderUiComponent', () => {
PaginationUiModule,
BulkactionmenuUiModule,
AngularSvgIconModule,
HttpClientTestingModule
HttpClientTestingModule,
ApolloTestingModule,
ImageModule
],
declarations: [TableheaderUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [TableheaderUiComponent]
})
.compileComponents();
}));
@ -30,6 +45,6 @@ describe('TableheaderUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -6,6 +6,12 @@ import {TablebodyUiModule} from '@components/table/table-body/table-body.module'
import {TablefooterUiModule} from '@components/table/table-footer/table-footer.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ImageModule} from '@components/image/image.module';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
describe('TableUiComponent', () => {
let component: TableUiComponent;
@ -18,9 +24,18 @@ describe('TableUiComponent', () => {
TablebodyUiModule,
TablefooterUiModule,
AngularSvgIconModule,
HttpClientTestingModule
HttpClientTestingModule,
ApolloTestingModule,
ImageModule
],
declarations: [TableUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
declarations: [TableUiComponent]
})
.compileComponents();
}));
@ -32,6 +47,6 @@ describe('TableUiComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component).toBeTruthy();
});
});

View file

@ -1,20 +1,20 @@
<!-- Start List View Widget Section -->
<div class="accordion list-view-widget mr-0">
<div class="widget-header">
<h3>
Quick Charts
<svg-icon class="sicon float-right widget-close-icon"
(click)="toggleWidgetContent()"
src="{{ widgetHeaderToggleIcon }}">
</svg-icon>
</h3>
</div>
<div class="widget-header">
<h3>
Quick Charts
<scrm-image class="sicon float-right widget-close-icon"
(click)="toggleWidgetContent()"
image="{{ widgetHeaderToggleIcon }}">
</scrm-image>
</h3>
</div>
<div *ngIf="displayWidgetContent" class="widget collapse show" data-parent=".list-view-widget"
[@widgetContentFade]="displayWidgetContent ? 'true' : 'false'" class="open-close-container">
<scrm-chart-ui></scrm-chart-ui>
</div>
<div *ngIf="displayWidgetContent" class="widget collapse show" data-parent=".list-view-widget"
[@widgetContentFade]="displayWidgetContent ? 'true' : 'false'" class="open-close-container">
<scrm-chart-ui></scrm-chart-ui>
</div>
</div>
<!-- End List View Widget Section -->

View file

@ -1,35 +1,50 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {WidgetUiComponent} from './widget.component';
import {AngularSvgIconModule} from "angular-svg-icon";
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ChartUiModule} from '@components/chart/chart.module';
import {HttpClientTestingModule} from "@angular/common/http/testing";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {of} from 'rxjs';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
import {ImageModule} from '@components/image/image.module';
describe('WidgetUiComponent', () => {
let component: WidgetUiComponent;
let fixture: ComponentFixture<WidgetUiComponent>;
let component: WidgetUiComponent;
let fixture: ComponentFixture<WidgetUiComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
AngularSvgIconModule,
ChartUiModule,
HttpClientTestingModule,
BrowserAnimationsModule
],
declarations: [WidgetUiComponent]
})
.compileComponents();
}));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
AngularSvgIconModule,
ChartUiModule,
HttpClientTestingModule,
BrowserAnimationsModule,
ApolloTestingModule,
ImageModule
],
declarations: [WidgetUiComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WidgetUiComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
beforeEach(() => {
fixture = TestBed.createComponent(WidgetUiComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,29 +1,28 @@
import {Component, OnInit} from '@angular/core';
import { ChartUiComponent } from "@components/chart/chart.component";
import {animate, style, transition, trigger} from "@angular/animations";
import {animate, style, transition, trigger} from '@angular/animations';
@Component({
selector: 'scrm-widget-ui',
templateUrl: 'widget.component.html',
animations: [
trigger("widgetFade", [
transition("void => *", [
style({transform: "translateX(100%)", opacity: 0}),
animate("500ms", style({transform: "translateX(0)", opacity: 1}))
trigger('widgetFade', [
transition('void => *', [
style({transform: 'translateX(100%)', opacity: 0}),
animate('500ms', style({transform: 'translateX(0)', opacity: 1}))
]),
transition("* => void", [
style({transform: "translateX(0)", opacity: 1}),
animate("500ms", style({transform: "translateX(100%)", opacity: 0}))
transition('* => void', [
style({transform: 'translateX(0)', opacity: 1}),
animate('500ms', style({transform: 'translateX(100%)', opacity: 0}))
])
]),
trigger("widgetContentFade", [
transition("void => *", [
style({transform: "translateY(-5%)", opacity: 0}),
animate("500ms", style({transform: "translateY(0)", opacity: 1}))
trigger('widgetContentFade', [
transition('void => *', [
style({transform: 'translateY(-5%)', opacity: 0}),
animate('500ms', style({transform: 'translateY(0)', opacity: 1}))
]),
transition("* => void", [
style({transform: "translateY(0)", opacity: 1}),
animate("500ms", style({transform: "translateY(-5%)", opacity: 0}))
transition('* => void', [
style({transform: 'translateY(0)', opacity: 1}),
animate('500ms', style({transform: 'translateY(-5%)', opacity: 0}))
])
])
]
@ -32,15 +31,15 @@ import {animate, style, transition, trigger} from "@angular/animations";
export class WidgetUiComponent implements OnInit {
displayWidgetContent: boolean = true;
widgetHeaderToggleIcon: string = "public/themes/suite8/images/minimise_circled.svg";
displayWidgetContent = true;
widgetHeaderToggleIcon = 'minimise_circled';
toggleWidgetContent() {
if (this.widgetHeaderToggleIcon == "public/themes/suite8/images/minimise_circled.svg") {
this.widgetHeaderToggleIcon = "public/themes/suite8/images/plus_thin.svg";
if (this.widgetHeaderToggleIcon === 'minimise_circled') {
this.widgetHeaderToggleIcon = 'plus_thin';
this.displayWidgetContent = false;
} else {
this.widgetHeaderToggleIcon = "public/themes/suite8/images/minimise_circled.svg";
this.widgetHeaderToggleIcon = 'minimise_circled';
this.displayWidgetContent = true;
}
}

View file

@ -1,19 +1,19 @@
import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {WidgetUiComponent} from './widget.component';
import {ChartUiModule} from '../chart/chart.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ChartUiModule} from '@components/chart/chart.module';
import {ImageModule} from '@components/image/image.module';
@NgModule({
declarations: [WidgetUiComponent],
exports: [WidgetUiComponent],
imports: [
CommonModule,
ChartUiModule,
AngularSvgIconModule
]
declarations: [WidgetUiComponent],
exports: [WidgetUiComponent],
imports: [
CommonModule,
ChartUiModule,
ImageModule
]
})
export class WidgetUiModule {
}
}

View file

@ -1,18 +1,19 @@
import {HostListener, Injectable} from '@angular/core';
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root',
providedIn: 'root',
})
export class WidgetService {
displayWidgets: boolean = true;
widgetHeaderToggleIcon: string = "public/themes/suite8/images/minimise_circled.svg";
listViewFullWidth: boolean = true;
displayWidgets = true;
widgetHeaderToggleIcon = 'public/themes/suite8/images/minimise_circled.svg';
listViewFullWidth = true;
emitData() {
this.displayWidgets = !this.displayWidgets;
this.listViewFullWidth = !this.listViewFullWidth;
}
constructor() { }
constructor() {
}
}
emitData() {
this.displayWidgets = !this.displayWidgets;
this.listViewFullWidth = !this.listViewFullWidth;
}
}

View file

@ -0,0 +1,132 @@
import {BehaviorSubject, Observable, of} from 'rxjs';
import {shareReplay} from 'rxjs/operators';
import {ThemeImages, ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {RecordGQL} from '@services/api/graphql-api/api.record.get';
import {appStateFacadeMock} from '@base/facades/app-state/app-state.facade.spec.mock';
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
export const themeImagesMockData = {
bgOcher: {
path: 'legacy/themes/default/images/bgOcher.gif',
name: 'bgOcher',
type: 'gif'
},
Createjjwg_Maps: {
path: 'legacy/themes/default/images/Createjjwg_Maps.gif',
name: 'Createjjwg_Maps',
type: 'gif'
},
close: {
path: 'legacy/themes/suite8/images/close.png',
name: 'close',
type: 'png'
},
ProjectCopy: {
path: 'legacy/themes/default/images/ProjectCopy.gif',
name: 'ProjectCopy',
type: 'gif'
},
line: {
path: 'legacy/themes/default/images/line.gif',
name: 'line',
type: 'gif'
},
download: {
path: 'core/app/themes/suite8/images/download.svg',
name: 'download',
type: 'svg'
},
paginate_next: {
path: 'core/app/themes/suite8/images/paginate_next.svg',
name: 'paginate_next',
type: 'svg'
},
filter: {
path: 'core/app/themes/suite8/images/filter.svg',
name: 'filter',
type: 'svg'
},
reset: {
path: 'core/app/themes/suite8/images/reset.svg',
name: 'reset',
type: 'svg'
},
column_chooser: {
path: 'core/app/themes/suite8/images/column_chooser.svg',
name: 'column_chooser',
type: 'svg'
},
pie: {
path: 'core/app/themes/suite8/images/pie.svg',
name: 'pie',
type: 'svg'
},
cross: {
path: 'core/app/themes/suite8/images/cross.svg',
name: 'cross',
type: 'svg'
},
plus: {
path: 'core/app/themes/suite8/images/plus.svg',
name: 'plus',
type: 'svg'
},
edit: {
path: 'core/app/themes/suite8/images/edit.svg',
name: 'edit',
type: 'svg'
},
minimise_circled: {
path: 'core/app/themes/suite8/images/minimise_circled.svg',
name: 'minimise_circled',
type: 'svg'
},
paginate_previous: {
path: 'core/app/themes/suite8/images/paginate_previous.svg',
name: 'paginate_previous',
type: 'svg'
},
paginate_first: {
path: 'core/app/themes/suite8/images/paginate_first.svg',
name: 'paginate_first',
type: 'svg'
},
paginate_last: {
path: 'core/app/themes/suite8/images/paginate_last.svg',
name: 'paginate_last',
type: 'svg'
}
};
class ThemeImagesRecordGQLSpy extends RecordGQL {
constructor() {
super(null);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public fetch(module: string, id: string, metadata: { fields: string[] }): Observable<any> {
return of({
data: {
themeImages: {
_id: 'suite8',
items: themeImagesMockData
}
}
}).pipe(shareReplay(1));
}
}
class MockThemeImagesFacade extends ThemeImagesFacade {
protected store = new BehaviorSubject<ThemeImages>({
theme: 'suite8',
images: themeImagesMockData
});
constructor(protected recordGQL: RecordGQL, protected appStateFacade: AppStateFacade) {
super(recordGQL, appStateFacade);
}
}
export const themeImagesFacadeMock = new MockThemeImagesFacade(new ThemeImagesRecordGQLSpy(), appStateFacadeMock);

View file

@ -0,0 +1,21 @@
import {getTestBed, TestBed} from '@angular/core/testing';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {themeImagesFacadeMock, themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
describe('ThemeImages Facade', () => {
const service: ThemeImagesFacade = themeImagesFacadeMock;
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('#load',
(done: DoneFn) => {
service.load('suite8').subscribe(data => {
expect(data).toEqual(jasmine.objectContaining(themeImagesMockData));
done();
});
});
});

View file

@ -0,0 +1,152 @@
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs';
import {map, distinctUntilChanged, tap, shareReplay} from 'rxjs/operators';
import {RecordGQL} from '@services/api/graphql-api/api.record.get';
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
export interface ThemeImage {
path: string;
name: string;
type: string;
}
export interface ThemeImages {
theme: string;
images: ThemeImageMap;
}
export interface ThemeImageMap {
[key: string]: ThemeImage;
}
let internalState: ThemeImages = {
theme: null,
images: {}
};
let cachedTheme = null;
let cache$: Observable<any> = null;
@Injectable({
providedIn: 'root',
})
export class ThemeImagesFacade {
protected store = new BehaviorSubject<ThemeImages>(internalState);
protected state$ = this.store.asObservable();
protected resourceName = 'themeImages';
protected frontendName = 'theme-images';
protected fieldsMetadata = {
fields: [
'id',
'_id',
'items'
]
};
/**
* Public long-lived observable streams
*/
images$ = this.state$.pipe(map(state => state.images), distinctUntilChanged());
constructor(protected recordGQL: RecordGQL, protected appStateFacade: AppStateFacade) {
}
/**
* Public Api
*/
/**
* Change the current theme
*
* @param theme
*/
public changeTheme(theme: string): void {
this.appStateFacade.updateLoading(true);
this.load(theme).pipe(
tap(() => this.appStateFacade.updateLoading(false))
).subscribe();
}
/**
* Returns the currently active image theme
*
* @returns string
*/
public getTheme(): string {
return internalState.theme;
}
/**
* Initial ThemeImages load if not cached and update state.
* Returns observable to be used in resolver if needed
*
* @returns Observable<any>
*/
public load(theme: string): Observable<any> {
return this.getThemeImages(theme).pipe(
tap(images => {
this.updateState({...internalState, images, theme});
})
);
}
/**
* Internal API
*/
/**
* Update the state
*
* @param state
*/
protected updateState(state: ThemeImages) {
this.store.next(internalState = state);
}
/**
* Get theme images cached Observable or call the backend
*
* @return Observable<any>
*/
protected getThemeImages(theme: string): Observable<any> {
if (cachedTheme !== theme || cache$ === null) {
cachedTheme = theme;
cache$ = this.fetch(theme).pipe(
shareReplay(1)
);
}
return cache$;
}
/**
* Fetch the theme images from the backend
*
* @returns Observable<any>
*/
protected fetch(theme: string): Observable<any> {
return this.recordGQL
.fetch(this.resourceName, `/api/${this.frontendName}/${theme}`, this.fieldsMetadata)
.pipe(
map(({data}) => {
let images = {};
if (data && data.themeImages) {
images = data.themeImages.items;
}
return images;
})
);
}
}

View file

@ -4,15 +4,8 @@ import {map, distinctUntilChanged, tap, shareReplay} from 'rxjs/operators';
import {CollectionGQL} from '@services/api/graphql-api/api.collection.get';
export interface UserPreference {
id: string;
_id: string;
value: string;
items: { [key: string]: any };
}
export interface UserPreferenceMap {
[key: string]: UserPreference;
[key: string]: string;
}
export interface UserPreferences {
@ -20,7 +13,6 @@ export interface UserPreferences {
loading: boolean;
}
let internalState: UserPreferences = {
userPreferences: {},
loading: false
@ -57,6 +49,20 @@ export class UserPreferenceFacade {
* Public Api
*/
/**
* Get user preferences value by key
*
* @param key
*/
public getUserPreference(key: string): string {
if (!internalState.userPreferences || !internalState.userPreferences[key]) {
return null;
}
return internalState.userPreferences[key];
}
/**
* Initial UserPreferences load if not cached and update state.
@ -117,9 +123,14 @@ export class UserPreferenceFacade {
if (data.userPreferences && data.userPreferences.edges) {
data.userPreferences.edges.forEach((edge) => {
for (const key in edge.node.items) {
userPreferences[key] = edge.node.items[key];
if (!edge.node.items) {
return;
}
Object.keys(edge.node.items).forEach(key => {
userPreferences[key] = edge.node.items[key];
});
});
}

View file

@ -1,20 +0,0 @@
import {Injectable} from '@angular/core';
import {
Resolve,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
import {flatMap} from 'rxjs/operators';
@Injectable({providedIn: 'root'})
export class UserPreferenceResolver implements Resolve<any> {
constructor(private userPreferenceFacade: UserPreferenceFacade) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.userPreferenceFacade.load();
}
}

View file

@ -1,15 +1,13 @@
import {Injectable} from '@angular/core';
import {
Resolve,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
import {concatAll, map, toArray} from 'rxjs/operators';
import {forkJoin, Observable} from 'rxjs';
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
import {LanguageFacade} from '@base/facades/language/language.facade';
import {concatAll, first, flatMap, map, mergeMap, tap, toArray} from 'rxjs/operators';
import {forkJoin, Observable} from 'rxjs';
import {NavigationFacade} from '@base/facades/navigation/navigation.facade';
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
@Injectable({providedIn: 'root'})
@ -17,7 +15,9 @@ export class BaseMetadataResolver implements Resolve<any> {
constructor(private systemConfigFacade: SystemConfigFacade,
private languageFacade: LanguageFacade,
private navigationFacade: NavigationFacade) {
private navigationFacade: NavigationFacade,
private userPreferenceFacade: UserPreferenceFacade,
private themeImagesFacade: ThemeImagesFacade) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
@ -55,8 +55,40 @@ export class BaseMetadataResolver implements Resolve<any> {
streams$.configs = configs$;
}
if (this.isToLoadUserPreferences(route)) {
return forkJoin(streams$);
streams$.preferences = this.userPreferenceFacade.load();
}
const parallelStream$ = forkJoin(streams$);
return parallelStream$.pipe(
map((data: any) => {
let theme = null;
if (this.systemConfigFacade.getConfigValue('default_theme')) {
theme = this.systemConfigFacade.getConfigValue('default_theme');
}
if (this.userPreferenceFacade.getUserPreference('user_theme')) {
theme = this.userPreferenceFacade.getUserPreference('user_theme');
}
if (this.themeImagesFacade.getTheme()) {
theme = this.themeImagesFacade.getTheme();
}
if (theme !== null) {
return this.themeImagesFacade.load(theme);
}
return data;
}),
concatAll(),
toArray()
);
}
/**
@ -128,4 +160,18 @@ export class BaseMetadataResolver implements Resolve<any> {
return route.data.load.navigation !== false;
}
/**
* Should load user preferences. If not set defaults to true
*
* @param route
* @returns boolean
*/
protected isToLoadUserPreferences(route: ActivatedRouteSnapshot): boolean {
if (!route.data || !route.data.load) {
return true;
}
return route.data.load.preferences !== false;
}
}

View file

@ -1,8 +1,8 @@
<!-- Start List View Section -->
<div class="list-view">
<scrm-list-header-ui></scrm-list-header-ui>
<scrm-list-container-ui></scrm-list-container-ui>
<scrm-list-header-ui></scrm-list-header-ui>
<scrm-list-container-ui></scrm-list-container-ui>
</div>
<!-- End List View Section -->

View file

@ -1,37 +1,52 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { ListComponent } from './list.component';
import {ListComponent} from './list.component';
import {ListheaderUiModule} from '@components/list-header/list-header.module';
import {ListcontainerUiModule} from '@components/list-container/list-container.module';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ImageModule} from '@components/image/image.module';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
import {take} from 'rxjs/operators';
import {of} from 'rxjs';
describe('ListComponent', () => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ListheaderUiModule,
ListcontainerUiModule,
HttpClientTestingModule,
RouterTestingModule,
BrowserAnimationsModule
],
declarations: [ ListComponent ]
})
.compileComponents();
}));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ListheaderUiModule,
ListcontainerUiModule,
HttpClientTestingModule,
RouterTestingModule,
BrowserAnimationsModule,
ImageModule,
ApolloTestingModule
],
declarations: [ListComponent],
providers: [
{
provide: ThemeImagesFacade, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,15 +1,16 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'scrm-list',
templateUrl: './list.component.html',
styleUrls: []
selector: 'scrm-list',
templateUrl: './list.component.html',
styleUrls: []
})
export class ListComponent implements OnInit {
constructor() { }
constructor() {
}
ngOnInit() {
}
ngOnInit() {
}
}

View file

@ -1,17 +1,18 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ListComponent } from './list.component';
import { ListheaderUiModule } from '../../src/components/list-header/list-header.module';
import { ListcontainerUiModule } from '../../src/components/list-container/list-container.module';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ListComponent} from './list.component';
import {ListheaderUiModule} from '@components/list-header/list-header.module';
import {ListcontainerUiModule} from '@components/list-container/list-container.module';
@NgModule({
declarations: [ListComponent],
exports: [ListComponent],
imports: [
CommonModule,
ListheaderUiModule,
ListcontainerUiModule
]
declarations: [ListComponent],
exports: [ListComponent],
imports: [
CommonModule,
ListheaderUiModule,
ListcontainerUiModule
]
})
export class ListModule {}
export class ListModule {
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\ThemeImages;
use App\Service\ThemeImageService;
/**
* Class ThemeImagesItemDataProvider
*/
final class ThemeImagesItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
{
/**
* @var ThemeImageService
*/
private $themeImageService;
/**
* ThemeImagesItemDataProvider constructor.
* @param ThemeImageService $themeImageService
*/
public function __construct(ThemeImageService $themeImageService)
{
$this->themeImageService = $themeImageService;
}
/**
* Defined supported resources
* @param string $resourceClass
* @param string|null $operationName
* @param array $context
* @return bool
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return ThemeImages::class === $resourceClass;
}
/**
* Get theme image information for given theme
* @param string $resourceClass
* @param array|int|string $id
* @param string|null $operationName
* @param array $context
* @return ThemeImages|null
*/
public function getItem(
string $resourceClass,
$id,
string $operationName = null,
array $context = []
): ?ThemeImages {
return $this->themeImageService->get($id);
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(
* itemOperations={
* "get"
* },
* collectionOperations={
* },
* graphql={
* "item_query",
* },
* )
*/
class ThemeImages
{
/**
* @ApiProperty(identifier=true)
* @var string|null
*/
protected $id;
/**
* @ApiProperty
* @var array|null
*/
protected $items;
/**
* Get Id
* @return string|null
*/
public function getId(): ?string
{
return $this->id;
}
/**
* Set Id
* @param string|null $id
* @return ThemeImages
*/
public function setId(?string $id): ThemeImages
{
$this->id = $id;
return $this;
}
/**
* Get items
* @return array|null
*/
public function getItems(): ?array
{
return $this->items;
}
/**
* Set Items
* @param array|null $items
* @return ThemeImages
*/
public function setItems(?array $items): ThemeImages
{
$this->items = $items;
return $this;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Service;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
class ThemeImageFinder
{
/**
* @var string[]
*/
private $themeImageSupportedTypes;
/**
* ThemeImageFinder constructor.
* @param array $themeImageSupportedTypes
*/
public function __construct(array $themeImageSupportedTypes)
{
$this->themeImageSupportedTypes = $themeImageSupportedTypes;
}
/**
* Get list of existing images
* @param $fullPath
* @return SplFileInfo[]
*/
public function find($fullPath): iterable
{
if (!is_dir($fullPath)) {
return [];
}
$finder = new Finder();
$finder->files();
foreach ($this->themeImageSupportedTypes as $extension) {
$finder->name("*.$extension");
}
$finder->in($fullPath);
return $finder->getIterator();
}
}

View file

@ -0,0 +1,156 @@
<?php
namespace App\Service;
use App\Entity\ThemeImages;
class ThemeImageService
{
/**
* @var string[]
*/
private $themeImagePaths;
/**
* @var string[]
*/
private $themeImageSupportedTypes;
/**
* @var array
*/
private $themeImageTypePriority;
/**
* @var string
*/
private $projectDir;
/**
* @var ThemeImageFinder
*/
private $themeImageFinder;
/**
* ThemeImageService constructor.
* @param string[] $themeImagePaths
* @param array $themeImageSupportedTypes
* @param string $projectDir
* @param ThemeImageFinder $themeImageFinder
*/
public function __construct(
array $themeImagePaths,
array $themeImageSupportedTypes,
string $projectDir,
ThemeImageFinder $themeImageFinder
) {
$this->themeImagePaths = $themeImagePaths;
$this->themeImageSupportedTypes = $themeImageSupportedTypes;
$this->projectDir = $projectDir;
$this->themeImageFinder = $themeImageFinder;
$this->themeImageTypePriority = $this->buildTypePriorityMap($themeImageSupportedTypes);
}
/**
* Get images for given $theme
* @param string $theme
* @return ThemeImages
*/
public function get(string $theme): ThemeImages
{
$images = new ThemeImages();
$images->setId($theme);
$items = [];
foreach ($this->themeImagePaths as $themeImagePath) {
$themeImages = $this->getImages($themeImagePath, $theme);
$items = array_merge($items, $themeImages);
}
$images->setItems($items);
return $images;
}
/**
* Get images information for given path and theme
* @param string $imagePath
* @param string $theme
* @return array
*/
protected function getImages(string $imagePath, string $theme): array
{
$path = $this->buildPath($imagePath, $theme);
$fullPath = $this->projectDir . '/' . $path;
$it = $this->themeImageFinder->find($fullPath);
$images = [];
if (empty($it)) {
return $images;
}
foreach ($it as $file) {
$name = $file->getFilenameWithoutExtension();
$filePath = "$path/{$file->getFilename()}";
$extension = $file->getExtension();
if (!$this->hasPriority($images, $name, $extension)) {
continue;
}
$images[$name] = [
'path' => $filePath,
'name' => $name,
'type' => $extension
];
}
return $images;
}
/**
* Build path
* @param string $imagePath
* @param string $theme
* @return string
*/
protected function buildPath(string $imagePath, string $theme): string
{
return str_replace('<theme>', $theme, $imagePath);
}
/**
* Build the priority map
* @param array $types
* @return array
*/
protected function buildTypePriorityMap(array $types): array
{
return array_flip($types);
}
/**
* Check if current image has priority over already store image
* @param array $images
* @param string $name
* @param string $currentType
* @return bool
*/
protected function hasPriority(array $images, string $name, string $currentType): bool
{
if (empty($images[$name])) {
return true;
}
$existingType = $images[$name]['type'];
$existingPriority = $this->themeImageTypePriority[$existingType];
$currentPriority = $this->themeImageTypePriority[$currentType];
return $existingPriority > $currentPriority;
}
}

View file

@ -0,0 +1,140 @@
<?php namespace App\Tests;
use App\Service\ThemeImageFinder;
use App\Service\ThemeImageService;
use Codeception\Test\Unit;
use Exception;
use Symfony\Component\Finder\SplFileInfo;
class ThemeImageServiceTest extends Unit
{
/**
* @var UnitTester
*/
protected $tester;
/**
* @var ThemeImageService
*/
protected $themeImageService;
/**
* @throws Exception
*/
protected function _before()
{
$themeImagePaths = [
'legacy/themes/default/images',
'legacy/custom/themes/default/images',
'core/app/themes/default/images',
'legacy/themes/<theme>/images',
'legacy/custom/themes/<theme>/images',
'core/app/themes/<theme>/images',
];
$themeImageSupportedTypes = [
'svg',
'png',
'jpg',
'jpeg',
'gif',
];
$mockImages = [
'/legacy/themes/default/images' => [
new SplFileInfo('logo.png', 'legacy/themes/default/images', 'legacy/themes/default/images'),
new SplFileInfo('legacy_image.png', 'legacy/themes/default/images', 'legacy/themes/default/images'),
new SplFileInfo('to_be_overwritten.png', 'legacy/themes/default/images',
'legacy/themes/default/images'),
new SplFileInfo('to_be_overwritten_with_different_extension.png', 'legacy/themes/default/images',
'legacy/themes/default/images')
],
'/legacy/themes/suite8/images' => [
new SplFileInfo('to_be_overwritten.png', 'legacy/themes/suite8/images', 'legacy/themes/suite8/images'),
new SplFileInfo('to_be_overwritten_with_different_extension.svg', 'legacy/themes/suite8/images',
'legacy/themes/suite8/images')
],
'/core/app/themes/suite8/images' => [
new SplFileInfo('logo.png', 'core/app/themes/suite8/images', 'core/app/themes/suite8/images'),
]
];
/** @var ThemeImageFinder $themeImageFinder */
$themeImageFinder = $this->make(
ThemeImageFinder::class,
[
'find' => static function ($fullPath) use ($mockImages) {
if (empty($mockImages[$fullPath])) {
return [];
}
return $mockImages[$fullPath];
}
]
);
$this->themeImageService = new ThemeImageService(
$themeImagePaths,
$themeImageSupportedTypes,
'',
$themeImageFinder
);
}
protected function _after()
{
}
/**
* Ensure the format of the returned items is the expected
*/
public function testItemFormat(): void
{
$images = $this->themeImageService->get('suite8');
static::assertNotNull($images);
static::assertNotEmpty($images->getItems());
static::assertArrayHasKey('logo', $images->getItems());
$item = $images->getItems()['logo'];
static::assertNotEmpty($item);
static::assertCount(3, $item);
static::assertArrayHasKey('path', $item);
static::assertArrayHasKey('name', $item);
static::assertArrayHasKey('type', $item);
static::assertEquals('logo', $item['name']);
}
/**
* Test image override order
*/
public function testImageOverrides(): void
{
$expected = [
'logo' => [
'path' => 'core/app/themes/suite8/images/logo.png',
'name' => 'logo',
'type' => 'png'
],
'legacy_image' => [
'path' => 'legacy/themes/default/images/legacy_image.png',
'name' => 'legacy_image',
'type' => 'png'
],
'to_be_overwritten_with_different_extension' => [
'path' => 'legacy/themes/suite8/images/to_be_overwritten_with_different_extension.svg',
'name' => 'to_be_overwritten_with_different_extension',
'type' => 'svg'
],
'to_be_overwritten' => [
'path' => 'legacy/themes/suite8/images/to_be_overwritten.png',
'name' => 'to_be_overwritten',
'type' => 'png'
],
];
$images = $this->themeImageService->get('suite8');
static::assertNotNull($images);
static::assertNotEmpty($images->getItems());
static::assertEquals($images->getItems(), $expected);
}
}