@sumaris-net/ngx-components 1.21.0-beta3 → 1.21.0-beta30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/bundles/sumaris-net.ngx-components.umd.js +464 -160
  2. package/bundles/sumaris-net.ngx-components.umd.js.map +1 -1
  3. package/bundles/sumaris-net.ngx-components.umd.min.js +2 -2
  4. package/bundles/sumaris-net.ngx-components.umd.min.js.map +1 -1
  5. package/esm2015/src/app/core/menu/menu.component.js +2 -2
  6. package/esm2015/src/app/shared/pipes/date-diff-duration.pipe.js +8 -4
  7. package/esm2015/src/app/social/job/job.model.js +4 -2
  8. package/esm2015/src/app/social/job/job.service.js +2 -2
  9. package/esm2015/src/app/social/job/progression/job-progression.component.js +9 -5
  10. package/esm2015/src/app/social/job/progression/job-progression.list.js +3 -3
  11. package/esm2015/src/app/social/user-event/notification/user-event-notification.component.js +23 -31
  12. package/esm2015/src/app/social/user-event/notification/user-event-notification.list.js +48 -9
  13. package/esm2015/src/app/social/user-event/testing/user-event.testing.service.js +73 -0
  14. package/esm2015/src/app/social/user-event/user-event.model.js +1 -1
  15. package/esm2015/src/app/social/user-event/user-event.module.js +6 -1
  16. package/esm2015/src/app/social/user-event/user-event.service.js +174 -63
  17. package/esm2015/src/app/social/user-event/user-events.table.js +2 -2
  18. package/esm2015/sumaris-net.ngx-components.js +8 -7
  19. package/fesm2015/sumaris-net.ngx-components.js +324 -108
  20. package/fesm2015/sumaris-net.ngx-components.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/app/shared/pipes/date-diff-duration.pipe.d.ts +6 -2
  23. package/src/app/social/job/job.model.d.ts +2 -1
  24. package/src/app/social/user-event/notification/user-event-notification.component.d.ts +5 -8
  25. package/src/app/social/user-event/notification/user-event-notification.list.d.ts +18 -8
  26. package/src/app/social/user-event/testing/user-event.testing.service.d.ts +35 -0
  27. package/src/app/social/user-event/user-event.model.d.ts +1 -2
  28. package/src/app/social/user-event/user-event.service.d.ts +31 -11
  29. package/src/assets/i18n/en.json +2 -1
  30. package/src/assets/i18n/fr.json +2 -1
  31. package/src/theme/_ngx-components.scss +1 -0
  32. package/sumaris-net.ngx-components.d.ts +7 -6
  33. package/sumaris-net.ngx-components.metadata.json +1 -1
@@ -1959,17 +1959,21 @@ class DateDiffDurationPipe {
1959
1959
  return '';
1960
1960
  const startDate = this.dateAdapter.parse(value.startValue, DATE_ISO_PATTERN);
1961
1961
  const endDate = this.dateAdapter.parse(value.endValue, DATE_ISO_PATTERN);
1962
- return this.format(startDate, endDate);
1962
+ return this.format(startDate, endDate, args);
1963
1963
  }
1964
- format(startDate, endDate) {
1964
+ format(startDate, endDate, args) {
1965
1965
  const duration = moment.duration(endDate.diff(startDate));
1966
1966
  if (duration.asMinutes() < 0)
1967
1967
  return '';
1968
+ const withSeconds = args === null || args === void 0 ? void 0 : args.seconds;
1968
1969
  const timeDuration = moment(0)
1969
1970
  .hour(duration.hours())
1970
1971
  .minute(duration.minutes());
1972
+ if (withSeconds) {
1973
+ timeDuration.second(duration.seconds());
1974
+ }
1971
1975
  const days = Math.floor(duration.asDays());
1972
- return (days > 0 ? days.toString() + (this.dayUnit + ' ') : '') + timeDuration.format('HH:mm');
1976
+ return (days > 0 ? days.toString() + (this.dayUnit + ' ') : '') + timeDuration.format(withSeconds ? 'HH:mm:ss' : 'HH:mm');
1973
1977
  }
1974
1978
  }
1975
1979
  DateDiffDurationPipe.ɵprov = ɵɵdefineInjectable({ factory: function DateDiffDurationPipe_Factory() { return new DateDiffDurationPipe(ɵɵinject(DateAdapter), ɵɵinject(TranslateService)); }, token: DateDiffDurationPipe, providedIn: "root" });
