ngx-edu-sharing-ui 0.7.0

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 (171) hide show
  1. package/.browserslistrc +16 -0
  2. package/.eslintrc.json +44 -0
  3. package/README.md +40 -0
  4. package/assets/scss/mixins.scss +95 -0
  5. package/assets/scss/variables.scss +33 -0
  6. package/karma.conf.js +42 -0
  7. package/ng-package.json +10 -0
  8. package/package.json +19 -0
  9. package/src/lib/actionbar/actionbar.component.html +59 -0
  10. package/src/lib/actionbar/actionbar.component.scss +123 -0
  11. package/src/lib/actionbar/actionbar.component.ts +174 -0
  12. package/src/lib/common/edu-sharing-ui-common.module.ts +80 -0
  13. package/src/lib/directives/border-box-observer.directive.ts +75 -0
  14. package/src/lib/directives/check-text-overflow.directive.ts +61 -0
  15. package/src/lib/directives/drag-nodes/drag-nodes.ts +32 -0
  16. package/src/lib/directives/drag-nodes/nodes-drag-source.directive.ts +79 -0
  17. package/src/lib/directives/drag-nodes/nodes-drag.directive.ts +43 -0
  18. package/src/lib/directives/drag-nodes/nodes-drop-target.directive.ts +116 -0
  19. package/src/lib/directives/focus-state.directive.ts +34 -0
  20. package/src/lib/directives/icon.directive.ts +142 -0
  21. package/src/lib/directives/nodes-drop-target-legacy.directive.ts +155 -0
  22. package/src/lib/dropdown/dropdown.component.html +32 -0
  23. package/src/lib/dropdown/dropdown.component.scss +67 -0
  24. package/src/lib/dropdown/dropdown.component.ts +71 -0
  25. package/src/lib/edu-sharing-ui-configuration.ts +47 -0
  26. package/src/lib/edu-sharing-ui.module.ts +49 -0
  27. package/src/lib/list-items/available-widgets.ts +30 -0
  28. package/src/lib/list-items/format-duration.pipe.ts +17 -0
  29. package/src/lib/list-items/list-base/list-base.component.html +52 -0
  30. package/src/lib/list-items/list-base/list-base.component.ts +44 -0
  31. package/src/lib/list-items/list-collection-info/list-collection-info.component.html +48 -0
  32. package/src/lib/list-items/list-collection-info/list-collection-info.component.scss +8 -0
  33. package/src/lib/list-items/list-collection-info/list-collection-info.component.ts +24 -0
  34. package/src/lib/list-items/list-counts/list-counts.component.html +1 -0
  35. package/src/lib/list-items/list-counts/list-counts.component.scss +3 -0
  36. package/src/lib/list-items/list-counts/list-counts.component.ts +59 -0
  37. package/src/lib/list-items/list-items.module.ts +33 -0
  38. package/src/lib/list-items/list-node-license/list-node-license.component.html +8 -0
  39. package/src/lib/list-items/list-node-license/list-node-license.component.ts +47 -0
  40. package/src/lib/list-items/list-node-replication-source/list-node-replication-source.component.html +11 -0
  41. package/src/lib/list-items/list-node-replication-source/list-node-replication-source.component.ts +60 -0
  42. package/src/lib/list-items/list-node-workflow/list-node-workflow.component.html +3 -0
  43. package/src/lib/list-items/list-node-workflow/list-node-workflow.component.ts +21 -0
  44. package/src/lib/list-items/list-text/list-text.component.html +176 -0
  45. package/src/lib/list-items/list-text/list-text.component.scss +3 -0
  46. package/src/lib/list-items/list-text/list-text.component.ts +107 -0
  47. package/src/lib/list-items/list-widget.ts +52 -0
  48. package/src/lib/list-items/node-row/node-row.component.html +31 -0
  49. package/src/lib/list-items/node-row/node-row.component.scss +50 -0
  50. package/src/lib/list-items/node-row/node-row.component.ts +16 -0
  51. package/src/lib/list-items/node-source.pipe.ts +48 -0
  52. package/src/lib/node-entries/combined-data-source.ts +51 -0
  53. package/src/lib/node-entries/custom-templates-data-source.ts +6 -0
  54. package/src/lib/node-entries/drag-preview/drag-preview.component.html +6 -0
  55. package/src/lib/node-entries/drag-preview/drag-preview.component.scss +35 -0
  56. package/src/lib/node-entries/drag-preview/drag-preview.component.ts +15 -0
  57. package/src/lib/node-entries/entries-model.ts +120 -0
  58. package/src/lib/node-entries/items-cap.ts +54 -0
  59. package/src/lib/node-entries/list-item-label.pipe.ts +28 -0
  60. package/src/lib/node-entries/mixins.scss +23 -0
  61. package/src/lib/node-entries/node-cache.spec.ts +199 -0
  62. package/src/lib/node-entries/node-cache.ts +81 -0
  63. package/src/lib/node-entries/node-data-source-remote.ts +33 -0
  64. package/src/lib/node-entries/node-data-source.ts +148 -0
  65. package/src/lib/node-entries/node-entries-card/node-entries-card.component.html +167 -0
  66. package/src/lib/node-entries/node-entries-card/node-entries-card.component.scss +28 -0
  67. package/src/lib/node-entries/node-entries-card/node-entries-card.component.ts +132 -0
  68. package/src/lib/node-entries/node-entries-card/node-entries-card.main.scss +261 -0
  69. package/src/lib/node-entries/node-entries-card-grid/node-entries-card-grid.component.html +205 -0
  70. package/src/lib/node-entries/node-entries-card-grid/node-entries-card-grid.component.scss +181 -0
  71. package/src/lib/node-entries/node-entries-card-grid/node-entries-card-grid.component.ts +361 -0
  72. package/src/lib/node-entries/node-entries-card-small/node-entries-card-small.component.html +100 -0
  73. package/src/lib/node-entries/node-entries-card-small/node-entries-card-small.component.scss +46 -0
  74. package/src/lib/node-entries/node-entries-card-small/node-entries-card-small.component.ts +40 -0
  75. package/src/lib/node-entries/node-entries-global-options/node-entries-global-options.component.html +23 -0
  76. package/src/lib/node-entries/node-entries-global-options/node-entries-global-options.component.scss +58 -0
  77. package/src/lib/node-entries/node-entries-global-options/node-entries-global-options.component.ts +16 -0
  78. package/src/lib/node-entries/node-entries-global.service.ts +79 -0
  79. package/src/lib/node-entries/node-entries-table/column-chooser/column-chooser.component.html +25 -0
  80. package/src/lib/node-entries/node-entries-table/column-chooser/column-chooser.component.scss +32 -0
  81. package/src/lib/node-entries/node-entries-table/column-chooser/column-chooser.component.ts +31 -0
  82. package/src/lib/node-entries/node-entries-table/node-entries-table.component.html +270 -0
  83. package/src/lib/node-entries/node-entries-table/node-entries-table.component.scss +169 -0
  84. package/src/lib/node-entries/node-entries-table/node-entries-table.component.ts +333 -0
  85. package/src/lib/node-entries/node-entries-templates.service.ts +31 -0
  86. package/src/lib/node-entries/node-entries-wrapper.component.ts +363 -0
  87. package/src/lib/node-entries/node-entries.component.html +33 -0
  88. package/src/lib/node-entries/node-entries.component.scss +13 -0
  89. package/src/lib/node-entries/node-entries.component.ts +151 -0
  90. package/src/lib/node-entries/node-entries.module.ts +93 -0
  91. package/src/lib/node-entries/node-rating/node-rating.component.html +53 -0
  92. package/src/lib/node-entries/node-rating/node-rating.component.scss +31 -0
  93. package/src/lib/node-entries/node-rating/node-rating.component.ts +105 -0
  94. package/src/lib/node-entries/node-stats-badges/node-stats-badges.component.html +39 -0
  95. package/src/lib/node-entries/node-stats-badges/node-stats-badges.component.scss +44 -0
  96. package/src/lib/node-entries/node-stats-badges/node-stats-badges.component.ts +43 -0
  97. package/src/lib/node-entries/node-type-badge/node-type-badge.component.html +31 -0
  98. package/src/lib/node-entries/node-type-badge/node-type-badge.component.scss +5 -0
  99. package/src/lib/node-entries/node-type-badge/node-type-badge.component.ts +36 -0
  100. package/src/lib/node-entries/option-button/option-button.component.ts +42 -0
  101. package/src/lib/node-entries/preview-image/preview-image.component.html +19 -0
  102. package/src/lib/node-entries/preview-image/preview-image.component.scss +31 -0
  103. package/src/lib/node-entries/preview-image/preview-image.component.ts +47 -0
  104. package/src/lib/node-entries/sort-select-panel/sort-select-panel.component.html +27 -0
  105. package/src/lib/node-entries/sort-select-panel/sort-select-panel.component.scss +9 -0
  106. package/src/lib/node-entries/sort-select-panel/sort-select-panel.component.ts +26 -0
  107. package/src/lib/node-url/node-url.component.html +66 -0
  108. package/src/lib/node-url/node-url.component.scss +32 -0
  109. package/src/lib/node-url/node-url.component.ts +136 -0
  110. package/src/lib/pipes/file-size.pipe.ts +24 -0
  111. package/src/lib/pipes/format-date.pipe.ts +39 -0
  112. package/src/lib/pipes/node-icon.pipe.ts +11 -0
  113. package/src/lib/pipes/node-image-size.pipe.ts +18 -0
  114. package/src/lib/pipes/node-image.pipe.ts +71 -0
  115. package/src/lib/pipes/node-person-name.pipe.ts +41 -0
  116. package/src/lib/pipes/node-title.pipe.ts +12 -0
  117. package/src/lib/pipes/option-tooltip.pipe.ts +32 -0
  118. package/src/lib/pipes/replace-chars.pipe.ts +21 -0
  119. package/src/lib/pipes/vcard-name.pipe.ts +11 -0
  120. package/src/lib/services/abstract/app.service.ts +4 -0
  121. package/src/lib/services/abstract/keyboard-shortcuts.service.ts +10 -0
  122. package/src/lib/services/abstract/options-helper.service.ts +29 -0
  123. package/src/lib/services/abstract/toast.service.ts +5 -0
  124. package/src/lib/services/accessibility.service.ts +101 -0
  125. package/src/lib/services/local-events.service.ts +29 -0
  126. package/src/lib/services/node-entries.service.ts +172 -0
  127. package/src/lib/services/node-helper.service.ts +239 -0
  128. package/src/lib/services/nodes-drag-drop.service.ts +165 -0
  129. package/src/lib/services/options-helper-data.service.ts +186 -0
  130. package/src/lib/services/repo-url.service.ts +46 -0
  131. package/src/lib/services/temporary-storage.service.ts +58 -0
  132. package/src/lib/services/ui.service.ts +182 -0
  133. package/src/lib/sort-dropdown/sort-dropdown.component.html +22 -0
  134. package/src/lib/sort-dropdown/sort-dropdown.component.scss +47 -0
  135. package/src/lib/sort-dropdown/sort-dropdown.component.ts +42 -0
  136. package/src/lib/spinner/spinner.component.html +14 -0
  137. package/src/lib/spinner/spinner.component.scss +141 -0
  138. package/src/lib/spinner/spinner.component.ts +12 -0
  139. package/src/lib/translations/README.md +44 -0
  140. package/src/lib/translations/fallback-translation-handler.ts +7 -0
  141. package/src/lib/translations/languages.ts +6 -0
  142. package/src/lib/translations/translation-loader.spec.ts +352 -0
  143. package/src/lib/translations/translation-loader.ts +189 -0
  144. package/src/lib/translations/translation-source.ts +9 -0
  145. package/src/lib/translations/translations.module.ts +49 -0
  146. package/src/lib/translations/translations.service.spec.ts +152 -0
  147. package/src/lib/translations/translations.service.ts +188 -0
  148. package/src/lib/types/accessibillity.ts +15 -0
  149. package/src/lib/types/api-models.ts +4 -0
  150. package/src/lib/types/drag-drop.ts +22 -0
  151. package/src/lib/types/keyboard-shortcuts.ts +29 -0
  152. package/src/lib/types/list-item.ts +67 -0
  153. package/src/lib/types/option-item.ts +247 -0
  154. package/src/lib/types/workflow.ts +35 -0
  155. package/src/lib/util/DateHelper.spec.ts +112 -0
  156. package/src/lib/util/DateHelper.ts +197 -0
  157. package/src/lib/util/VCard.ts +277 -0
  158. package/src/lib/util/color-helper.ts +125 -0
  159. package/src/lib/util/duration-helper.spec.ts +35 -0
  160. package/src/lib/util/duration-helper.ts +98 -0
  161. package/src/lib/util/functions.ts +15 -0
  162. package/src/lib/util/helper.ts +60 -0
  163. package/src/lib/util/isNumeric.ts +13 -0
  164. package/src/lib/util/rest-helper.ts +28 -0
  165. package/src/lib/util/ui-animation.ts +154 -0
  166. package/src/lib/util/ui-constants.ts +20 -0
  167. package/src/module.ts +76 -0
  168. package/src/test.ts +28 -0
  169. package/tsconfig.lib.json +15 -0
  170. package/tsconfig.lib.prod.json +10 -0
  171. package/tsconfig.spec.json +17 -0
