Merge branch 'develop' into feature/output-angular-version-in-console-on-startup

This commit is contained in:
Lorenz Hilpert
2020-06-22 11:14:14 +02:00
28 changed files with 418 additions and 78 deletions

View File

@@ -456,6 +456,41 @@
}
}
}
},
"native-container": {
"projectType": "library",
"root": "apps/native-container",
"sourceRoot": "apps/native-container/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "apps/native-container/tsconfig.lib.json",
"project": "apps/native-container/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/native-container/src/test.ts",
"tsConfig": "apps/native-container/tsconfig.spec.json",
"karmaConfig": "apps/native-container/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/native-container/tsconfig.lib.json",
"apps/native-container/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "sales"

View File

@@ -0,0 +1,24 @@
# NativeContainer
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.14.
## Code scaffolding
Run `ng generate component component-name --project native-container` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project native-container`.
> Note: Don't forget to add `--project native-container` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build native-container` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build native-container`, go to the dist folder `cd dist/native-container` and run `npm publish`.
## Running unit tests
Run `ng test native-container` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/native-container'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/native-container",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "native-container",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
}
}

View File

@@ -0,0 +1,3 @@
export * from './native-container.service';
export * from './scan-request.type';
export * from './window-ref.service';

View File

@@ -0,0 +1,89 @@
import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, fromEvent, Subject } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { WindowRef } from './window-ref.service';
import { ScanRequestType } from './scan-request.type';
@Injectable({
providedIn: 'root',
})
export class NativeContainerService {
private wm: Observable<any>;
public windowMessages = new Subject<any>();
private webViewDetected = false;
private webViewEventRecieved = false;
private browserDetected = false;
constructor(private windowRef: WindowRef) {
this.defineWindowCallback();
this.wm = fromEvent(this.windowRef.nativeWindow, 'message').pipe(
map((e: MessageEvent) => {
return e.data;
})
);
this.wm.subscribe((data) => {
if (data.status === 'INIT') {
this.webViewEventRecieved = true;
}
this.windowMessages.next(data);
});
}
public openScanner(scanRequestType: ScanRequestType) {
const scanRequest = {
[scanRequestType]: true,
};
this.sendMessage(scanRequest);
return this.windowMessages.asObservable();
}
public sendMessage(message: any) {
this.windowRef.nativeWindow.postMessage({ status: 'IN_PROGRESS', data: 'Scan Started' }, '*');
if (this.isUiWebview()) {
(this.windowRef.nativeWindow as any).webkit.messageHandlers.scanRequest.postMessage(message);
} else {
this.windowRef.nativeWindow.postMessage({ status: 'ERROR', data: 'Not a WebView' }, '*');
this.windowRef.nativeWindow.postMessage(message, '*');
}
}
public isUiWebview() {
const navigator = this.windowRef.nativeWindow.navigator as Navigator;
const standalone = (navigator as any).standalone,
userAgent = navigator.userAgent.toLowerCase(),
safari = /safari/.test(userAgent),
ios = /iphone|ipod|ipad/.test(userAgent),
chrome = /chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor),
crios = /crios/.test(userAgent);
this.webViewDetected = ios && !standalone && !safari;
this.browserDetected = !standalone && (safari || chrome) && !crios;
return {
isSafari: this.browserDetected,
isNative: this.webViewDetected || this.webViewEventRecieved,
};
}
private defineWindowCallback() {
if (this.windowRef.nativeWindow['scanResults'] === undefined) {
this.windowRef.nativeWindow['scanResults'] = (result) => window.postMessage(result, '*');
}
if (this.windowRef.nativeWindow['isRunningNative'] === undefined) {
this.windowRef.nativeWindow['isRunningNative'] = (_) => window.postMessage({ status: 'INIT', data: 'Is a WebView' }, '*');
}
// Try sending ping request, to invoke the containers isRunningNative event
try {
(this.windowRef.nativeWindow as any).webkit.messageHandlers.scanRequest.postMessage('PING');
} catch (error) {
this.windowRef.nativeWindow.postMessage({ status: 'ERROR', data: 'Not a WebView' }, '*');
this.windowRef.nativeWindow.postMessage('PING', '*');
}
}
}

View File

@@ -0,0 +1,8 @@
export type ScanRequestType =
| 'scanLogin'
| 'scanBook'
| 'scanShelf'
| 'scanCustomer'
| 'remissionContainer'
| 'remissionProduct'
| 'shippingDocument';

View File

@@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WindowRef {
get nativeWindow(): Window {
return window;
}
}

View File

@@ -0,0 +1,5 @@
/*
* Public API Surface of native-container
*/
export * from './lib';

View File

@@ -0,0 +1,21 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,26 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

View File

@@ -5,7 +5,7 @@ import { SsoService } from 'sso';
@Component({
selector: 'app-log-in',
templateUrl: './log-in.component.html',
styleUrls: ['./log-in.component.scss']
styleUrls: ['./log-in.component.scss'],
})
export class LogInComponent implements OnInit {
constructor(private route: ActivatedRoute, private ssoService: SsoService) {}
@@ -13,7 +13,7 @@ export class LogInComponent implements OnInit {
ngOnInit() {
const token = this.route.snapshot.paramMap.get('token');
if (token === null) {
this.ssoService.logoff();
// this.ssoService.logoff();
}
this.logIn(token);

View File

@@ -6,7 +6,7 @@ import { NativeContainerService } from 'shared/lib/barcode-scanner';
@Component({
selector: 'app-log-out',
templateUrl: './log-out.component.html',
styleUrls: ['./log-out.component.scss']
styleUrls: ['./log-out.component.scss'],
})
export class LogOutComponent implements OnInit {
id = 'logout-modal';
@@ -16,7 +16,6 @@ export class LogOutComponent implements OnInit {
logoff() {
this.ssoService.logoff();
this.containerNotificationMessage();
}
openDialog() {
@@ -26,12 +25,4 @@ export class LogOutComponent implements OnInit {
closeModal() {
this.modalService.close(this.id);
}
private containerNotificationMessage() {
// Notify the container app if the user has logged out
// For the purpposes of removing the container Header element
if (this.nativeContainer.isUiWebview()) {
this.nativeContainer.sendMessage({ scanLogin: true });
}
}
}

View File

@@ -223,4 +223,15 @@ export class RemissionSelectors {
return remission.activeView;
}
@Selector([RemissionState])
static getRemissionSupplierId(remissionState: RemissionStateModel) {
const remission = remissionState.remission;
const supplier =
remission.remissionProcess &&
remission.remissionProcess.filter &&
remission.remissionProcess.filter.target;
return supplier && supplier.id;
}
}

View File

@@ -175,11 +175,14 @@ export class CustomerSearchResultComponent implements OnInit {
!user.first_name ||
!user.last_name ||
!user.email ||
!(
isNullOrUndefined(user.delivery_addres) ||
!!(
isNullOrUndefined(user.delivery_addres) &&
isNullOrUndefined(user.invoice_address)
) ||
!(user.delivery_addres.country || user.invoice_address.country)
!(
(user.delivery_addres && user.delivery_addres.country) ||
(user.invoice_address && user.invoice_address.country)
)
) {
return false;
}

View File

@@ -278,32 +278,46 @@ export class RemissionListComponent implements OnInit, OnDestroy {
});
this.isLoading = false;
if ((this.page === 0 || result.skip === 0) && !this.isFullLoad) {
this.products = [];
}
this.remissionHelper.setRemissionListHits(result.hits || -1);
if (result.items && result.items.length > 0) {
if (!this.isItRefetch && !this.isFullLoad) {
this.products = this.filterUniqueProducts([
...this.products,
...result.items,
]);
} else if (this.isItRefetch && !this.isFullLoad) {
this.products.splice(
result.skip,
result.items.length,
...result.items
);
this.isItRefetch = false;
} else if (this.isFullLoad) {
this.products = this.filterUniqueProducts([
...this.products,
...result.items,
]);
this.isFullLoad = false;
this.isFullListLoaded = true;
this.cdr.detectChanges();
this.fullListLoaded.emit();
if (
(this.page === 0 || result.skip === 0) &&
result.completed &&
result.hits > 0 &&
result.items
) {
if (!result.items.length) {
this.products = [...this.products];
} else {
this.products = [...result.items];
}
} else {
if ((this.page === 0 || result.skip === 0) && !this.isFullLoad) {
this.products = [];
}
this.remissionHelper.setRemissionListHits(result.hits || -1);
if (result.items && result.items.length > 0) {
if (!this.isItRefetch && !this.isFullLoad) {
this.products = this.filterUniqueProducts([
...this.products,
...result.items,
]);
} else if (this.isItRefetch && !this.isFullLoad) {
this.products.splice(
result.skip,
result.items.length,
...result.items
);
this.isItRefetch = false;
} else if (this.isFullLoad) {
this.products = this.filterUniqueProducts([
...this.products,
...result.items,
]);
this.isFullLoad = false;
this.isFullListLoaded = true;
this.cdr.detectChanges();
this.fullListLoaded.emit();
}
}
}
@@ -353,7 +367,6 @@ export class RemissionListComponent implements OnInit, OnDestroy {
if (this.page === 0) {
return;
}
this.page = Math.floor(params.numberOfProductsFetched / this.pageSize);
const allProductsFetched = params.numberOfProductsFetched >= this.totalHits;

View File

@@ -24,13 +24,12 @@
}
.selection-container {
@include mq-tablet-portrait() {
max-height: 635px;
}
overflow: scroll;
}
.filter-container {
height: 95%;
max-height: 95%;
position: relative;
margin-left: auto;
margin-right: auto;

View File

@@ -55,6 +55,8 @@ export class RemissionListFilterComponent implements OnInit, OnDestroy {
remissionSource$: Observable<RemissionResourceType>;
@Select(RemissionSelectors.getRemissionFilters)
remissionFilter$: Observable<RemissionSelectedFilters>;
@Select(RemissionSelectors.getRemissionSupplierId)
remissionSupplierId$: Observable<number>;
@Select(RemissionSelectors.getRemissionActiveFilters)
activeFilters$: Observable<{
[filterId: string]: string[];
@@ -144,8 +146,9 @@ export class RemissionListFilterComponent implements OnInit, OnDestroy {
map(([pendingFilters]) =>
this.filterService.mapToRemissionFilter(pendingFilters || [])
),
switchMap((selectedFilters) =>
this.remissionService.getCapacities(selectedFilters)
withLatestFrom(this.remissionSupplierId$),
switchMap(([selectedFilters, supplierId]) =>
this.remissionService.getCapacities({ selectedFilters, supplierId })
)
);
}

View File

@@ -394,24 +394,33 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy {
.pipe(
take(1),
map((process) => process.id),
withLatestFrom(this.currentRemissionTarget$),
withLatestFrom(
this.currentRemissionTarget$,
this.currentRemissionSource$
),
catchError((error) => {
this.store.dispatch(new SetRemissionIsLoading(false));
return [error];
})
)
.subscribe(([remissionProcessId, target]) => {
.subscribe(([remissionProcessId, target, source]) => {
const targetSource = 'zentral';
let newTarget = target;
if (this.isValidSupplierDTO(product.target)) {
newTarget = product.target;
}
this.store.dispatch(
new RequestUpdateRemissionFilter(remissionProcessId, {
source: 'zentral',
target: newTarget,
})
);
const targetHasChanged = newTarget.name !== target;
const sourceHasChanged = targetSource !== source;
if (targetHasChanged || sourceHasChanged) {
this.store.dispatch(
new RequestUpdateRemissionFilter(remissionProcessId, {
source: targetSource,
target: newTarget,
})
);
}
});
this.navigateRemissionCreateList();

View File

@@ -44,7 +44,7 @@ jobs:
demands:
- Agent.OS -equals Linux
- docker
condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/integration'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/integration'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
steps:
- task: npmAuthenticate@0
displayName: 'npm auth'

View File

@@ -2,12 +2,13 @@ import { Injectable } from '@angular/core';
import { OAuthService, JwksValidationHandler } from 'angular-oauth2-oidc';
import { SsoInterface } from './sso.interface';
import { isNullOrUndefined } from 'util';
import { NativeContainerService, ScanRequestType } from 'native-container';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class SsoService {
constructor(private oauthService: OAuthService, private externalService: SsoInterface) {}
constructor(private oauthService: OAuthService, private externalService: SsoInterface, private native: NativeContainerService) {}
public registerAuthentication() {
this.oauthService.configure(this.externalService.getConfigurations());
@@ -17,7 +18,7 @@ export class SsoService {
this.oauthService.setupAutomaticSilentRefresh();
}
return this.oauthService.loadDiscoveryDocumentAndTryLogin().then(_ => {
return this.oauthService.loadDiscoveryDocumentAndTryLogin().then((_) => {
const hasIdToken = !!this.getIdToken();
const hasAccessToken = !!this.getToken();
const isLoggedIn = hasIdToken && hasAccessToken;
@@ -26,7 +27,7 @@ export class SsoService {
this.login();
}
return new Promise(resolve => resolve());
return new Promise((resolve) => resolve());
});
}
@@ -50,7 +51,7 @@ export class SsoService {
this.oauthService.configure(this.externalService.getConfigurations());
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.customQueryParams = {
temp_token: token
temp_token: token,
};
this.login();
}
@@ -61,6 +62,9 @@ export class SsoService {
public logoff() {
this.oauthService.logOut();
if (this.native.isUiWebview().isNative) {
this.native.openScanner('scanLogin');
}
}
public getTokenClaims() {

View File

@@ -45,6 +45,8 @@ $tab-width: 158px;
background-color: $background-active;
color: $font-color-active;
border-radius: $tab-border-radius;
touch-action: none;
pointer-events: none;
}
&:first-of-type {

24
package-lock.json generated
View File

@@ -3261,33 +3261,33 @@
}
},
"@isa/catsearch-api": {
"version": "0.0.55",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/catsearch-api/-/catsearch-api-0.0.55.tgz",
"integrity": "sha1-J29nBWk+aT0zUShXFMfLX+uThZs=",
"version": "0.0.56",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/catsearch-api/-/catsearch-api-0.0.56.tgz",
"integrity": "sha1-VQWugpfYeSER3UnIsYOQVtnd5FA=",
"requires": {
"tslib": "^1.9.0"
}
},
"@isa/print-api": {
"version": "0.0.55",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/print-api/-/print-api-0.0.55.tgz",
"integrity": "sha1-DFH2J4gmqEHHmtpr2NsXmOkVgNY=",
"version": "0.0.56",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/print-api/-/print-api-0.0.56.tgz",
"integrity": "sha1-8cSMtEczwDnSe/C8piozLDmVYMA=",
"requires": {
"tslib": "^1.9.0"
}
},
"@isa/remi-api": {
"version": "0.0.55",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/remi-api/-/remi-api-0.0.55.tgz",
"integrity": "sha1-+s05XrFx0Z9pSVnZMtZsjeOkVjE=",
"version": "0.0.56",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/remi-api/-/remi-api-0.0.56.tgz",
"integrity": "sha1-bQBbsKL7D+j+nrB26qIOaobBjmc=",
"requires": {
"tslib": "^1.9.0"
}
},
"@isa/remission": {
"version": "0.3.23",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/remission/-/remission-0.3.23.tgz",
"integrity": "sha1-hqHKegGcgIFEu4N/pa1BEDriJOc=",
"version": "0.3.28",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/@isa/remission/-/remission-0.3.28.tgz",
"integrity": "sha1-5T0muSdyEYHJzYW6IJJfoShoqyw=",
"requires": {
"tslib": "^1.9.0"
}

View File

@@ -13,6 +13,7 @@
"build:sales:dev": "ng build --configuration=development",
"build:sales:prod": "ng build --prod",
"build:lib": "npm-run-all -l -n build:lib:*",
"build:lib:native-container": "ng build native-container",
"build:lib:sso": "ng build sso",
"build:lib:ui": "ng build ui",
"build:lib:swagger-availability": "ng build @swagger/availability",
@@ -53,10 +54,10 @@
"@cmf/core": "^0.1.33",
"@cmf/inventory-api": "^0.1.33",
"@cmf/trade-api": "^0.1.33",
"@isa/catsearch-api": "^0.0.55",
"@isa/print-api": "0.0.55",
"@isa/remi-api": "^0.0.55",
"@isa/remission": "^0.3.23",
"@isa/catsearch-api": "^0.0.56",
"@isa/print-api": "0.0.56",
"@isa/remi-api": "^0.0.56",
"@isa/remission": "^0.3.28",
"@ng-idle/core": "^8.0.0-beta.4",
"@ng-idle/keepalive": "^8.0.0-beta.4",
"@ngxs/store": "^3.6.2",

View File

@@ -38,7 +38,9 @@
"@swagger/cat": ["dist/swagger/cat"],
"@swagger/cat/*": ["dist/swagger/cat/*"],
"shared": ["libs/shared/src"],
"shared/*": ["libs/shared/src/*"]
"shared/*": ["libs/shared/src/*"],
"native-container": ["dist/native-container"],
"native-container/*": ["dist/native-container/*"]
}
}
}