@@ -21431,7 +21435,7 @@ class MenuComponent {
21431
21435
  MenuComponent.decorators = [
21432
21436
  { type: Component, args: [{
21433
21437
  selector: 'app-menu',
21434
- template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" contentId=\"menu-content\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px;\"/>\n <span *ngIf=\"!logo\" width=\"108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n\n<!-- todo ajouter ici les 2 composants du module social -->\n\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n",
21438
+ template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" contentId=\"menu-content\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px;\"/>\n <span *ngIf=\"!logo\" width=\"108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n\n<!-- todo ajouter ici les 2 composants du module social ??? -->\n\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n",
21435
21439
  animations: [fadeInAnimation],
21436
21440
  changeDetection: ChangeDetectionStrategy.OnPush,
21437
21441
  styles: ["ion-menu{--ion-item-background:transparent;--ion-item-divider-background:transparent;--ion-item-icon-color:var(--ion-color-primary-tint);--ion-item-text-color:var(--ion-color-primary-tint);--ion-item-background-selected:var(--ion-color-secondary100);--ion-item-text-color-selected:var(--ion-color-primary);--ion-item-icon-color-selected:var(--ion-color-primary);--ion-item-text-color-disable:var(--ion-color-medium);--ion-item-icon-color-disable:var(--ion-color-medium)}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:50%;background-color:var(--ion-color-secondary);border:1px solid var(--ion-color-primary);overflow:hidden!important;font-size:var(--avatar-size,60px)!important;line-height:var(--avatar-size,60px);height:var(--avatar-size,60px)!important;width:var(--avatar-size,60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size,60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu .scroll-content{margin-top:79px!important}ion-menu .has-user-header .scroll-content{margin-top:188px!important}ion-menu ion-content.has-profile-header{--offset-top:188px}ion-menu ion-content.no-profile-header{--offset-top:79px}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color:var(--ion-color-primary-tint);--ion-item-text-color:var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color:var(--ion-color-secondary-tint);--ion-item-text-color:var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color:var(--ion-color-tertiary-tint);--ion-item-text-color:var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color:var(--ion-color-danger-tint);--ion-item-text-color:var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color:var(--ion-color-medium-shade);--ion-item-text-color:var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color:var(--ion-color-dark-tint);--ion-item-text-color:var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item ion-icon,ion-menu ion-content ion-list ion-item mat-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-label,ion-menu ion-content ion-list ion-item ion-text{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover:var(--ion-item-background-selected)!important}ion-menu ion-content ion-list ion-item.selected ion-icon,ion-menu ion-content ion-list ion-item.selected mat-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-label,ion-menu ion-content ion-list ion-item.selected ion-text{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover:var(--ion-item-background-selected)!important;--ion-item-icon-color-selected:var(--ion-item-icon-color)!important;--ion-item-text-color-selected:var(--ion-item-text-color)!important}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width:40px}@media screen and (max-width:767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width:768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size,80px)!important;line-height:var(--avatar-size,80px);height:var(--avatar-size,80px)!important;width:var(--avatar-size,80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size,80px)}}"]
@@ -25952,34 +25956,47 @@ const UserEventFragments = {
25952
25956
  const moment$6 = momentImported;
25953
25957
  const UserEventServiceToken = new InjectionToken('UserEventService');
25954
25958
  class AbstractUserEventService extends BaseGraphqlService {
25955
- constructor(graphql, accountService, network, translate, toastController, queries, mutations, subscriptions, environment) {
25959
+ constructor(graphql, accountService, network, translate, queries, mutations, subscriptions, environment) {
25956
25960
  super(graphql, environment);
25957
25961
  this.graphql = graphql;
25958
25962
  this.accountService = accountService;
25959
25963
  this.network = network;
25960
25964
  this.translate = translate;
25961
- this.toastController = toastController;
25962
25965
  this.queries = queries;
25963
25966
  this.mutations = mutations;
25964
25967
  this.subscriptions = subscriptions;
25965
25968
  this.environment = environment;
25966
- this._count$ = new BehaviorSubject(0);
25967
- this._subscriptions = new Subscription();
25969
+ this.count$ = new BehaviorSubject(0);
25968
25970
  this.listeners = [];
25971
+ this._subscriptions = new Subscription();
25969
25972
  this._logPrefix = '[user-event-service] ';
25970
25973
  // For DEV only
25971
25974
  this._debug = !(environment === null || environment === void 0 ? void 0 : environment.production);
25972
- // listen login/logout
25973
- this._subscriptions.add(accountService.onLogin.subscribe(() => __awaiter(this, void 0, void 0, function* () {
25974
- const count = yield this.count(this.defaultFilter());
25975
- if (count && this._debug) {
25976
- console.debug(`${this._logPrefix}Received ${count} unread notifications`);
25977
- }
25978
- this._count$.next(count);
25979
- })));
25980
25975
  }
25981
25976
  get userEventCount() {
25982
- return this._count$.asObservable();
25977
+ return this.count$.asObservable();
25978
+ }
25979
+ ngOnStart() {
25980
+ return __awaiter(this, void 0, void 0, function* () {
25981
+ // Update component when refresh is need (=login events)
25982
+ this._subscriptions.add(merge(this.accountService.onLogin, this.accountService.onLogout)
25983
+ .pipe(
25984
+ // Wait account service ready (can be restarted)
25985
+ mergeMap(() => this.accountService.ready()), map(() => this.accountService.isLogin()), distinctUntilChanged())
25986
+ .subscribe(login => {
25987
+ if (login) {
25988
+ this.onLogin();
25989
+ }
25990
+ else {
25991
+ this.onLogout();
25992
+ }
25993
+ }));
25994
+ // First attempt if account is ready
25995
+ yield this.accountService.ready();
25996
+ if (this.accountService.isLogin()) {
25997
+ setTimeout(() => this.onLogin());
25998
+ }
25999
+ });
25983
26000
  }
25984
26001
  ngOnDestroy() {
25985
26002
  this._subscriptions.unsubscribe();
@@ -25990,13 +26007,15 @@ class AbstractUserEventService extends BaseGraphqlService {
25990
26007
  watchPage(page, filter, options) {
25991
26008
  var _a;
25992
26009
  let now = this._debug && Date.now();
25993
- //if (this._debug)
26010
+ if (!filter) {
26011
+ filter = this.defaultFilter();
26012
+ }
25994
26013
  console.debug(`${this._logPrefix}Loading user events...`, filter);
25995
26014
  filter = this.asFilter(filter);
25996
26015
  // Force recipient to current issuer, if not admin and not specified
25997
- if (isEmptyArray(filter.recipients) || !this.accountService.isAdmin()) {
26016
+ if (isEmptyArray(filter === null || filter === void 0 ? void 0 : filter.recipients) || !this.accountService.isAdmin()) {
25998
26017
  const recipient = this.defaultRecipient();
25999
- if (!((_a = filter.recipients) === null || _a === void 0 ? void 0 : _a.includes(recipient))) {
26018
+ if (!((_a = filter === null || filter === void 0 ? void 0 : filter.recipients) === null || _a === void 0 ? void 0 : _a.includes(recipient))) {
26000
26019
  console.warn('[user-events-service] Force user event filter.recipient=' + recipient);
26001
26020
  filter.recipients = [recipient];
26002
26021
  }
@@ -26006,23 +26025,26 @@ class AbstractUserEventService extends BaseGraphqlService {
26006
26025
  queryName: withContent ? 'LoadAllWithContent' : 'LoadAll',
26007
26026
  query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
26008
26027
  variables: {
26009
- page: Object.assign({ sortBy: 'updateDate', sortDirection: 'desc' }, page),
26028
+ page: Object.assign(Object.assign({ sortBy: 'creationDate' }, page), {
26029
+ // @ts-ignore
26030
+ sortDirection: (page.sortDirection || 'desc').toUpperCase() }),
26010
26031
  filter: filter && filter.asPodObject()
26011
26032
  },
26012
26033
  arrayFieldName: 'data',
26013
26034
  error: { code: SocialErrorCodes.LOAD_USER_EVENTS_ERROR, message: 'SOCIAL.ERROR.LOAD_USER_EVENTS_ERROR' },
26014
- fetchPolicy: (options === null || options === void 0 ? void 0 : options.fetchPolicy) || "cache-first"
26035
+ fetchPolicy: (options === null || options === void 0 ? void 0 : options.fetchPolicy) || 'cache-first'
26015
26036
  })
26016
26037
  .pipe(map(res => {
26017
- const data = ((res === null || res === void 0 ? void 0 : res.data) || []).map(this.processUserEvent);
26038
+ let data = ((res === null || res === void 0 ? void 0 : res.data) || []).map(value => this.processUserEvent(value, this));
26018
26039
  if (now) {
26019
26040
  console.debug(`${this._logPrefix}${data.length} user events loaded in ${Date.now() - now}ms`);
26020
26041
  now = null;
26021
26042
  }
26022
- const unreadCount = this.unreadEvents(data);
26043
+ // Must re-order because of listenChanges result will add new event at the end
26044
+ data = EntityUtils.sort(data, page.sortBy, page.sortDirection);
26023
26045
  return {
26024
26046
  data,
26025
- total: toNumber(res === null || res === void 0 ? void 0 : res.total, unreadCount)
26047
+ total: toNumber(res === null || res === void 0 ? void 0 : res.total, data.length)
26026
26048
  };
26027
26049
  }));
26028
26050
  }
@@ -26036,7 +26058,7 @@ class AbstractUserEventService extends BaseGraphqlService {
26036
26058
  data: entity
26037
26059
  });
26038
26060
  // Update count
26039
- this._count$.next(this._count$.value + 1);
26061
+ this.count$.next(this.count$.value + 1);
26040
26062
  }
26041
26063
  count(filter, options) {
26042
26064
  var _a;
@@ -26046,7 +26068,9 @@ class AbstractUserEventService extends BaseGraphqlService {
26046
26068
  return undefined;
26047
26069
  }
26048
26070
  if (!filter)
26049
- throw new Error(`Missing argument 'filter'`);
26071
+ filter = this.defaultFilter();
26072
+ // Apply last reset date as start date
26073
+ filter.startDate = this.resetCountDate;
26050
26074
  filter = this.asFilter(filter);
26051
26075
  if (this._debug)
26052
26076
  console.debug(`${this._logPrefix}Counting user events with filter:`, filter);
@@ -26061,6 +26085,35 @@ class AbstractUserEventService extends BaseGraphqlService {
26061
26085
  return res.total;
26062
26086
  });
26063
26087
  }
26088
+ listenCountChanges(filter, options) {
26089
+ var _a;
26090
+ if (!((_a = this.subscriptions) === null || _a === void 0 ? void 0 : _a.listenCountChanges)) {
26091
+ console.warn(`${this._logPrefix}Subscription query 'listenCountChanges' not provided, skip.`);
26092
+ return of();
26093
+ }
26094
+ if (!filter)
26095
+ filter = this.defaultFilter();
26096
+ // Apply last reset date as start date
26097
+ filter.startDate = this.resetCountDate;
26098
+ filter = this.asFilter(filter);
26099
+ if (this._debug)
26100
+ console.debug(`${this._logPrefix}[WS] Listening count changes for user events with filter:`, filter);
26101
+ return this.graphql.subscribe({
26102
+ query: this.subscriptions.listenCountChanges,
26103
+ fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
26104
+ variables: { filter: filter.asPodObject(), interval: toNumber(options === null || options === void 0 ? void 0 : options.interval, 10) },
26105
+ error: {
26106
+ code: SocialErrorCodes.SUBSCRIBE_USER_EVENTS_ERROR,
26107
+ message: 'SOCIAL.ERROR.SUBSCRIBE_USER_EVENTS_ERROR'
26108
+ }
26109
+ }).pipe(map(({ total }) => {
26110
+ if (total && this._debug)
26111
+ console.debug(`${this._logPrefix}Received new user events count:`, total);
26112
+ // update count
26113
+ this.count$.next(total);
26114
+ return total;
26115
+ }));
26116
+ }
26064
26117
  listenChanges(filter, options) {
26065
26118
  var _a;
26066
26119
  if (!((_a = this.subscriptions) === null || _a === void 0 ? void 0 : _a.listenChanges)) {
@@ -26068,7 +26121,7 @@ class AbstractUserEventService extends BaseGraphqlService {
26068
26121
  return of();
26069
26122
  }
26070
26123
  if (!filter)
26071
- throw new Error(`Missing argument 'filter'`);
26124
+ filter = this.defaultFilter();
26072
26125
  filter = this.asFilter(filter);
26073
26126
  const withContent = (options === null || options === void 0 ? void 0 : options.withContent) === true;
26074
26127
  if (this._debug)
@@ -26084,14 +26137,12 @@ class AbstractUserEventService extends BaseGraphqlService {
26084
26137
  }).pipe(map(({ data }) => {
26085
26138
  if (data && this._debug)
26086
26139
  console.debug(`${this._logPrefix}Received new user events:`, data);
26087
- const updatedData = data === null || data === void 0 ? void 0 : data.map(this.processUserEvent);
26140
+ const updatedData = data === null || data === void 0 ? void 0 : data.map(value => this.processUserEvent(value, this));
26088
26141
  // Update cache
26089
26142
  this.insertIntoMutableCachedQueries(this.graphql.cache, {
26090
26143
  query: withContent ? this.queries.loadAllWithContent : this.queries.loadAll,
26091
26144
  data: updatedData
26092
26145
  });
26093
- // update count
26094
- this._count$.next(this.unreadEvents(updatedData));
26095
26146
  return updatedData;
26096
26147
  }));
26097
26148
  }
@@ -26136,7 +26187,7 @@ class AbstractUserEventService extends BaseGraphqlService {
26136
26187
  return __awaiter(this, void 0, void 0, function* () {
26137
26188
  this.fillDefaultProperties(entity);
26138
26189
  // Transform into json
26139
- const isNew = isNil(entity.id);
26190
+ const withContent = !!entity.content;
26140
26191
  const json = entity.asObject();
26141
26192
  const now = Date.now();
26142
26193
  if (this._debug)
@@ -26155,39 +26206,48 @@ class AbstractUserEventService extends BaseGraphqlService {
26155
26206
  console.debug(`${this._logPrefix}User event saved in ${Date.now() - now}ms`, entity);
26156
26207
  this.copyIdAndUpdateDate(savedEntity, entity);
26157
26208
  // Add to cache
26158
- if (isNew) {
26159
- this.insertIntoMutableCachedQueries(proxy, {
26160
- query: this.queries.loadAll,
26161
- data: Object.assign(Object.assign({}, savedEntity), { content: null })
26162
- });
26209
+ if (withContent) {
26163
26210
  this.insertIntoMutableCachedQueries(proxy, {
26164
26211
  query: this.queries.loadAllWithContent,
26165
26212
  data: savedEntity
26166
26213
  });
26167
26214
  }
26215
+ else {
26216
+ this.insertIntoMutableCachedQueries(proxy, {
26217
+ query: this.queries.loadAll,
26218
+ data: Object.assign(Object.assign({}, savedEntity), { content: null })
26219
+ });
26220
+ }
26168
26221
  }
26169
26222
  }
26170
26223
  });
26171
26224
  return entity;
26172
26225
  });
26173
26226
  }
26174
- saveAll(data, opts) {
26175
- return Promise.all(data
26176
- .map(entity => this.save(entity, opts)));
26227
+ saveAll(entities, opts) {
26228
+ return __awaiter(this, void 0, void 0, function* () {
26229
+ return Promise.all(entities
26230
+ .map(entity => this.save(entity, opts)));
26231
+ });
26177
26232
  }
26178
- markAsRead(entity) {
26233
+ markAsRead(entities) {
26179
26234
  return __awaiter(this, void 0, void 0, function* () {
26180
- // Get onRead listeners
26181
- const onReadListeners = (this.listeners || []).filter(listener => !!listener.onRead).filter(listener => listener.accept(entity));
26182
- if (onReadListeners.length) {
26183
- for (const listener of onReadListeners) {
26184
- yield listener.onRead(entity);
26235
+ const userEventWithoutReadListeners = [];
26236
+ for (const entity of (entities || [])) {
26237
+ // Get onRead listeners
26238
+ const onReadListeners = (this.listeners || []).filter(listener => !!listener.onRead).filter(listener => listener.accept(entity));
26239
+ if (onReadListeners.length) {
26240
+ for (const listener of onReadListeners) {
26241
+ yield listener.onRead(entity);
26242
+ }
26243
+ }
26244
+ else {
26245
+ // Add to this list for default operation
26246
+ userEventWithoutReadListeners.push(entity);
26185
26247
  }
26186
26248
  }
26187
- else {
26188
- // Use default
26189
- yield this.defaultMarkAsRead(entity);
26190
- }
26249
+ // Use default
26250
+ yield this.defaultMarkAsRead(userEventWithoutReadListeners);
26191
26251
  });
26192
26252
  }
26193
26253
  registerListener(listener) {
@@ -26195,37 +26255,93 @@ class AbstractUserEventService extends BaseGraphqlService {
26195
26255
  this.listeners = [];
26196
26256
  this.listeners.push(listener);
26197
26257
  }
26258
+ resetCount() {
26259
+ this.count$.next(undefined);
26260
+ this.resetCountDate = moment$6();
26261
+ // restart listening count changes
26262
+ this.startListenCountChanges();
26263
+ }
26198
26264
  /* -- protected methods -- */
26199
- processUserEvent(source) {
26265
+ onLogin() {
26266
+ return __awaiter(this, void 0, void 0, function* () {
26267
+ // Get first count
26268
+ const count = yield this.count(undefined);
26269
+ if (this._debug)
26270
+ console.debug(`${this._logPrefix}Receiving user events count`, count);
26271
+ // Update count
26272
+ this.count$.next(count);
26273
+ // Then listen changes
26274
+ this.startListenCountChanges();
26275
+ });
26276
+ }
26277
+ onLogout() {
26278
+ this.stopListenCountChanges();
26279
+ }
26280
+ startListenCountChanges() {
26281
+ this.stopListenCountChanges();
26282
+ this._listenSubscription = this.listenCountChanges(undefined).subscribe();
26283
+ }
26284
+ stopListenCountChanges() {
26285
+ var _a;
26286
+ (_a = this._listenSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
26287
+ this._listenSubscription = undefined;
26288
+ }
26289
+ processUserEvent(source, that) {
26290
+ that = that || this;
26200
26291
  // Convert json source to IUserEvent
26201
- let target = this.fromObject(source);
26292
+ let target = that.fromObject(source);
26202
26293
  // Get listeners
26203
- const listeners = (this.listeners || []).filter(listener => listener.onReceived).filter(listener => listener.accept(target));
26294
+ const listeners = (that.listeners || []).filter(listener => listener.onReceived).filter(listener => listener.accept(target));
26204
26295
  for (const listener of listeners) {
26205
26296
  if (target) {
26206
26297
  target = listener.onReceived(target);
26207
26298
  }
26208
26299
  else {
26209
- if (this._debug)
26210
- console.debug(`${this._logPrefix}Event has been rejected by a listener`, source);
26300
+ if (that._debug)
26301
+ console.debug(`${that._logPrefix}Event has been rejected by a listener`, source);
26211
26302
  break;
26212
26303
  }
26213
26304
  }
26214
26305
  return target;
26215
26306
  }
26216
- defaultMarkAsRead(userEvent) {
26307
+ defaultMarkAsRead(userEvents) {
26308
+ var _a;
26217
26309
  return __awaiter(this, void 0, void 0, function* () {
26218
- if (!userEvent)
26310
+ if (!userEvents)
26219
26311
  throw new Error(`${this._logPrefix}Invalid event to save`);
26312
+ if (!userEvents.length)
26313
+ return;
26220
26314
  // set read date
26221
- userEvent.readDate = moment$6();
26222
- if (!userEvent.id || userEvent.id < 0) {
26315
+ userEvents.forEach(userEvent => userEvent.readDate = moment$6());
26316
+ // split local/remote entities
26317
+ const localEntities = userEvents.filter(userEvent => !userEvent.id || userEvent.id < 0);
26318
+ const remoteEntities = userEvents.filter(userEvent => userEvent.id > 0);
26319
+ // save local entities
26320
+ if (localEntities.length) {
26223
26321
  if (this._debug)
26224
26322
  console.debug(`${this._logPrefix}Save local user event`);
26225
26323
  // TODO !!!!!!!!!!!!!
26226
26324
  }
26227
- else {
26228
- yield this.save(userEvent);
26325
+ // mark remote entities as read
26326
+ if (remoteEntities.length) {
26327
+ if (!((_a = this.mutations) === null || _a === void 0 ? void 0 : _a.markAsRead)) {
26328
+ console.warn(`${this._logPrefix}Mutation query 'markAsRead' not provided, skip.`);
26329
+ return;
26330
+ }
26331
+ // Collect ids
26332
+ const ids = remoteEntities.map(value => value.id);
26333
+ const now = Date.now();
26334
+ if (this._debug)
26335
+ console.debug(`${this._logPrefix}Mark user events as read...`, ids);
26336
+ yield this.graphql.mutate({
26337
+ mutation: this.mutations.markAsRead,
26338
+ variables: {
26339
+ ids
26340
+ },
26341
+ error: { code: SocialErrorCodes.SAVE_USER_EVENT_ERROR, message: 'SOCIAL.ERROR.SAVE_USER_EVENT_ERROR' },
26342
+ });
26343
+ if (this._debug)
26344
+ console.debug(`${this._logPrefix}User events marked as read in ${Date.now() - now}ms`, ids);
26229
26345
  }
26230
26346
  });
26231
26347
  }
@@ -26262,7 +26378,6 @@ AbstractUserEventService.ctorParameters = () => [
26262
26378
  { type: AccountService },
26263
26379
  { type: NetworkService },
26264
26380
  { type: TranslateService },
26265
- { type: ToastController },
26266
26381
  { type: undefined },
26267
26382
  { type: undefined },
26268
26383
  { type: undefined },
@@ -26303,7 +26418,7 @@ const userEventMutations = {
26303
26418
  };
26304
26419
  class UserEventService extends AbstractUserEventService {
26305
26420
  constructor(graphql, accountService, network, translate, toastController, environment) {
26306
- super(graphql, accountService, network, translate, toastController, userEventQueries, userEventMutations, undefined, environment);
26421
+ super(graphql, accountService, network, translate, userEventQueries, userEventMutations, undefined, environment);
26307
26422
  this.graphql = graphql;
26308
26423
  this.accountService = accountService;
26309
26424
  this.network = network;
@@ -26548,7 +26663,7 @@ class UserEventsTable extends AppTable {
26548
26663
  UserEventsTable.decorators = [
26549
26664
  { type: Component, args: [{
26550
26665
  selector: 'app-user-events-table',
26551
- template: "\n<!-- Type = options menu -->\n<mat-menu #optionsMenu=\"matMenu\" xPosition=\"after\">\n\n <!-- display columns -->\n <button mat-menu-item\n (click)=\"openSelectColumnsModal($event)\">\n <mat-icon>view_column</mat-icon>\n <ion-label translate>COMMON.DISPLAYED_COLUMNS_DOTS</ion-label>\n </button>\n\n</mat-menu>\n\n<!-- top header -->\n<mat-toolbar>\n\n <ng-container *ngIf=\"!selection.hasValue(); else hasSelection\">\n\n <button mat-icon-button [title]=\"'COMMON.BTN_REFRESH'|translate\" (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n </ng-container>\n\n <!-- if row selection -->\n <ng-template #hasSelection>\n\n <!-- delete -->\n <button mat-icon-button class=\"hidden-xs hidden-sm\" *ngIf=\"canDelete\"\n [title]=\"'COMMON.BTN_DELETE'|translate\" (click)=\"deleteSelection($event)\">\n <mat-icon>delete</mat-icon>\n </button>\n </ng-template>\n\n <!-- error -->\n <ion-item *ngIf=\"error\" hidden-xs hidden-sm hidden-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button mat-icon-button\n [title]=\"'COMMON.BTN_OPTIONS'|translate\"\n [matMenuTriggerFor]=\"optionsMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n</mat-toolbar>\n\n\n<mat-table [dataSource]=\"dataSource\" matSort\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear [trackBy]=\"trackByFn\">\n\n <!-- select -->\n <ng-container matColumnDef=\"select\">\n <mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!canEdit\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!canEdit\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </mat-cell>\n </ng-container>\n\n <!-- id -->\n <ng-container matColumnDef=\"id\">\n <mat-header-cell *matHeaderCellDef mat-sort-header [class.cdk-visually-hidden]=\"!isAdmin\">\n <app-loading-spinner [loading]=\"loadingSubject|async\"><ion-label>#</ion-label></app-loading-spinner>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!isAdmin\">{{ row.currentData.id | mathAbs }}</mat-cell>\n </ng-container>\n\n <!-- creationDate -->\n <ng-container matColumnDef=\"creationDate\">\n <mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>SOCIAL.USER_EVENT.CREATION_DATE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <ion-text>\n {{row.currentData.creationDate | dateFromNow }}<br/>\n <ion-text color=\"medium\"><small>{{row.currentData.creationDate | dateFormat: {time: true} }}</small></ion-text>\n </ion-text>\n </mat-cell>\n </ng-container>\n\n <!-- icon -->\n <ng-container matColumnDef=\"icon\">\n <mat-header-cell *matHeaderCellDef>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <ng-container *ngIf=\"getIcon(row.currentData); let details\">\n <ion-icon slot=\"start\" *ngIf=\"details.icon\" [name]=\"details.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"details.matIcon\">{{details.matIcon}}</mat-icon>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <!-- event type -->\n <ng-container matColumnDef=\"eventType\">\n <mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>SOCIAL.USER_EVENT.EVENT_TYPE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <span>{{'SOCIAL.USER_EVENT.TYPE_ENUM.' + row.currentData.eventType | translate}}</span>\n </mat-cell>\n </ng-container>\n\n <!-- message -->\n <ng-container matColumnDef=\"message\">\n <mat-header-cell *matHeaderCellDef>\n <ion-label translate>SOCIAL.USER_EVENT.MESSAGE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <ng-container *ngIf=\"getDetail(row.currentData); let details\">\n <ion-text [innerHTML]=\"details.title|translate\">\n </ion-text>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <!-- Actions buttons column -->\n <ng-container matColumnDef=\"actions\">\n <mat-header-cell *matHeaderCellDef>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <ng-container *ngIf=\"getDetail(row.currentData); let details\">\n <button mat-icon-button color=\"light\"\n *ngFor=\"let action of details.actions\"\n [title]=\"action.title |translate\"\n [color]=\"action.color\"\n (click)=\"doAction(action, row)\">\n <ion-icon slot=\"start\" *ngIf=\"action.icon\" [name]=\"action.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"action.matIcon\">{{action.matIcon}}</mat-icon>\n </button>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <mat-header-row *matHeaderRowDef=\"displayedColumns\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n class=\"mat-row-disabled\"\n (click)=\"clickRow($event, row)\"></mat-row>\n\n</mat-table>\n\n<ion-row class=\"ion-no-padding\">\n <ion-col></ion-col>\n <ion-col class=\"ion-no-padding\" size=\"auto\">\n <mat-paginator [length]=\"totalRowCount\" [pageSize]=\"defaultPageSize\" [pageSizeOptions]=\"defaultPageSizeOptions\" showFirstLastButtons>\n </mat-paginator>\n </ion-col>\n</ion-row>\n",
26666
+ template: "\n<!-- Type = options menu -->\n<mat-menu #optionsMenu=\"matMenu\" xPosition=\"after\">\n\n <!-- display columns -->\n <button mat-menu-item\n (click)=\"openSelectColumnsModal($event)\">\n <mat-icon>view_column</mat-icon>\n <ion-label translate>COMMON.DISPLAYED_COLUMNS_DOTS</ion-label>\n </button>\n\n</mat-menu>\n\n<!-- top header -->\n<mat-toolbar>\n\n <ng-container *ngIf=\"!selection.hasValue(); else hasSelection\">\n\n <button mat-icon-button [title]=\"'COMMON.BTN_REFRESH'|translate\" (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n </ng-container>\n\n <!-- if row selection -->\n <ng-template #hasSelection>\n\n <!-- delete -->\n <button mat-icon-button class=\"hidden-xs hidden-sm\" *ngIf=\"canDelete\"\n [title]=\"'COMMON.BTN_DELETE'|translate\" (click)=\"deleteSelection($event)\">\n <mat-icon>delete</mat-icon>\n </button>\n </ng-template>\n\n <!-- error -->\n <ion-item *ngIf=\"error\" hidden-xs hidden-sm hidden-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button mat-icon-button\n [title]=\"'COMMON.BTN_OPTIONS'|translate\"\n [matMenuTriggerFor]=\"optionsMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n</mat-toolbar>\n\n\n<mat-table [dataSource]=\"dataSource\" matSort\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n matSortDisableClear [trackBy]=\"trackByFn\">\n\n <!-- select -->\n <ng-container matColumnDef=\"select\">\n <mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!canEdit\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!canEdit\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </mat-cell>\n </ng-container>\n\n <!-- id -->\n <ng-container matColumnDef=\"id\">\n <mat-header-cell *matHeaderCellDef mat-sort-header [class.cdk-visually-hidden]=\"!isAdmin\">\n <app-loading-spinner [loading]=\"loadingSubject|async\"><ion-label>#</ion-label></app-loading-spinner>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!isAdmin\">{{ row.currentData.id | mathAbs }}</mat-cell>\n </ng-container>\n\n <!-- creationDate -->\n <ng-container matColumnDef=\"creationDate\">\n <mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>SOCIAL.USER_EVENT.CREATION_DATE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <ion-text>\n {{row.currentData.creationDate | dateFromNow }}<br/>\n <ion-text color=\"medium\"><small>{{row.currentData.creationDate | dateFormat: {time: true} }}</small></ion-text>\n </ion-text>\n </mat-cell>\n </ng-container>\n\n <!-- icon -->\n <ng-container matColumnDef=\"icon\">\n <mat-header-cell *matHeaderCellDef>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <ng-container *ngIf=\"getIcon(row.currentData); let details\">\n <ion-icon slot=\"start\" *ngIf=\"details.icon\" [name]=\"details.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"details.matIcon\">{{details.matIcon}}</mat-icon>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <!-- event type -->\n <ng-container matColumnDef=\"eventType\">\n <mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label translate>SOCIAL.USER_EVENT.EVENT_TYPE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <span>{{'SOCIAL.USER_EVENT.TYPE_ENUM.' + row.currentData.eventType | translate}}</span>\n </mat-cell>\n </ng-container>\n\n <!-- message -->\n <ng-container matColumnDef=\"message\">\n <mat-header-cell *matHeaderCellDef>\n <ion-label translate>SOCIAL.USER_EVENT.MESSAGE</ion-label>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row;\">\n <ng-container *ngIf=\"getDetail(row.currentData); let details\">\n <ion-text [innerHTML]=\"details.title|translate\">\n </ion-text>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <!-- Actions buttons column -->\n <ng-container matColumnDef=\"actions\">\n <mat-header-cell *matHeaderCellDef>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <ng-container *ngIf=\"getDetail(row.currentData); let details\">\n <button mat-icon-button color=\"light\"\n *ngFor=\"let action of details.actions\"\n [title]=\"action.title |translate\"\n [color]=\"action.color\"\n (click)=\"doAction(action, row)\">\n <app-icon slot=\"start\" *ngIf=\"action.iconRef\" [ref]=\"action.iconRef\"></app-icon>\n </button>\n </ng-container>\n </mat-cell>\n </ng-container>\n\n <mat-header-row *matHeaderRowDef=\"displayedColumns\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n class=\"mat-row-disabled\"\n (click)=\"clickRow($event, row)\"></mat-row>\n\n</mat-table>\n\n<ion-row class=\"ion-no-padding\">\n <ion-col></ion-col>\n <ion-col class=\"ion-no-padding\" size=\"auto\">\n <mat-paginator [length]=\"totalRowCount\" [pageSize]=\"defaultPageSize\" [pageSizeOptions]=\"defaultPageSizeOptions\" showFirstLastButtons>\n </mat-paginator>\n </ion-col>\n</ion-row>\n",
26552
26667
  changeDetection: ChangeDetectionStrategy.OnPush,
26553
26668
  providers: [
26554
26669
  {
@@ -26574,17 +26689,47 @@ UserEventsTable.propDecorators = {
26574
26689
  };
26575
26690
 
26576
26691
  class UserEventNotificationList {
26577
- constructor(popoverController) {
26692
+ constructor(cd, popoverController, userEventService) {
26693
+ this.cd = cd;
26578
26694
  this.popoverController = popoverController;
26695
+ this.userEventService = userEventService;
26696
+ this.debug = false;
26697
+ this.userEvents = new BehaviorSubject(undefined);
26698
+ this.subscriptions = new Subscription();
26579
26699
  }
26580
26700
  ngOnInit() {
26701
+ // Watch all user events
26702
+ this.subscriptions.add(this.userEventService
26703
+ .watchAll(0, 10, 'creationDate', "desc", undefined)
26704
+ .subscribe(result => {
26705
+ if (this.debug)
26706
+ console.debug(`[user-event-notification-list] receiving ${result.total} user events`, result.data);
26707
+ this.userEvents.next(result.data);
26708
+ this.markForCheck();
26709
+ }));
26710
+ // Listen changes
26711
+ this.subscriptions.add(this.userEventService.listenChanges(undefined)
26712
+ .subscribe(data => {
26713
+ if (this.debug)
26714
+ console.debug(`[user-event-notification-list] receiving ${data.length} new user events`, data);
26715
+ }));
26716
+ }
26717
+ ngOnDestroy() {
26718
+ this.subscriptions.unsubscribe();
26581
26719
  }
26582
- read($event, userEvent) {
26720
+ read(event, userEvent) {
26583
26721
  var _a;
26584
26722
  if (userEvent === null || userEvent === void 0 ? void 0 : userEvent.readDate)
26585
26723
  return;
26586
26724
  (_a = this.readEvent) === null || _a === void 0 ? void 0 : _a.emit(userEvent);
26587
26725
  }
26726
+ readAll(event) {
26727
+ var _a;
26728
+ const unreadUserEvents = (this.userEvents.value || []).filter(value => !value.readDate);
26729
+ if (!unreadUserEvents.length)
26730
+ return;
26731
+ (_a = this.readEvents) === null || _a === void 0 ? void 0 : _a.emit(unreadUserEvents);
26732
+ }
26588
26733
  executeAction(action, userEvent) {
26589
26734
  // Execute then close popover
26590
26735
  action.executeAction(userEvent);
@@ -26593,69 +26738,74 @@ class UserEventNotificationList {
26593
26738
  dismiss() {
26594
26739
  this.popoverController.dismiss();
26595
26740
  }
26741
+ markForCheck() {
26742
+ this.cd.markForCheck();
26743
+ }
26596
26744
  }
26597
26745
  UserEventNotificationList.decorators = [
26598
26746
  { type: Component, args: [{
26599
26747
  selector: 'app-user-event-notification-list',
26600
- template: "\n<ion-list class=\"ion-list-popover\">\n\n <ion-row class=\"ion-list-header column\">\n <ion-col>\n <ion-label>{{titleI18n | translate}}</ion-label>\n </ion-col>\n </ion-row>\n\n <ion-item *ngIf=\"!(userEvents | async)?.length\">\n {{'SOCIAL.USER_EVENT.NOTIFICATION.EMPTY' | translate}}\n </ion-item>\n <ion-item\n *ngFor=\"let userEvent of userEvents | async\"\n (click)=\"read($event, userEvent)\"\n >\n\n <ion-avatar slot=\"start\">\n\n <img *ngIf=\"userEvent.avatar; else avatarIcon\" src=\"{{ userEvent.avatar }}\">\n\n <ng-template #avatarIcon>\n <app-icon\n *ngIf=\"userEvent.avatarIcon\"\n [ref]=\"userEvent.avatarIcon\"\n height=\"40\"\n width=\"40\"\n ></app-icon>\n </ng-template>\n\n </ion-avatar>\n\n <ion-grid class=\"ion-no-margin ion-no-padding\">\n <ion-row>\n <ion-col>\n <p [class.unread]=\"!userEvent.readDate\">\n {{userEvent.message}}\n </p>\n </ion-col>\n </ion-row>\n <ion-row *ngIf=\"userEvent.actions?.length\">\n <ion-col>\n <p>\n <span *ngFor=\"let action of userEvent.actions\">\n <a\n [style.margin-right.px]=\"8\"\n [style.cursor]=\"'pointer'\"\n (click)=\"executeAction(action, userEvent)\"\n >\n {{ '&#10142;&nbsp;' + action.name}}\n </a>\n </span>\n </p>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <app-icon\n *ngIf=\"userEvent.icon\"\n [ref]=\"userEvent.icon\"\n height=\"16\"\n width=\"16\"\n [style.vertical-align]=\"'sub'\"\n ></app-icon>\n <small>\n <span>{{userEvent.creationDate|dateFromNow}}</span>\n <span [style.color]=\"'gray'\">{{ ' | ' + (userEvent.creationDate|dateFormat:{time:true}) }}</span>\n </small>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n\n</ion-list>\n",
26601
- styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}.unread{font-weight:700}"]
26748
+ template: "\n<ion-list class=\"ion-list-popover\">\n\n <ion-row class=\"ion-list-header column\">\n <ion-col>\n <ion-label>{{titleI18n | translate}}</ion-label>\n </ion-col>\n <ion-col class=\"top-action\">\n <a (click)=\"readAll($event)\">{{'SOCIAL.USER_EVENT.NOTIFICATION.READ_ALL' | translate}}</a>\n </ion-col>\n </ion-row>\n\n <ion-item *ngIf=\"!(userEvents | async)?.length\">\n {{'SOCIAL.USER_EVENT.NOTIFICATION.EMPTY' | translate}}\n </ion-item>\n <ion-item\n *ngFor=\"let userEvent of userEvents | async\"\n (click)=\"read($event, userEvent)\"\n >\n\n <ion-avatar slot=\"start\">\n\n <img *ngIf=\"userEvent.avatar; else avatarIcon\" src=\"{{ userEvent.avatar }}\">\n\n <ng-template #avatarIcon>\n <app-icon\n *ngIf=\"userEvent.avatarIcon\"\n [ref]=\"userEvent.avatarIcon\"\n height=\"40\"\n width=\"40\"\n ></app-icon>\n </ng-template>\n\n </ion-avatar>\n\n <ion-grid class=\"ion-no-margin ion-no-padding message\">\n <ion-row>\n <ion-col>\n <p [class.unread]=\"!userEvent.readDate\" [innerHTML]=\"userEvent.message\"></p>\n </ion-col>\n </ion-row>\n <ion-row *ngIf=\"userEvent.actions?.length\">\n <ion-col>\n <span *ngFor=\"let action of userEvent.actions\" class=\"action\">\n <a (click)=\"executeAction(action, userEvent)\">\n <app-icon *ngIf=\"action.iconRef\" slot=\"start\" height=\"20\" width=\"20\" [ref]=\"action.iconRef\"></app-icon>\n {{ action.name }}\n </a>\n </span>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <app-icon\n *ngIf=\"userEvent.icon\"\n [ref]=\"userEvent.icon\"\n height=\"16\"\n width=\"16\"\n style=\"vertical-align: sub; margin-right: 3px\"\n ></app-icon>\n <small>\n <span>{{userEvent.creationDate|dateFromNow}}</span>\n <span style=\"color: gray\">{{ ' | ' + (userEvent.creationDate|dateFormat:{time:true}) }}</span>\n </small>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n\n <ion-row class=\"ion-list-footer column\">\n </ion-row>\n\n</ion-list>\n",
26749
+ changeDetection: ChangeDetectionStrategy.OnPush,
26750
+ styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}.unread{font-weight:700}.message{font-size:.9em}.message p{margin-top:5px;margin-bottom:5px}.top-action{text-align:end;margin-right:8px}.top-action a{cursor:pointer;color:#f5f5f5;font-style:italic}.action a{margin-right:10px;cursor:pointer}.action app-icon{vertical-align:middle}"]
26602
26751
  },] }
26603
26752
  ];
26604
26753
  UserEventNotificationList.ctorParameters = () => [
26605
- { type: PopoverController }
26754
+ { type: ChangeDetectorRef },
26755
+ { type: PopoverController },
26756
+ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [UserEventServiceToken,] }] }
26606
26757
  ];
26607
26758
  UserEventNotificationList.propDecorators = {
26759
+ debug: [{ type: Input }],
26608
26760
  titleI18n: [{ type: Input }],
26609
- userEvents: [{ type: Input }],
26610
- readEvent: [{ type: Output }]
26761
+ readEvent: [{ type: Output }],
26762
+ readEvents: [{ type: Output }]
26611
26763
  };
26612
26764
 
26613
26765
  class UserEventNotificationComponent {
26614
- constructor(userEventService, accountService, popoverController, cd) {
26766
+ constructor(userEventService, accountService, popoverController) {
26615
26767
  this.userEventService = userEventService;
26616
26768
  this.accountService = accountService;
26617
26769
  this.popoverController = popoverController;
26618
- this.cd = cd;
26619
26770
  this.debug = false;
26620
26771
  this.titleI18n = 'SOCIAL.USER_EVENT.NOTIFICATION.TITLE';
26621
26772
  this.disabled = false;
26622
- this.userEventsSubject = new BehaviorSubject(undefined);
26623
- this._subscriptions = new Subscription();
26624
26773
  this._logPrefix = '[user-event-notification] ';
26625
26774
  this._readEvent = new EventEmitter();
26775
+ this._readEvents = new EventEmitter();
26626
26776
  }
26627
26777
  get count() {
26628
26778
  var _a;
26629
26779
  return (_a = this.userEventService) === null || _a === void 0 ? void 0 : _a.userEventCount.pipe(map(value => value === 0 ? undefined : value));
26630
26780
  }
26631
26781
  ngOnInit() {
26632
- var _a;
26633
- if (isNil(this.userEventService)) {
26634
- console.warn(`${this._logPrefix}No service injected`);
26635
- this.disabled = true;
26636
- return;
26637
- }
26638
- this._subscriptions.add(this.userEventService.watchAll(0, 10, 'creationDate', "desc", {
26639
- recipients: [(_a = this.accountService.person) === null || _a === void 0 ? void 0 : _a.pubkey]
26640
- }).subscribe(value => {
26641
- const data = value.data;
26642
- if (this.debug) {
26643
- console.debug(`${this._logPrefix}Receiving user events`, data);
26782
+ return __awaiter(this, void 0, void 0, function* () {
26783
+ if (isNil(this.userEventService)) {
26784
+ console.warn(`${this._logPrefix}No service injected`);
26785
+ this.disabled = true;
26786
+ return;
26644
26787
  }
26645
- this.userEventsSubject.next(data);
26646
- }));
26647
- this._readEvent.subscribe(value => {
26648
- this.userEventService.markAsRead(value);
26788
+ // Subscribe to read event
26789
+ this._readEvent.subscribe(value => {
26790
+ // default: mark event as read
26791
+ this.userEventService.markAsRead([value]);
26792
+ });
26793
+ this._readEvents.subscribe(value => {
26794
+ this.userEventService.markAsRead(value);
26795
+ });
26649
26796
  });
26650
26797
  }
26651
26798
  showList(event) {
26652
26799
  return __awaiter(this, void 0, void 0, function* () {
26800
+ // Reset count
26801
+ this.userEventService.resetCount();
26653
26802
  const popover = yield this.popoverController.create({
26654
26803
  component: UserEventNotificationList,
26655
26804
  componentProps: {
26805
+ debug: this.debug,
26656
26806
  titleI18n: this.titleI18n,
26657
- userEvents: this.userEventsSubject,
26658
- readEvent: this._readEvent
26807
+ readEvent: this._readEvent,
26808
+ readEvents: this._readEvents
26659
26809
  },
26660
26810
  backdropDismiss: true,
26661
26811
  keyboardClose: true,
@@ -26666,14 +26816,10 @@ class UserEventNotificationComponent {
26666
26816
  yield popover.present();
26667
26817
  });
26668
26818
  }
26669
- markForCheck() {
26670
- this.cd.markForCheck();
26671
- }
26672
26819
  ngOnDestroy() {
26673
26820
  if (this.debug) {
26674
26821
  console.debug(`${this._logPrefix}Destroying`);
26675
26822
  }
26676
- this._subscriptions.unsubscribe();
26677
26823
  }
26678
26824
  }
26679
26825
  UserEventNotificationComponent.decorators = [
@@ -26687,8 +26833,7 @@ UserEventNotificationComponent.decorators = [
26687
26833
  UserEventNotificationComponent.ctorParameters = () => [
26688
26834
  { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [UserEventServiceToken,] }] },
26689
26835
  { type: AccountService },
26690
- { type: PopoverController },
26691
- { type: ChangeDetectorRef }
26836
+ { type: PopoverController }
26692
26837
  ];
26693
26838
  UserEventNotificationComponent.propDecorators = {
26694
26839
  debug: [{ type: Input }],
@@ -26696,6 +26841,69 @@ UserEventNotificationComponent.propDecorators = {
26696
26841
  disabled: [{ type: Input }]
26697
26842
  };
26698
26843
 
26844
+ class UserEventTestService extends AbstractUserEventService {
26845
+ constructor(graphql, accountService, network, translate, environment) {
26846
+ super(graphql, accountService, network, translate, undefined, undefined, undefined, environment);
26847
+ this.graphql = graphql;
26848
+ this.accountService = accountService;
26849
+ this.network = network;
26850
+ this.translate = translate;
26851
+ this.environment = environment;
26852
+ this.userEvents = [];
26853
+ }
26854
+ fromObject(source) {
26855
+ return UserEvent.fromObject(source);
26856
+ }
26857
+ add(entity) {
26858
+ entity = this.processUserEvent(entity);
26859
+ entity.id = this.userEvents.length + 1;
26860
+ this.userEvents.push(entity);
26861
+ this.count$.next(this.userEvents.length);
26862
+ }
26863
+ asFilter(filter) {
26864
+ return filter;
26865
+ }
26866
+ count(filter, options) {
26867
+ return Promise.resolve(this.userEvents.length);
26868
+ }
26869
+ delete(entity) {
26870
+ return Promise.resolve(undefined);
26871
+ }
26872
+ deleteAll(data, opts) {
26873
+ return Promise.resolve(undefined);
26874
+ }
26875
+ listenChanges(filter, options) {
26876
+ return of();
26877
+ }
26878
+ save(entity, options) {
26879
+ var _a;
26880
+ const toSave = (_a = this.userEvents) === null || _a === void 0 ? void 0 : _a.find(value => value.id == (entity === null || entity === void 0 ? void 0 : entity.id));
26881
+ Object.assign(toSave, entity);
26882
+ return entity;
26883
+ }
26884
+ saveAll(data, opts) {
26885
+ return __awaiter(this, void 0, void 0, function* () {
26886
+ for (const entity of (data || [])) {
26887
+ yield this.save(entity);
26888
+ }
26889
+ return Promise.resolve(this.userEvents);
26890
+ });
26891
+ }
26892
+ watchAll(offset, size, sortBy, sortDirection, filter, options) {
26893
+ return of({ data: this.userEvents });
26894
+ }
26895
+ }
26896
+ UserEventTestService.decorators = [
26897
+ { type: Injectable }
26898
+ ];
26899
+ UserEventTestService.ctorParameters = () => [
26900
+ { type: GraphqlService },
26901
+ { type: AccountService },
26902
+ { type: NetworkService },
26903
+ { type: TranslateService },
26904
+ { type: Environment, decorators: [{ type: Optional }, { type: Inject, args: [ENVIRONMENT,] }] }
26905
+ ];
26906
+
26699
26907
  class UserEventModule {
26700
26908
  }
26701
26909
  UserEventModule.decorators = [
@@ -26713,6 +26921,9 @@ UserEventModule.decorators = [
26713
26921
  exports: [
26714
26922
  UserEventsTable,
26715
26923
  UserEventNotificationComponent
26924
+ ],
26925
+ providers: [
26926
+ { provide: UserEventServiceToken, useClass: UserEventTestService }
26716
26927
  ]
26717
26928
  },] }
26718
26929
  ];
@@ -26953,11 +27164,13 @@ class Job {
26953
27164
  }
26954
27165
  // @dynamic
26955
27166
  let JobProgression = JobProgression_1 = class JobProgression extends Entity {
26956
- constructor() {
27167
+ constructor(id) {
26957
27168
  super(JobProgression_1.TYPENAME);
27169
+ this.id = id;
26958
27170
  }
26959
27171
  fromObject(source) {
26960
27172
  super.fromObject(source);
27173
+ this.name = source.name;
26961
27174
  this.message = source.message;
26962
27175
  this.current = source.current;
26963
27176
  this.total = source.total;
@@ -26971,10 +27184,10 @@ const JobProgressionServiceToken = new InjectionToken('JobProgressionService');
26971
27184
  const jobProgressionSubscription = gql `subscription UpdateJobProgression($id: Int!, $interval: Int){
26972
27185
  data: updateJobProgression(id: $id, interval: $interval) {
26973
27186
  id
27187
+ name
26974
27188
  message
26975
27189
  current
26976
27190
  total
26977
- updateDate
26978
27191
  }
26979
27192
  }`;
26980
27193
  class JobProgressionService extends BaseGraphqlService {
@@ -27021,8 +27234,8 @@ class JobProgressionList {
27021
27234
  JobProgressionList.decorators = [
27022
27235
  { type: Component, args: [{
27023
27236
  selector: 'app-job-progression-list',
27024
- template: "<ion-list class=\"ion-list-popover\">\n\n <ion-row class=\"ion-list-header column\">\n <ion-col>\n <ion-label>{{titleI18n | translate}}</ion-label>\n </ion-col>\n </ion-row>\n\n <ion-item *ngIf=\"!jobProgressions?.length\">\n {{'SOCIAL.JOB.PROGRESSION.EMPTY' | translate}}\n </ion-item>\n <ion-item *ngFor=\"let jobProgression of jobProgressions\">\n <ion-grid class=\"ion-no-margin\">\n <ion-row>\n <ion-col>\n {{jobProgression.message}}\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <ion-progress-bar\n [value]=\"jobProgression.total > 0 ? jobProgression.current / jobProgression.total : undefined\"\n [type]=\"jobProgression.total > 0 ? 'determinate' : 'indeterminate'\">\n </ion-progress-bar>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n\n</ion-list>\n",
27025
- styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}"]
27237
+ template: "<ion-list class=\"ion-list-popover\">\n\n <ion-row class=\"ion-list-header column\">\n <ion-col>\n <ion-label>{{titleI18n | translate}}</ion-label>\n </ion-col>\n </ion-row>\n\n <ion-item *ngIf=\"!jobProgressions?.length\">\n {{'SOCIAL.JOB.PROGRESSION.EMPTY' | translate}}\n </ion-item>\n <ion-item *ngFor=\"let jobProgression of jobProgressions\" class=\"ion-item-job-progression\">\n <ion-grid class=\"ion-no-margin\">\n <ion-row class=\"name\">\n <ion-col>\n {{jobProgression.name}}\n </ion-col>\n </ion-row>\n <ion-row class=\"message\">\n <ion-col>\n {{jobProgression.message}}\n </ion-col>\n </ion-row>\n <ion-row class=\"progress-bar\">\n <ion-col>\n <ion-progress-bar\n [value]=\"jobProgression.total > 0 ? jobProgression.current / jobProgression.total : undefined\"\n [type]=\"jobProgression.total > 0 ? 'determinate' : 'indeterminate'\">\n </ion-progress-bar>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-item>\n\n</ion-list>\n",
27238
+ styles: [":host(.popover-viewport){overflow-y:auto;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ion-item-job-progression{font-size:.9em}.ion-item-job-progression .message{font-style:italic}.ion-item-job-progression .progress-bar{margin-top:5px;margin-bottom:5px}"]
27026
27239
  },] }
27027
27240
  ];
27028
27241
  JobProgressionList.ctorParameters = () => [];
@@ -27065,13 +27278,16 @@ class JobProgressionComponent {
27065
27278
  console.warn(`${this._logPrefix}No service injected. Can't add a job`);
27066
27279
  return;
27067
27280
  }
27068
- if (this.debug) {
27069
- console.debug(`${this._logPrefix}Add job id=${id}`);
27070
- }
27071
27281
  if (!!this.getProgression(id)) {
27072
27282
  console.warn(`${this._logPrefix}Job (id=${id}) already present`);
27073
27283
  return;
27074
27284
  }
27285
+ if (this.debug) {
27286
+ console.debug(`${this._logPrefix}Add job id=${id}`);
27287
+ }
27288
+ // Adding empty job progression
27289
+ const progression = new JobProgression(id);
27290
+ this.jobProgressions.push(progression);
27075
27291
  this.disabled = false;
27076
27292
  const sub = this.jobProgressionService.listenChanges(id)
27077
27293
  .pipe(filter(progression => {
@@ -27086,7 +27302,7 @@ class JobProgressionComponent {
27086
27302
  // Get current progression object
27087
27303
  const currentProgression = this.getProgression(id);
27088
27304
  if (!currentProgression) {
27089
- // add it
27305
+ // add it if absent
27090
27306
  this.jobProgressions.push(progression);
27091
27307
  if (this.debug) {
27092
27308
  console.debug(`${this._logPrefix}Progression added id=${id}`, progression);
@@ -29179,5 +29395,5 @@ CoreTestingModule.decorators = [
29179
29395
  * Generated bundle index. Do not edit.
29180
29396
  */
29181
29397
 
29182
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_TESTING_PAGES, AboutModal, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AndroidOsEnvironment, AnimationState, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppForm, AppFormField, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppInMemoryTable, AppInstallUpgradeCard, AppListForm, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppTabEditor, AppTabEditorOptions, AppTable, AppTableDataSourceOptions, AppTableUtils, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AuthForm, AuthGuardService, AuthModal, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments$1 as Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, Job, JobModule, JobProgression, JobProgressionComponent, JobProgressionService, JobProgressionServiceToken, JobUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatBadgeIconDirective, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, ResizableComponent, ResizableDirective, ResizableModule, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBadgeIconModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, Software, StartableService, StatusById, StatusIds, StatusList, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEvent, UserEventFilter, UserEventFragments, UserEventModule, UserEventNotificationComponent, UserEventService, UserEventServiceToken, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, isAndroid, isBlankString, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moment$5 as moment, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, tz, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForTrue, waitIdle, waitWhilePending, ɵ0, CustomReuseStrategy as ɵa, MatNumpadDomService as ɵb, isFocusableElement as ɵc, RegisterForm as ɵd, AccountValidatorService as ɵe, RegisterModal as ɵf, UserSettingsValidatorService as ɵg, LocalSettingsValidatorService as ɵh, AppUpdateOfflineModeCard as ɵi, AppIconComponent as ɵj, UserEventNotificationList as ɵk, JobProgressionList as ɵl, DateTestPage as ɵm, NumpadTestPage as ɵn, MatBadgeIconTestPage as ɵo, ToastTestingModule as ɵp, ToastTestingPage as ɵq };
29398
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_TESTING_PAGES, AboutModal, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AndroidOsEnvironment, AnimationState, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppForm, AppFormField, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppInMemoryTable, AppInstallUpgradeCard, AppListForm, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppTabEditor, AppTabEditorOptions, AppTable, AppTableDataSourceOptions, AppTableUtils, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AuthForm, AuthGuardService, AuthModal, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments$1 as Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, Job, JobModule, JobProgression, JobProgressionComponent, JobProgressionService, JobProgressionServiceToken, JobUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatBadgeIconDirective, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, ResizableComponent, ResizableDirective, ResizableModule, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBadgeIconModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, Software, StartableService, StatusById, StatusIds, StatusList, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEvent, UserEventFilter, UserEventFragments, UserEventModule, UserEventNotificationComponent, UserEventService, UserEventServiceToken, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, isAndroid, isBlankString, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moment$5 as moment, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, tz, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForTrue, waitIdle, waitWhilePending, ɵ0, CustomReuseStrategy as ɵa, MatNumpadDomService as ɵb, isFocusableElement as ɵc, RegisterForm as ɵd, AccountValidatorService as ɵe, RegisterModal as ɵf, UserSettingsValidatorService as ɵg, LocalSettingsValidatorService as ɵh, AppUpdateOfflineModeCard as ɵi, AppIconComponent as ɵj, UserEventNotificationList as ɵk, UserEventTestService as ɵl, JobProgressionList as ɵm, DateTestPage as ɵn, NumpadTestPage as ɵo, MatBadgeIconTestPage as ɵp, ToastTestingModule as ɵq, ToastTestingPage as ɵr };
29183
29399
  //# sourceMappingURL=sumaris-net.ngx-components.js.map