@@ -0,0 +1,53 @@
1
+ <ng-container *ngIf="hasPermission && node.type === 'ccm:io' && isFromHomeRepo(node)">
2
+ <div *ngIf="mode === 'likes'" class="likes">
3
+ <button
4
+ (click)="toogleLike()"
5
+ [disabled]="(this.authenticationService.observeLoginInfo() | async)?.isGuest"
6
+ mat-icon-button
7
+ color="primary"
8
+ matTooltip="{{ 'RATING.LIKES' | translate }}"
9
+ >
10
+ <i [esIcon]="'edu-thumb_up' + (!node.rating?.user ? '_off' : '')"></i>
11
+ </button>
12
+ <span matTooltip="{{ 'RATING.COUNT_LIKES' | translate }}">{{
13
+ node.rating?.overall.count
14
+ }}</span>
15
+ </div>
16
+ <div *ngIf="mode === 'stars'" class="stars" mat-icon-button color="primary">
17
+ <button
18
+ *ngFor="let rating of [1, 2, 3, 4, 5]"
19
+ mat-icon-button
20
+ color="primary"
21
+ class="star"
22
+ (click)="setRating(rating)"
23
+ [disabled]="(this.authenticationService.observeLoginInfo() | async)?.isGuest"
24
+ (focus)="hoverStar = rating"
25
+ (mouseenter)="hoverStar = rating"
26
+ (mouseleave)="hoverStar = 0"
27
+ (blur)="hoverStar = 0"
28
+ [class.star-hover]="hoverStar >= rating"
29
+ >
30
+ <i
31
+ [esIcon]="
32
+ 'star' +
33
+ ((hoverStar ? hoverStar >= rating : getPrimaryRating() >= rating) ? '' : '_border')
34
+ "
35
+ matTooltip="{{ 'RATING.STARS_RATE' | translate : { rating: rating } }}"
36
+ ></i>
37
+ </button>
38
+ <button
39
+ *ngIf="!!node.rating?.user"
40
+ mat-icon-button
41
+ color="primary"
42
+ class="delete"
43
+ matTooltip="{{ 'RATING.DELETE_RATING' | translate }}"
44
+ (click)="deleteRating()"
45
+ [disabled]="(this.authenticationService.observeLoginInfo() | async)?.isGuest"
46
+ >
47
+ <i esIcon="clear"></i>
48
+ </button>
49
+ <span matTooltip="{{ 'RATING.COUNT_RATINGS' | translate }}"
50
+ >({{ node.rating?.overall.count }})</span
51
+ >
52
+ </div>
53
+ </ng-container>
@@ -0,0 +1,31 @@
1
+ .stars {
2
+ display: flex;
3
+ align-items: center;
4
+ span {
5
+ padding-left: 8px;
6
+ }
7
+ }
8
+ .likes {
9
+ display: flex;
10
+ align-items: center;
11
+ }
12
+ .mat-icon-button.mat-primary {
13
+ color: var(--textLight);
14
+ &.likes {
15
+ }
16
+ &.star,
17
+ &.delete {
18
+ min-width: unset;
19
+ width: 18px;
20
+ height: 18px;
21
+ ::ng-deep {
22
+ i {
23
+ font-size: 17px;
24
+ line-height: 1;
25
+ }
26
+ }
27
+ &.star-hover {
28
+ color: var(--colorStarActive);
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,105 @@
1
+ import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
2
+ import {
3
+ Node,
4
+ ConfigService,
5
+ AuthenticationService,
6
+ RestConstants,
7
+ NetworkService,
8
+ } from 'ngx-edu-sharing-api';
9
+ import { Toast } from '../../services/abstract/toast.service';
10
+ import { take, takeUntil } from 'rxjs/operators';
11
+ import { RestHelper } from '../../util/rest-helper';
12
+ @Component({
13
+ selector: 'es-node-rating',
14
+ templateUrl: 'node-rating.component.html',
15
+ styleUrls: ['node-rating.component.scss'],
16
+ })
17
+ export class NodeRatingComponent<T extends Node> implements OnInit {
18
+ // @TODO
19
+ ratingService: any = {};
20
+ @Input() node: T;
21
+ mode: RatingMode;
22
+ hasPermission: boolean;
23
+ hoverStar: number;
24
+ constructor(
25
+ public toast: Toast,
26
+ public configService: ConfigService,
27
+ private networkApi: NetworkService,
28
+ public authenticationService: AuthenticationService,
29
+ public changeDetectorRef: ChangeDetectorRef, // @TODO // public ratingService: RestRatingService,
30
+ ) {}
31
+
32
+ async ngOnInit() {
33
+ await this.configService.observeConfig().pipe(take(1)).toPromise();
34
+ this.mode = this.configService.instant('rating.mode', 'none');
35
+ this.hasPermission = await this.authenticationService.hasToolpermission(
36
+ RestConstants.TOOLPERMISSION_RATE_READ,
37
+ );
38
+ }
39
+
40
+ async toogleLike() {
41
+ const name = RestHelper.getTitle(this.node);
42
+ if (this.node.rating?.user) {
43
+ try {
44
+ await this.ratingService.deleteNodeRating(this.node.ref.id).toPromise();
45
+ this.toast.toast('RATING.TOAST.LIKE_REMOVED', { name });
46
+ this.node.rating.user = 0;
47
+ this.node.rating.overall.count--;
48
+ } catch (e) {
49
+ this.toast.error(e);
50
+ }
51
+ } else {
52
+ try {
53
+ await this.ratingService.updateNodeRating(this.node.ref.id, 5).toPromise();
54
+ this.toast.toast('RATING.TOAST.LIKED', { name });
55
+ this.node.rating.user = 5;
56
+ this.node.rating.overall.count++;
57
+ } catch (e) {
58
+ this.toast.error(e);
59
+ }
60
+ }
61
+ this.changeDetectorRef.detectChanges();
62
+ }
63
+
64
+ getPrimaryRating() {
65
+ if (!this.node.rating) {
66
+ return 0;
67
+ }
68
+ if (this.node.rating.user) {
69
+ return this.node.rating.user;
70
+ }
71
+ return this.node.rating.overall.sum / this.node.rating.overall.count;
72
+ }
73
+
74
+ async setRating(rating: number) {
75
+ const name = RestHelper.getTitle(this.node);
76
+ try {
77
+ await this.ratingService.updateNodeRating(this.node.ref.id, rating).toPromise();
78
+ this.toast.toast('RATING.TOAST.RATED', { name, rating });
79
+ this.node.rating.overall.count += this.node.rating.user ? 0 : 1;
80
+ this.node.rating.user = rating;
81
+ this.changeDetectorRef.detectChanges();
82
+ } catch (e) {
83
+ this.toast.error(e);
84
+ }
85
+ }
86
+
87
+ async deleteRating() {
88
+ const name = RestHelper.getTitle(this.node);
89
+ try {
90
+ await this.ratingService.deleteNodeRating(this.node.ref.id).toPromise();
91
+ this.toast.toast('RATING.TOAST.RATING_REMOVED', { name });
92
+ this.node.rating.overall.count--;
93
+ this.node.rating.overall.sum -= this.node.rating.user;
94
+ this.node.rating.user = 0;
95
+ this.changeDetectorRef.detectChanges();
96
+ } catch (e) {
97
+ this.toast.error(e);
98
+ }
99
+ }
100
+
101
+ isFromHomeRepo(node: Node) {
102
+ return this.networkApi.isFromHomeRepository(node);
103
+ }
104
+ }
105
+ export type RatingMode = 'none' | 'likes' | 'stars';
@@ -0,0 +1,39 @@
1
+ <!--
2
+ <div
3
+ *ngIf="node.aspects?.includes('ccm:published')"
4
+ class="card-top-bar-published-copy"
5
+ >
6
+ <div class="node-published-copy">
7
+ <i
8
+ esIcon="content_copy"
9
+ [matTooltip]="'PUBLISHED_COPY_TOOLTIP' | translate"
10
+ ></i>
11
+ </div>
12
+ </div>
13
+ -->
14
+ <div class="card-top-bar-comments">
15
+ <div
16
+ class="node-comments"
17
+ *ngIf="node.type === 'ccm:io'"
18
+ (click)="
19
+ entriesService?.onClicked({
20
+ event: $event,
21
+ element: node,
22
+ source: ClickSource.Comments
23
+ })
24
+ "
25
+ [matTooltip]="('COMMENTS_MULTIPLE' | translate) + ': ' + node.commentCount"
26
+ >
27
+ <i esIcon="comment"></i>
28
+ <span>{{ node.commentCount }}</span>
29
+ </div>
30
+ </div>
31
+ <div class="card-top-bar-childobjects" *ngIf="childObjectCount > 0">
32
+ <div
33
+ class="childobject-count"
34
+ matTooltip="{{ 'CHILDOBJECT_COUNT' | translate : { count: childObjectCount + 1 } }}"
35
+ >
36
+ <span>{{ childObjectCount + 1 }}</span
37
+ ><i esIcon="filter_none"></i>
38
+ </div>
39
+ </div>
@@ -0,0 +1,44 @@
1
+ :host {
2
+ display: flex;
3
+ gap: 15px;
4
+ }
5
+
6
+ .card-top-bar-comments,
7
+ .card-top-bar-published-copy,
8
+ .card-top-bar-childobjects {
9
+ > div {
10
+ :host[backgroundStyle='lighten'] & {
11
+ background-color: rgba(255, 255, 255, 0.75);
12
+ }
13
+ :host[backgroundStyle='darken'] & {
14
+ background-color: rgba(0, 0, 0, 0.2);
15
+ }
16
+ border-radius: 15px;
17
+ display: inline-flex;
18
+ align-items: center;
19
+ min-width: 35px;
20
+ min-height: 22px;
21
+ justify-content: center;
22
+ cursor: default;
23
+ user-select: none;
24
+ padding: 2px 8px;
25
+ &.childobject-count {
26
+ > i {
27
+ font-size: 13px;
28
+ margin-left: 4px;
29
+ }
30
+ }
31
+ &.node-published-copy {
32
+ min-width: auto;
33
+ > i {
34
+ font-size: 13px;
35
+ }
36
+ }
37
+ &.node-comments {
38
+ > i {
39
+ font-size: 13px;
40
+ margin-right: 4px;
41
+ }
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,43 @@
1
+ import { Component, HostBinding, Input, Optional } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+ import { ClickSource } from '../entries-model';
4
+ import { NodeEntriesService } from '../../services/node-entries.service';
5
+
6
+ @Component({
7
+ selector: 'es-node-stats-badges',
8
+ templateUrl: './node-stats-badges.component.html',
9
+ styleUrls: ['./node-stats-badges.component.scss'],
10
+ })
11
+ export class NodeStatsBadgesComponent {
12
+ readonly ClickSource = ClickSource;
13
+
14
+ childObjectCount = 0;
15
+
16
+ private _node: Node;
17
+ @Input()
18
+ get node(): Node {
19
+ return this._node;
20
+ }
21
+ set node(node: Node) {
22
+ this._node = node;
23
+ this.childObjectCount = this.getChildObjectCount(node);
24
+ }
25
+
26
+ @HostBinding('attr.backgroundStyle')
27
+ @Input()
28
+ backgroundStyle: 'darken' | 'lighten' = 'lighten';
29
+
30
+ constructor(
31
+ @Optional()
32
+ public entriesService: NodeEntriesService<Node>,
33
+ ) {}
34
+
35
+ private getChildObjectCount(node: Node): number {
36
+ const value = node.properties?.['virtual:childobjectcount']?.[0];
37
+ if (value) {
38
+ return parseInt(value);
39
+ } else {
40
+ return 0;
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,31 @@
1
+ <ng-container *ngIf="isCollection">
2
+ <i
3
+ [matTooltip]="
4
+ 'COLLECTION.SCOPE.' + nodeHelper.getCollectionScopeInfo($any(node)).scopeName | translate
5
+ "
6
+ [altText]="
7
+ 'COLLECTION.SCOPE.' + nodeHelper.getCollectionScopeInfo($any(node)).scopeName | translate
8
+ "
9
+ [esIcon]="nodeHelper.getCollectionScopeInfo($any(node)).icon"
10
+ ></i>
11
+ </ng-container>
12
+ <ng-container *ngIf="!isCollection">
13
+ <ng-container *ngIf="getCustomTemplate() as ref">
14
+ <ng-container *ngTemplateOutlet="ref; context: { node: this.node }"></ng-container>
15
+ </ng-container>
16
+ <ng-container *ngIf="!getCustomTemplate()">
17
+ <img
18
+ [src]="node | esNodeIcon | async"
19
+ [alt]="
20
+ node.mediatype
21
+ ? ('NODE.mediatype' | translate) + ': ' + ('MEDIATYPE.' + node.mediatype | translate)
22
+ : ''
23
+ "
24
+ [matTooltip]="
25
+ node.mediatype
26
+ ? ('NODE.mediatype' | translate) + ': ' + ('MEDIATYPE.' + node.mediatype | translate)
27
+ : ''
28
+ "
29
+ />
30
+ </ng-container>
31
+ </ng-container>
@@ -0,0 +1,5 @@
1
+ @import '../mixins.scss';
2
+
3
+ :host {
4
+ @include card-top-bar-badge;
5
+ }
@@ -0,0 +1,36 @@
1
+ import { Component, Input, OnChanges } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+ import { CustomFieldSpecialType, NodeEntriesGlobalService } from '../node-entries-global.service';
4
+ import { NodeHelperService } from '../../services/node-helper.service';
5
+
6
+ /**
7
+ * A small circular badge that depicts the node's type.
8
+ *
9
+ * To be used in the top bar of a card or similar places.
10
+ */
11
+ @Component({
12
+ selector: 'es-node-type-badge',
13
+ templateUrl: './node-type-badge.component.html',
14
+ styleUrls: ['./node-type-badge.component.scss'],
15
+ })
16
+ export class NodeTypeBadgeComponent implements OnChanges {
17
+ @Input() node: Node;
18
+
19
+ isCollection: boolean;
20
+
21
+ constructor(
22
+ public nodeHelper: NodeHelperService,
23
+ private nodeEntriesGlobalService: NodeEntriesGlobalService,
24
+ ) {}
25
+
26
+ ngOnChanges(): void {
27
+ this.isCollection = this.nodeHelper.isNodeCollection(this.node);
28
+ }
29
+
30
+ getCustomTemplate() {
31
+ return this.nodeEntriesGlobalService.getCustomFieldTemplate(
32
+ { type: 'NODE', name: CustomFieldSpecialType.type },
33
+ this.node,
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,42 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+ import { OptionItem } from '../../types/option-item';
4
+ // TODO: Decide if providing focus highlights and ripples with this component is a good idea. When
5
+ // using `app-node-url` for cards, we might need highlights and ripples for the whole card while
6
+ // `app-node-url` should only wrap the title since links with lots of content confuse screen
7
+ // readers.
8
+
9
+ @Component({
10
+ selector: 'es-option-button',
11
+ template: `
12
+ <button
13
+ mat-icon-button
14
+ color="primary"
15
+ matTooltip="{{ option.name | translate }}"
16
+ [class.display-none]="!optionIsShown(option, node)"
17
+ [disabled]="!optionIsValid(option, node)"
18
+ (click)="optionIsShown(option, node) ? option.callback(node) : null"
19
+ attr.data-test="option-button-{{ option.name }}"
20
+ >
21
+ <i esIcon="{{ option.icon }}" [aria]="false"></i>
22
+ </button>
23
+ `,
24
+ })
25
+ export class OptionButtonComponent {
26
+ @Input() option: OptionItem;
27
+ @Input() node: Node;
28
+
29
+ optionIsValid(optionItem: OptionItem, node: Node): boolean {
30
+ if (optionItem.enabledCallback) {
31
+ return optionItem.enabledCallback(node);
32
+ }
33
+ return optionItem.isEnabled;
34
+ }
35
+
36
+ optionIsShown(optionItem: OptionItem, node: Node): boolean {
37
+ if (optionItem.showCallback) {
38
+ return optionItem.showCallback(node);
39
+ }
40
+ return true;
41
+ }
42
+ }
@@ -0,0 +1,19 @@
1
+ <div class="card-image-backdrop">
2
+ <!-- The blur filter does not work with canvas as it does with img on Chrome, so we go for a black
3
+ background for now. -->
4
+ <!-- <canvas *ngIf="showCanvas" #backdropCanvas role="presentation" aria-hidden="true"></canvas> -->
5
+ <img
6
+ [src]="node | esNodeImage : { maxWidth: 300, maxHeight: 300, crop: true } | async"
7
+ alt=""
8
+ *ngIf="!replacedWithStatic"
9
+ />
10
+ </div>
11
+
12
+ <canvas *ngIf="showCanvas" #canvas role="presentation" aria-hidden="true"></canvas>
13
+ <img
14
+ (load)="onImageLoad($event)"
15
+ #image
16
+ [src]="node | esNodeImage : { maxWidth: 300, maxHeight: 300, crop: true } | async"
17
+ alt=""
18
+ [class.display-none]="replacedWithStatic && !playAnimation"
19
+ />
@@ -0,0 +1,31 @@
1
+ @import '../../../../assets/scss/mixins';
2
+
3
+ :host {
4
+ position: relative;
5
+ overflow: hidden;
6
+ }
7
+
8
+ .card-image-backdrop {
9
+ position: absolute;
10
+ width: 100%;
11
+ height: 100%;
12
+ background-color: black;
13
+ img,
14
+ canvas {
15
+ object-fit: cover;
16
+ @include blurImage(15px);
17
+ opacity: 1;
18
+ }
19
+ }
20
+
21
+ :host > img,
22
+ :host > canvas {
23
+ width: 100%;
24
+ height: 100%;
25
+ object-fit: contain;
26
+ position: absolute;
27
+ }
28
+
29
+ .display-none {
30
+ display: none;
31
+ }
@@ -0,0 +1,47 @@
1
+ import { Component, ElementRef, Input, ViewChild } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+
4
+ /**
5
+ * Displays the preview image of `node`.
6
+ *
7
+ * When `node` is a video, the animated preview image is replaced with a static canvas unless
8
+ * `playAnimation` is set to `true`.
9
+ */
10
+ @Component({
11
+ selector: 'es-preview-image',
12
+ templateUrl: './preview-image.component.html',
13
+ styleUrls: ['./preview-image.component.scss'],
14
+ })
15
+ export class PreviewImageComponent<T extends Node> {
16
+ @Input() node: T;
17
+ @Input() playAnimation = false;
18
+
19
+ @ViewChild('image') imageRef: ElementRef<HTMLImageElement>;
20
+ @ViewChild('canvas') canvasRef: ElementRef<HTMLCanvasElement>;
21
+ // @ViewChild('backdropCanvas') backdropCanvasRef: ElementRef<HTMLCanvasElement>;
22
+
23
+ showCanvas: boolean = false;
24
+ replacedWithStatic: boolean = false;
25
+
26
+ constructor() {}
27
+
28
+ onImageLoad(event: Event): void {
29
+ if (this.node.mimetype?.startsWith('video')) {
30
+ const image = event.target as HTMLImageElement;
31
+ this.showCanvas = true;
32
+ setTimeout(() => {
33
+ this.initCanvas(image, this.canvasRef.nativeElement);
34
+ // this.initCanvas(image, this.backdropCanvasRef.nativeElement);
35
+ this.replacedWithStatic = true;
36
+ });
37
+ }
38
+ }
39
+
40
+ private initCanvas(image: HTMLImageElement, canvas: HTMLCanvasElement): void {
41
+ var width = image.naturalWidth;
42
+ var height = image.naturalHeight;
43
+ canvas.width = width;
44
+ canvas.height = height;
45
+ canvas.getContext('2d').drawImage(image, 0, 0, width, height);
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ <mat-slide-toggle
2
+ *ngIf="active === 'ccm:collection_ordered_position'"
3
+ [(ngModel)]="customSortingInProgress"
4
+ (ngModelChange)="customSortingInProgressChange.emit($event)"
5
+ >
6
+ {{ 'COLLECTIONS.SORT_SLIDER' | translate }}
7
+ </mat-slide-toggle>
8
+ <button
9
+ *ngIf="sortDropdown.menu"
10
+ [matMenuTriggerFor]="sortDropdown.menu"
11
+ mat-button
12
+ color="primary"
13
+ >
14
+ <span *ngIf="active">{{ 'NODE.' + active | translate }}</span>
15
+ <span *ngIf="!active">{{ 'SORT_BY' | translate }}</span>
16
+ <i
17
+ *ngIf="active && active !== 'ccm:collection_ordered_position'"
18
+ [esIcon]="'arrow_' + (direction === 'asc' ? 'upward' : 'downward')"
19
+ ></i>
20
+ </button>
21
+ <es-sort-dropdown
22
+ #sortDropdown
23
+ [columns]="columns"
24
+ [sortBy]="active"
25
+ [sortAscending]="direction === 'asc'"
26
+ (onSort)="onSort($event)"
27
+ ></es-sort-dropdown>
@@ -0,0 +1,9 @@
1
+ :host {
2
+ display: flex;
3
+ justify-content: flex-end;
4
+ align-items: center;
5
+ }
6
+
7
+ mat-slide-toggle {
8
+ margin-right: 10px;
9
+ }
@@ -0,0 +1,26 @@
1
+ import { Component, EventEmitter, Input, Output } from '@angular/core';
2
+ import { Sort, SortDirection } from '@angular/material/sort';
3
+ import { ListItemSort, SortEvent, SortPanel } from '../../types/list-item';
4
+
5
+ @Component({
6
+ selector: 'es-sort-select-panel',
7
+ templateUrl: './sort-select-panel.component.html',
8
+ styleUrls: ['./sort-select-panel.component.scss'],
9
+ })
10
+ export class SortSelectPanelComponent implements SortPanel {
11
+ @Input() active: string;
12
+ @Input() direction: SortDirection;
13
+ @Input() columns: ListItemSort[];
14
+ @Output() sortChange = new EventEmitter<Sort>();
15
+ @Input() customSortingInProgress: boolean;
16
+ @Output() customSortingInProgressChange = new EventEmitter<boolean>();
17
+
18
+ constructor() {}
19
+
20
+ onSort(event: SortEvent) {
21
+ this.sortChange.emit({
22
+ active: event.name,
23
+ direction: event.ascending ? 'asc' : 'desc',
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,66 @@
1
+ <ng-template #content><ng-content></ng-content></ng-template>
2
+ <ng-container *ngIf="mode === 'link'">
3
+ <a
4
+ *ngIf="!disabled"
5
+ #link
6
+ matRipple
7
+ matRippleColor="primary"
8
+ [matRippleDisabled]="isNested"
9
+ [routerLink]="get('routerLink')"
10
+ [state]="getState()"
11
+ [target]="target"
12
+ [queryParams]="get('queryParams')"
13
+ queryParamsHandling="merge"
14
+ cdkMonitorElementFocus
15
+ [attr.aria-label]="ariaLabel ? node.name : null"
16
+ [attr.aria-describedby]="ariaDescribedby"
17
+ >
18
+ <ng-container *ngTemplateOutlet="content"></ng-container>
19
+ </a>
20
+ <!-- We use `div`s instead of `span`s here because the legacy `ListTable` component will insert
21
+ `div` elements into this component's content. However, this will lead to invalid HTML for "button"
22
+ mode. So really, users of this component should only insert flow content. -->
23
+ <div *ngIf="disabled && !alwaysRipple">
24
+ <ng-container *ngTemplateOutlet="content"></ng-container>
25
+ </div>
26
+ <div *ngIf="disabled && alwaysRipple" matRipple matRippleColor="primary">
27
+ <ng-container *ngTemplateOutlet="content"></ng-container>
28
+ </div>
29
+ </ng-container>
30
+ <ng-container *ngIf="mode === 'wrapper'">
31
+ <div
32
+ class="node-url-wrapper"
33
+ #wrapper
34
+ matRipple
35
+ matRippleColor="primary"
36
+ [matRippleDisabled]="disabled && !alwaysRipple"
37
+ (click)="clickWrapper($event)"
38
+ (auxclick)="clickWrapper($event)"
39
+ >
40
+ <ng-container *ngTemplateOutlet="content"></ng-container>
41
+ </div>
42
+ <!-- An invisible link that will by clicked programmatically by `clickWrapper()`. -->
43
+ <a
44
+ #link
45
+ [routerLink]="get('routerLink')"
46
+ [state]="getState()"
47
+ [queryParams]="get('queryParams')"
48
+ queryParamsHandling="merge"
49
+ tabindex="-1"
50
+ aria-hidden="true"
51
+ class="cdk-visually-hidden"
52
+ ></a>
53
+ </ng-container>
54
+ <ng-container *ngIf="mode === 'button'">
55
+ <button
56
+ [disabled]="disabled"
57
+ #link
58
+ matRipple
59
+ matRippleColor="primary"
60
+ [matRippleDisabled]="disabled && !alwaysRipple"
61
+ (click)="buttonClick.emit($event)"
62
+ cdkMonitorElementFocus
63
+ >
64
+ <ng-container *ngTemplateOutlet="content"></ng-container>
65
+ </button>
66
+ </ng-container>