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,33 @@
1
+ import { NodeDataSource } from './node-data-source';
2
+ import { GenericAuthority, Node } from 'ngx-edu-sharing-api';
3
+ import { MatTableDataSourcePaginator } from '@angular/material/table';
4
+ import { ActivatedRoute } from '@angular/router';
5
+ import { PaginationStrategy } from './node-entries-global.service';
6
+ import { Sort } from '@angular/material/sort';
7
+ import { SortPanel } from '../types/list-item';
8
+
9
+ export interface PaginationConfig {
10
+ defaultPageSize: number;
11
+ strategy: PaginationStrategy;
12
+ }
13
+
14
+ export abstract class NodeDataSourceRemote<
15
+ T extends Node | GenericAuthority = Node,
16
+ P extends MatTableDataSourcePaginator = MatTableDataSourcePaginator,
17
+ > extends NodeDataSource<T> {
18
+ abstract get paginator(): P | null;
19
+ abstract set paginator(value: P | null);
20
+ abstract get sortPanel(): SortPanel;
21
+ abstract set sortPanel(value: SortPanel);
22
+ abstract loadMore(): boolean;
23
+
24
+ abstract registerQueryParameters(route: ActivatedRoute): void;
25
+
26
+ abstract init({
27
+ paginationConfig,
28
+ defaultSort,
29
+ }: {
30
+ paginationConfig: PaginationConfig;
31
+ defaultSort: Sort;
32
+ }): void;
33
+ }
@@ -0,0 +1,148 @@
1
+ import { DataSource } from '@angular/cdk/collections';
2
+ import { BehaviorSubject, Observable, Subscription } from 'rxjs';
3
+ import { ItemsCap } from './items-cap';
4
+ import { Node, GenericAuthority, Pagination } from 'ngx-edu-sharing-api';
5
+ import { Helper } from '../util/helper';
6
+
7
+ export type LoadingState =
8
+ // The data source is loading data for the first time.
9
+ | 'initial'
10
+ // Loading data after change of parameters, i.e., it will replace the current data when done.
11
+ | 'reset'
12
+ // Loading another page with unchanged parameters.
13
+ | 'page'
14
+ // Loading done.
15
+ | false;
16
+
17
+ export class NodeDataSource<T extends Node | GenericAuthority> extends DataSource<T> {
18
+ protected dataStream = new BehaviorSubject<T[]>([]);
19
+ private pagination$ = new BehaviorSubject<Pagination>(null);
20
+ // Include `LoadingState` to be type-compatible to `NodeDataSourceRemote` although not used
21
+ // here.
22
+ public isLoadingSubject = new BehaviorSubject<LoadingState | boolean>(false);
23
+ get isLoading() {
24
+ return this.isLoadingSubject.value;
25
+ }
26
+ set isLoading(isLoading: LoadingState | boolean) {
27
+ this.isLoadingSubject.next(isLoading);
28
+ }
29
+ initialPageLoaded = false;
30
+ protected _itemsCap: ItemsCap<T> | null;
31
+ get itemsCap(): ItemsCap<T> | null {
32
+ return this._itemsCap;
33
+ }
34
+ set itemsCap(value: ItemsCap<T> | null) {
35
+ this._itemsCap = value;
36
+ this.connectRenderData();
37
+ }
38
+ protected renderData = new BehaviorSubject<T[]>([]);
39
+ protected renderDataSubscription: Subscription | null;
40
+
41
+ constructor(initialData: T[] = []) {
42
+ super();
43
+ this.setData(initialData);
44
+ }
45
+
46
+ connect(): Observable<T[]> {
47
+ if (!this.renderDataSubscription) {
48
+ this.connectRenderData();
49
+ }
50
+ return this.renderData;
51
+ }
52
+
53
+ private connectRenderData(): void {
54
+ this.renderDataSubscription?.unsubscribe();
55
+ if (this.itemsCap) {
56
+ this.renderDataSubscription = this.itemsCap
57
+ .connect(this.dataStream)
58
+ .subscribe((data) => this.renderData.next(data));
59
+ } else {
60
+ this.renderDataSubscription = this.dataStream.subscribe((data) =>
61
+ this.renderData.next(data),
62
+ );
63
+ }
64
+ }
65
+
66
+ connectPagination(): Observable<Pagination> {
67
+ return this.pagination$;
68
+ }
69
+
70
+ disconnect() {}
71
+
72
+ setData(data: T[], pagination: Pagination = null) {
73
+ this.dataStream.next(data);
74
+ this.setPagination(pagination);
75
+ }
76
+
77
+ appendData(appendData: T[], location: 'before' | 'after' = 'after') {
78
+ let data = this.getData();
79
+ if (location === 'after') {
80
+ data = data.concat(appendData);
81
+ } else {
82
+ data = appendData.concat(data);
83
+ }
84
+ this.dataStream.next(data);
85
+ }
86
+
87
+ /**
88
+ * Removes elements from the visible data.
89
+ */
90
+ removeData(toRemove: T[]): void {
91
+ const newData = this.getData().filter(
92
+ (value) =>
93
+ !toRemove.some((d) => Helper.objectEquals((d as Node).ref, (value as Node).ref)),
94
+ );
95
+ const removedData = this.getData().filter((value) => !newData.includes(value));
96
+ this.dataStream.next(newData);
97
+ if (this.pagination$.value) {
98
+ const pagination = this.pagination$.value;
99
+ this.setPagination({
100
+ count: pagination.count - removedData.length,
101
+ from: pagination.from,
102
+ total: pagination.total - removedData.length,
103
+ });
104
+ }
105
+ }
106
+
107
+ setPagination(pagination: Pagination) {
108
+ this.pagination$.next(pagination);
109
+ }
110
+
111
+ reset() {
112
+ this.setData([]);
113
+ }
114
+
115
+ hasMore() {
116
+ if (!this.pagination$.value) {
117
+ return undefined;
118
+ }
119
+ return this.pagination$.value.total > this.dataStream.value?.length;
120
+ }
121
+
122
+ // FIXME: This is somewhat dangerous because we rely on `connect` being called from outside, but
123
+ // this method provides a way to access data without ever calling `connect`.
124
+ getData() {
125
+ return this.renderData.value;
126
+ }
127
+
128
+ isEmpty(): boolean {
129
+ return this.dataStream.value?.length === 0;
130
+ }
131
+
132
+ getTotal() {
133
+ return this.pagination$.value?.total ?? this.dataStream.value?.length ?? 0;
134
+ }
135
+
136
+ isFullyLoaded() {
137
+ return this.getTotal() <= this.dataStream.value?.length;
138
+ }
139
+
140
+ /**
141
+ * force a refresh of all elements in the current data stream
142
+ * trigger this to enforce a rebuild of the nodes in all sub-components
143
+ * i.e. if data from some nodes has changed
144
+ */
145
+ refresh() {
146
+ this.dataStream.next(Helper.deepCopy(this.dataStream.value));
147
+ }
148
+ }
@@ -0,0 +1,167 @@
1
+ <div
2
+ class="grid-card"
3
+ [class.grid-card-collection]="isCollection"
4
+ [class.grid-card-virtual]="$any(node).virtual"
5
+ [style.background-color]="isCollection ? node.collection.color : null"
6
+ [class.dynamic-single-click]="entriesService.singleClickHint === 'dynamic'"
7
+ [class.selected]="entriesService.selection.isSelected(node)"
8
+ (contextmenu)="openContextmenu($event)"
9
+ (keydown.ContextMenu)="openContextmenu($event)"
10
+ >
11
+ <div
12
+ *ngIf="templatesService.overlay"
13
+ class="card-overlay"
14
+ (click)="
15
+ entriesService.clickItem.emit({
16
+ element: node,
17
+ source: ClickSource.Overlay
18
+ })
19
+ "
20
+ >
21
+ <ng-container
22
+ *ngTemplateOutlet="templatesService.overlay; context: { element: node }"
23
+ ></ng-container>
24
+ </div>
25
+ <button
26
+ *ngIf="dropdown"
27
+ #menuTrigger="matMenuTrigger"
28
+ mat-button
29
+ class="dropdown-dummy cdk-visually-hidden"
30
+ [style.left.px]="dropdownLeft"
31
+ [style.top.px]="dropdownTop"
32
+ [matMenuTriggerFor]="dropdown.menu"
33
+ tabindex="-1"
34
+ aria-hidden="true"
35
+ ></button>
36
+ <div class="card-top-bar" [style.background-color]="isCollection ? node.collection.color : null">
37
+ <div class="card-top-bar-collection-color" *ngIf="nodeHelper.isNodeCollection(node)"></div>
38
+ <es-node-type-badge [node]="node"></es-node-type-badge>
39
+ <div *ngIf="isCollection && node.collection.pinned" class="card-top-bar-flag">
40
+ <i esIcon="edu-pin"></i>
41
+ </div>
42
+ <div class="card-top-bar-empty"></div>
43
+ <es-node-stats-badges [node]="node"></es-node-stats-badges>
44
+ <div class="card-top-bar-checkbox" *ngIf="entriesService.checkbox">
45
+ <mat-checkbox
46
+ [checked]="entriesService.selection.isSelected(node)"
47
+ (change)="entriesService.onCheckboxChanged(node, $event.checked)"
48
+ aria-label="{{ 'SELECT' | translate : { element: (node | nodeTitle) } }}"
49
+ ></mat-checkbox>
50
+ </div>
51
+ </div>
52
+ <es-node-url
53
+ *ngIf="entriesService.elementInteractionType === InteractionType.DefaultActionLink"
54
+ mode="wrapper"
55
+ [node]="node"
56
+ esFocusState
57
+ #cardFocusState="esFocusState"
58
+ >
59
+ <ng-container
60
+ *ngTemplateOutlet="
61
+ image;
62
+ context: { playAnimation: cardFocusState.hovering || cardFocusState.hasFocus }
63
+ "
64
+ ></ng-container>
65
+ <ng-container *ngTemplateOutlet="meta"></ng-container>
66
+ </es-node-url>
67
+ <div
68
+ *ngIf="entriesService.elementInteractionType !== InteractionType.DefaultActionLink"
69
+ matRipple
70
+ (click)="
71
+ entriesService.onClicked({
72
+ event: $event,
73
+ element: node,
74
+ source: ClickSource.Metadata
75
+ })
76
+ "
77
+ (dblclick)="
78
+ entriesService.dblClickItem.emit({
79
+ element: node,
80
+ source: ClickSource.Metadata
81
+ })
82
+ "
83
+ esFocusState
84
+ #cardFocusState="esFocusState"
85
+ >
86
+ <ng-container
87
+ *ngTemplateOutlet="
88
+ image;
89
+ context: { playAnimation: cardFocusState.hovering || cardFocusState.hasFocus }
90
+ "
91
+ ></ng-container>
92
+ <ng-container *ngTemplateOutlet="meta"></ng-container>
93
+ </div>
94
+ <div class="card-options" *ngIf="entriesService.options || showRatings">
95
+ <div class="card-rating-area">
96
+ <es-node-rating [node]="node"></es-node-rating>
97
+ </div>
98
+ <div class="card-options-area">
99
+ <es-option-button
100
+ *ngFor="let option of optionsOnCard()"
101
+ class="card-options-always"
102
+ [option]="option"
103
+ [node]="node"
104
+ ></es-option-button>
105
+ <div class="card-options-spacer"></div>
106
+ <button
107
+ *ngIf="dropdown"
108
+ mat-icon-button
109
+ color="primary"
110
+ (click)="openMenu(node)"
111
+ [matMenuTriggerFor]="dropdown.menu"
112
+ [attr.aria-label]="'OPTIONS_FOR' | translate : { element: (node | nodeTitle) }"
113
+ data-test="card-options-button"
114
+ >
115
+ <i esIcon="more_vert"></i>
116
+ </button>
117
+ </div>
118
+ </div>
119
+ <ng-template #image let-playAnimation="playAnimation">
120
+ <div
121
+ class="card-image-area"
122
+ [style.background-color]="isCollection ? node.collection.color : null"
123
+ >
124
+ <ng-container *ngIf="getTemplate(CustomFieldSpecialType.preview) as ref">
125
+ <ng-container *ngTemplateOutlet="ref; context: { node: this.node }"></ng-container>
126
+ </ng-container>
127
+ <ng-container *ngIf="!getTemplate(CustomFieldSpecialType.preview)">
128
+ <es-preview-image
129
+ *ngIf="!(isCollection && node.preview.isIcon)"
130
+ [node]="node"
131
+ [playAnimation]="playAnimation"
132
+ ></es-preview-image>
133
+ <div *ngIf="isCollection && node.preview.isIcon" class="card-collection-image">
134
+ <i esIcon="layers"></i>
135
+ </div>
136
+ </ng-container>
137
+ </div>
138
+ </ng-template>
139
+ <ng-template #meta let-link="link">
140
+ <div class="card-meta">
141
+ <div
142
+ *ngFor="let displayPart of getVisibleColumns(); let first = first"
143
+ class="card-meta-row card-meta-row-{{ displayPart.name | lowercase }}"
144
+ [class.card-meta-row-primary]="first"
145
+ >
146
+ <ng-container *ngIf="first">
147
+ <es-node-url
148
+ *ngIf="entriesService.elementInteractionType === InteractionType.DefaultActionLink"
149
+ [node]="node"
150
+ #link
151
+ >
152
+ <es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
153
+ </es-node-url>
154
+ <div *ngIf="entriesService.elementInteractionType !== InteractionType.DefaultActionLink">
155
+ <es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
156
+ </div>
157
+ </ng-container>
158
+ <ng-container *ngIf="!first">
159
+ <label>
160
+ {{ displayPart | esListItemLabel | async }}
161
+ </label>
162
+ <es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
163
+ </ng-container>
164
+ </div>
165
+ </div>
166
+ </ng-template>
167
+ </div>
@@ -0,0 +1,28 @@
1
+ $imageHeight: 150px;
2
+ $entriesCardPaddingHorizontal: 20px;
3
+ $entriesCardPaddingVertical: 10px;
4
+ $topBarHeight: 40px;
5
+ $collectionIconPadding: 40px;
6
+ $collectionIconSize: 40px;
7
+
8
+ @import 'node-entries-card.main';
9
+
10
+ .grid-card {
11
+ grid-template-rows: $topBarHeight auto $optionBarHeight;
12
+ .card-meta {
13
+ .card-meta-row {
14
+ > es-list-base {
15
+ @include limitLineCount(2, 1.25);
16
+ }
17
+ }
18
+ }
19
+ }
20
+ :host ::ng-deep {
21
+ .grid-card {
22
+ es-node-url {
23
+ .node-url-wrapper {
24
+ height: 100%;
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,132 @@
1
+ import {
2
+ ApplicationRef,
3
+ Component,
4
+ Input,
5
+ OnChanges,
6
+ OnInit,
7
+ Optional,
8
+ SimpleChanges,
9
+ ViewChild,
10
+ } from '@angular/core';
11
+ import { MatMenuTrigger } from '@angular/material/menu';
12
+ import { ClickSource, InteractionType } from '../entries-model';
13
+ import { NodeEntriesTemplatesService } from '../node-entries-templates.service';
14
+ import { CustomFieldSpecialType, NodeEntriesGlobalService } from '../node-entries-global.service';
15
+ import { Target } from '../../types/option-item';
16
+ import { NodeEntriesService } from '../../services/node-entries.service';
17
+ import { NodeHelperService } from '../../services/node-helper.service';
18
+ import { AuthenticationService, ConfigService, Node, RestConstants } from 'ngx-edu-sharing-api';
19
+ import { ColorHelper, PreferredColor } from '../../util/color-helper';
20
+ import { take } from 'rxjs/operators';
21
+ import { DropdownComponent } from '../../dropdown/dropdown.component';
22
+ import { Toast } from '../../services/abstract/toast.service';
23
+
24
+ @Component({
25
+ selector: 'es-node-entries-card',
26
+ templateUrl: 'node-entries-card.component.html',
27
+ styleUrls: ['node-entries-card.component.scss'],
28
+ })
29
+ export class NodeEntriesCardComponent<T extends Node> implements OnChanges, OnInit {
30
+ readonly InteractionType = InteractionType;
31
+ readonly Target = Target;
32
+ readonly ClickSource = ClickSource;
33
+ readonly CustomFieldSpecialType = CustomFieldSpecialType;
34
+ @Input() dropdown: DropdownComponent;
35
+
36
+ @ViewChild('menuTrigger') menuTrigger: MatMenuTrigger;
37
+
38
+ @Input() node: T;
39
+ dropdownLeft: number;
40
+ dropdownTop: number;
41
+ showRatings: boolean;
42
+ isCollection: boolean;
43
+ constructor(
44
+ public entriesService: NodeEntriesService<T>,
45
+ public nodeHelper: NodeHelperService,
46
+ public applicationRef: ApplicationRef,
47
+ public configService: ConfigService,
48
+ public authenticationService: AuthenticationService,
49
+ public templatesService: NodeEntriesTemplatesService,
50
+ private nodeEntriesGlobalService: NodeEntriesGlobalService,
51
+ @Optional() private toast: Toast,
52
+ ) {}
53
+
54
+ ngOnChanges(changes: SimpleChanges): void {
55
+ this.isCollection = this.nodeHelper.isNodeCollection(changes.node?.currentValue);
56
+ }
57
+
58
+ getTextColor() {
59
+ return ColorHelper.getPreferredColor(this.node.collection.color) === PreferredColor.Black
60
+ ? '#000'
61
+ : '#fff';
62
+ }
63
+ optionsOnCard() {
64
+ const options = this.entriesService.options[Target.List];
65
+ const always = options.filter((o) => o.showAlways);
66
+ if (always.some((o) => o.showCallback(this.node))) {
67
+ return always;
68
+ }
69
+ // we do NOT show any additional actions
70
+ return [];
71
+ // return options.filter((o) => o.showAsAction && o.showCallback(this.node)).slice(0, 3);
72
+ }
73
+
74
+ openContextmenu(event: MouseEvent | Event) {
75
+ event.stopPropagation();
76
+ event.preventDefault();
77
+ if (!this.dropdown) {
78
+ // Call `preventDefault()` even when there is no menu, so we can use `cdkDrag` with a
79
+ // start delay without being interrupted by the standard long-tap action.
80
+ return;
81
+ }
82
+ if (event instanceof MouseEvent) {
83
+ ({ clientX: this.dropdownLeft, clientY: this.dropdownTop } = event);
84
+ } else {
85
+ ({ x: this.dropdownLeft, y: this.dropdownTop } = (
86
+ event.target as HTMLElement
87
+ ).getBoundingClientRect());
88
+ }
89
+ if (!this.entriesService.selection.selected.includes(this.node)) {
90
+ this.entriesService.selection.clear();
91
+ this.entriesService.selection.select(this.node);
92
+ }
93
+ // Wait for the menu to reflect changed options.
94
+ setTimeout(() => {
95
+ if (this.dropdown.canShowDropdown()) {
96
+ this.menuTrigger.openMenu();
97
+ } else {
98
+ this.toast.toast('NO_AVAILABLE_OPTIONS');
99
+ }
100
+ });
101
+ }
102
+
103
+ getVisibleColumns() {
104
+ return this.entriesService.columns?.filter((c) => c.visible);
105
+ }
106
+
107
+ async openMenu(node: T) {
108
+ this.entriesService.selection.clear();
109
+ this.entriesService.selection.select(node);
110
+ await this.applicationRef.tick();
111
+ this.dropdown.menu.focusFirstItem();
112
+ }
113
+
114
+ async ngOnInit() {
115
+ await this.configService.observeConfig().pipe(take(1)).toPromise();
116
+ this.showRatings =
117
+ this.configService.instant('', 'none') !== 'none' &&
118
+ (await this.authenticationService.hasToolpermission(
119
+ RestConstants.TOOLPERMISSION_RATE_READ,
120
+ ));
121
+ }
122
+
123
+ getTemplate(name: CustomFieldSpecialType) {
124
+ return this.nodeEntriesGlobalService.getCustomFieldTemplate(
125
+ {
126
+ type: 'NODE',
127
+ name,
128
+ },
129
+ this.node as Node,
130
+ );
131
+ }
132
+ }