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,361 @@
1
+ import { CdkDragEnter, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ ElementRef,
6
+ Input,
7
+ NgZone,
8
+ OnDestroy,
9
+ OnInit,
10
+ QueryList,
11
+ TemplateRef,
12
+ ViewChild,
13
+ ViewChildren,
14
+ } from '@angular/core';
15
+ import { Sort } from '@angular/material/sort';
16
+ import { Node, RestConstants } from 'ngx-edu-sharing-api';
17
+ import * as rxjs from 'rxjs';
18
+ import { BehaviorSubject, Subject } from 'rxjs';
19
+ import { distinctUntilChanged, map, switchMap, take, takeUntil } from 'rxjs/operators';
20
+ import { GridLayout, NodeEntriesDisplayType } from '../entries-model';
21
+ import { ItemsCap } from '../items-cap';
22
+ import { NodeEntriesGlobalService } from '../node-entries-global.service';
23
+ import { NodeEntriesTemplatesService } from '../node-entries-templates.service';
24
+ import { SortSelectPanelComponent } from '../sort-select-panel/sort-select-panel.component';
25
+ import { CustomTemplatesDataSource } from '../custom-templates-data-source';
26
+ import { Target } from '../../types/option-item';
27
+ import { NodeEntriesService } from '../../services/node-entries.service';
28
+ import { UIService } from '../../services/ui.service';
29
+ import { ListItemSort } from '../../types/list-item';
30
+ import { DragData } from '../../types/drag-drop';
31
+
32
+ let displayedWarnings: string[] = [];
33
+
34
+ @Component({
35
+ selector: 'es-node-entries-card-grid',
36
+ templateUrl: 'node-entries-card-grid.component.html',
37
+ styleUrls: ['node-entries-card-grid.component.scss'],
38
+ changeDetection: ChangeDetectionStrategy.OnPush,
39
+ })
40
+ export class NodeEntriesCardGridComponent<T extends Node> implements OnInit, OnDestroy {
41
+ readonly NodeEntriesDisplayType = NodeEntriesDisplayType;
42
+ readonly Target = Target;
43
+ /**
44
+ * relative scrolling when a scrolling arrow (left or right) is used
45
+ * a value of 1 would mean to scroll the full width of the entire content
46
+ */
47
+ readonly ScrollingOffsetPercentage = 0.4;
48
+
49
+ @ViewChild('gridTop', { static: true }) set gridTop(value: TemplateRef<unknown>) {
50
+ this.registerGridTop(value);
51
+ }
52
+ @ViewChild(SortSelectPanelComponent)
53
+ set sortPanel(value: SortSelectPanelComponent) {
54
+ // if (this.entriesService.dataSource instanceof NodeDataSourceRemote) {
55
+ setTimeout(() => {
56
+ (this.entriesService.dataSource as any).sortPanel = value;
57
+ });
58
+ // }
59
+ }
60
+ @ViewChildren(CdkDropList) dropListsQuery: QueryList<CdkDropList>;
61
+ @ViewChild('grid') gridRef: ElementRef;
62
+ @ViewChildren('item', { read: ElementRef }) itemRefs: QueryList<ElementRef<HTMLElement>>;
63
+ @Input() displayType: NodeEntriesDisplayType;
64
+
65
+ isDragging = false; // Drag-and-drop, not rearrange
66
+ dropLists: CdkDropList[];
67
+ /**
68
+ * Whether the number of shown items is limited by `gridConfig.maxRows`.
69
+ *
70
+ * A value of `true` does not mean, that there would be more items available.
71
+ */
72
+ visibleItemsLimited = false;
73
+ layout: GridLayout;
74
+ /**
75
+ * updates via boxObserver
76
+ * and holds the information if scrolling in the direction is currently feasible
77
+ */
78
+ scroll = {
79
+ left: false,
80
+ right: false,
81
+ };
82
+
83
+ readonly nodes$;
84
+ private readonly maxRows$;
85
+ private readonly layout$;
86
+ private readonly itemsPerRowSubject = new BehaviorSubject<number | null>(null);
87
+ readonly itemsCap = new ItemsCap<T>();
88
+ private globalCursorStyle: HTMLStyleElement;
89
+ private destroyed = new Subject<void>();
90
+
91
+ constructor(
92
+ public entriesService: NodeEntriesService<T>,
93
+ public entriesGlobalService: NodeEntriesGlobalService,
94
+ public templatesService: NodeEntriesTemplatesService,
95
+ public ui: UIService,
96
+ private ngZone: NgZone,
97
+ ) {
98
+ this.nodes$ = this.entriesService.dataSource$.pipe(
99
+ switchMap((dataSource) => dataSource?.connect()),
100
+ );
101
+ this.maxRows$ = this.entriesService.gridConfig$.pipe(
102
+ map((gridConfig) => gridConfig?.maxRows || null),
103
+ distinctUntilChanged(),
104
+ );
105
+ this.layout$ = this.entriesService.gridConfig$.pipe(
106
+ map((gridConfig) => gridConfig?.layout || 'grid'),
107
+ distinctUntilChanged(),
108
+ );
109
+ this.entriesService.dataSource$.pipe(takeUntil(this.destroyed)).subscribe(() => {
110
+ this.updateScrollState();
111
+ });
112
+ }
113
+
114
+ ngOnInit(): void {
115
+ this.registerItemsCap();
116
+ this.registerLayout();
117
+ this.registerVisibleItemsLimited();
118
+ }
119
+
120
+ ngOnDestroy(): void {
121
+ this.destroyed.next();
122
+ this.destroyed.complete();
123
+ }
124
+
125
+ private registerGridTop(gridTop: TemplateRef<unknown>): void {
126
+ setTimeout(() => {
127
+ this.templatesService.entriesTopMatter = gridTop;
128
+ });
129
+ this.destroyed.subscribe(() => {
130
+ if (this.templatesService.entriesTopMatter === gridTop) {
131
+ setTimeout(() => {
132
+ this.templatesService.entriesTopMatter = null;
133
+ });
134
+ }
135
+ });
136
+ }
137
+
138
+ private registerItemsCap() {
139
+ this.entriesService.dataSource$
140
+ .pipe(takeUntil(this.destroyed))
141
+ .subscribe((dataSource) => (dataSource.itemsCap = this.itemsCap));
142
+ rxjs.combineLatest([this.itemsPerRowSubject.pipe(distinctUntilChanged()), this.maxRows$])
143
+ .pipe(
144
+ map(([itemsPerRow, maxRows]) =>
145
+ maxRows > 0 && itemsPerRow !== null ? itemsPerRow * maxRows : null,
146
+ ),
147
+ )
148
+ .subscribe((cap) => (this.itemsCap.cap = cap));
149
+ }
150
+
151
+ private registerLayout() {
152
+ this.layout$.subscribe((layout) => (this.layout = layout));
153
+ }
154
+
155
+ onSortChange(sort: Sort) {
156
+ this.entriesService.sort.active = sort.active;
157
+ this.entriesService.sort.direction = sort.direction;
158
+ this.entriesService.sortChange.emit(this.entriesService.sort);
159
+ }
160
+
161
+ loadData(source: 'scroll' | 'button') {
162
+ // @TODO: Maybe this is better handled in a more centraled service
163
+ if (source === 'scroll') {
164
+ // check if there is a footer
165
+ const elements = document.getElementsByTagName('footer');
166
+ if (elements.length && elements.item(0).innerHTML.trim()) {
167
+ return;
168
+ }
169
+ }
170
+ const couldLoadMore = this.entriesService.loadMore(source);
171
+ if (couldLoadMore && source === 'button') {
172
+ this.focusFirstNewItemWhenLoaded();
173
+ }
174
+ }
175
+
176
+ onCustomSortingInProgressChange() {
177
+ this.entriesService.sortChange.emit(this.entriesService.sort);
178
+ setTimeout(() => {
179
+ this.refreshDropLists();
180
+ });
181
+ }
182
+
183
+ onRearrangeDragEntered($event: CdkDragEnter) {
184
+ moveItemInArray(
185
+ this.entriesService.dataSource.getData(),
186
+ $event.item.data,
187
+ $event.container.data,
188
+ );
189
+ // `CdkDrag` doesn't really want us to rearrange the items while dragging. Its cached
190
+ // element positions get out of sync unless we update them manually.
191
+ this.ngZone.runOutsideAngular(() =>
192
+ setTimeout(() =>
193
+ this.dropLists?.forEach((list) => (list._dropListRef as any)['_cacheItems']()),
194
+ ),
195
+ );
196
+ }
197
+
198
+ onRearrangeDragStarted() {
199
+ this.globalCursorStyle = document.createElement('style');
200
+ document.body.appendChild(this.globalCursorStyle);
201
+ this.globalCursorStyle.innerHTML = `* {cursor: grabbing !important; }`;
202
+ }
203
+
204
+ onRearrangeDragEnded() {
205
+ document.body.removeChild(this.globalCursorStyle);
206
+ this.globalCursorStyle = null;
207
+ }
208
+
209
+ getDragStartDelay(): number {
210
+ if (this.ui.isMobile()) {
211
+ return 500;
212
+ } else {
213
+ return null;
214
+ }
215
+ }
216
+
217
+ private registerVisibleItemsLimited() {
218
+ this.maxRows$.subscribe((maxRows) => {
219
+ this.visibleItemsLimited = maxRows > 0;
220
+ });
221
+ }
222
+
223
+ private refreshDropLists() {
224
+ this.dropLists = this.dropListsQuery.toArray();
225
+ }
226
+
227
+ private focusFirstNewItemWhenLoaded() {
228
+ const oldLength = this.itemRefs.length;
229
+ this.itemRefs.changes
230
+ .pipe(take(1))
231
+ .subscribe((items: NodeEntriesCardGridComponent<T>['itemRefs']) => {
232
+ if (items.length > oldLength) {
233
+ this.focusOnce(items.get(oldLength).nativeElement);
234
+ }
235
+ });
236
+ }
237
+
238
+ private focusOnce(element: HTMLElement): void {
239
+ element.setAttribute('tabindex', '-1');
240
+ element.focus();
241
+ element.addEventListener('blur', () => element.removeAttribute('tabindex'), { once: true });
242
+ }
243
+
244
+ onGridSizeChanges(): void {
245
+ const itemsPerRow = this.getItemsPerRow();
246
+ this.itemsPerRowSubject.next(itemsPerRow);
247
+ this.updateScrollState();
248
+ }
249
+
250
+ private getItemsPerRow(): number | null {
251
+ if (!this.gridRef?.nativeElement) {
252
+ return null;
253
+ }
254
+ return getComputedStyle(this.gridRef.nativeElement)
255
+ .getPropertyValue('grid-template-columns')
256
+ .split(' ').length;
257
+ }
258
+
259
+ getSortColumns() {
260
+ return this.entriesService.sort?.columns?.filter((c) => {
261
+ const result = this.entriesService.columns
262
+ .concat(
263
+ new ListItemSort('NODE', 'score'),
264
+ new ListItemSort('NODE', RestConstants.CCM_PROP_COLLECTION_ORDERED_POSITION),
265
+ new ListItemSort('NODE', RestConstants.CM_PROP_TITLE),
266
+ new ListItemSort('NODE', RestConstants.CM_NAME),
267
+ new ListItemSort('NODE', RestConstants.CM_MODIFIED_DATE),
268
+ new ListItemSort('NODE', RestConstants.CCM_PROP_REPLICATIONMODIFIED),
269
+ new ListItemSort('NODE', RestConstants.CCM_PROP_REPLICATIONSOURCETIMESTAMP),
270
+ )
271
+ .some((c2) => c2.name === c.name);
272
+ if (!result) {
273
+ const warning =
274
+ 'Sort field ' +
275
+ c.name +
276
+ ' was specified but is not present as a column. ' +
277
+ 'It will be ignored. Please also configure this field in the <lists> section';
278
+ if (!displayedWarnings.includes(warning)) {
279
+ console.warn(warning);
280
+ displayedWarnings.push(warning);
281
+ }
282
+ }
283
+ return result;
284
+ });
285
+ }
286
+
287
+ canDropNodes = (dragData: DragData<T>) => this.entriesService.dragDrop.dropAllowed?.(dragData);
288
+
289
+ onNodesDropped(dragData: DragData<Node>) {
290
+ this.entriesService.dragDrop.dropped(dragData.target, {
291
+ element: dragData.draggedNodes,
292
+ mode: dragData.action,
293
+ });
294
+ }
295
+
296
+ getDragEnabled(): boolean {
297
+ return this.entriesService.dragDrop?.dragAllowed && !this.ui.isMobile();
298
+ }
299
+
300
+ getDragData(node: T): T[] {
301
+ const selection = this.entriesService.selection;
302
+ if (selection.isSelected(node)) {
303
+ return selection.selected;
304
+ } else {
305
+ return [node];
306
+ }
307
+ }
308
+
309
+ onDragStarted(node: T) {
310
+ if (!this.entriesService.selection.isSelected(node)) {
311
+ this.entriesService.selection.clear();
312
+ this.entriesService.selection.select(node);
313
+ }
314
+ this.isDragging = true;
315
+ }
316
+
317
+ onDragEnded() {
318
+ this.isDragging = false;
319
+ }
320
+
321
+ private canScroll(direction: 'left' | 'right') {
322
+ const element = this.gridRef?.nativeElement;
323
+ if (element) {
324
+ if (direction === 'left') {
325
+ return element.scrollLeft > 0;
326
+ } else if (direction === 'right') {
327
+ /*
328
+ use a small pixel buffer (10px) because scrolling aligns with the start of each card and
329
+ it can cause slight alignment issues on the end of the container
330
+ */
331
+ return element.scrollLeft < element.scrollWidth - element.clientWidth - 10;
332
+ }
333
+ }
334
+ return false;
335
+ }
336
+
337
+ updateScrollState() {
338
+ if (this.layout === 'scroll') {
339
+ this.scroll.left = this.canScroll('left');
340
+ this.scroll.right = this.canScroll('right');
341
+ }
342
+ }
343
+
344
+ doScroll(direction: 'left' | 'right') {
345
+ // 1 is enough because the browser will handle it via css snapping
346
+ const leftScroll = this.gridRef?.nativeElement.scrollLeft;
347
+ const rect = this.gridRef?.nativeElement.getBoundingClientRect();
348
+ // using scroll because it works more reliable than scrollBy
349
+ this.gridRef?.nativeElement.scroll({
350
+ left:
351
+ leftScroll +
352
+ Math.max(250, rect.width * this.ScrollingOffsetPercentage) *
353
+ (direction === 'right' ? 1 : -1),
354
+ behavior: 'smooth',
355
+ });
356
+ }
357
+
358
+ isCustomTemplate() {
359
+ return this.entriesService.dataSource instanceof CustomTemplatesDataSource;
360
+ }
361
+ }
@@ -0,0 +1,100 @@
1
+ <div
2
+ class="grid-card"
3
+ [class.grid-card-collection]="nodeHelper.isNodeCollection(node)"
4
+ [class.dynamic-single-click]="entriesService.singleClickHint === 'dynamic'"
5
+ [style.background-color]="nodeHelper.isNodeCollection(node) ? node.collection.color : null"
6
+ (contextmenu)="openContextmenu($event)"
7
+ (keydown.ContextMenu)="openContextmenu($event)"
8
+ >
9
+ <div
10
+ *ngIf="templatesService.overlay"
11
+ class="card-overlay"
12
+ (click)="
13
+ entriesService.clickItem.emit({
14
+ element: node,
15
+ source: ClickSource.Overlay
16
+ })
17
+ "
18
+ >
19
+ <ng-container
20
+ *ngTemplateOutlet="templatesService.overlay; context: { element: node }"
21
+ ></ng-container>
22
+ </div>
23
+ <es-node-url
24
+ *ngIf="entriesService.elementInteractionType === InteractionType.DefaultActionLink"
25
+ mode="wrapper"
26
+ [node]="node"
27
+ esFocusState
28
+ #cardFocusState="esFocusState"
29
+ >
30
+ <ng-container
31
+ *ngTemplateOutlet="
32
+ image;
33
+ context: { playAnimation: cardFocusState.hovering || cardFocusState.hasFocus }
34
+ "
35
+ ></ng-container>
36
+ <ng-container *ngTemplateOutlet="meta"></ng-container>
37
+ </es-node-url>
38
+ <ng-container *ngIf="entriesService.elementInteractionType !== InteractionType.DefaultActionLink">
39
+ <ng-container *ngTemplateOutlet="image"></ng-container>
40
+ <ng-container *ngTemplateOutlet="meta"></ng-container>
41
+ </ng-container>
42
+ </div>
43
+ <ng-template #image let-playAnimation="playAnimation">
44
+ <div
45
+ class="card-image-area"
46
+ (click)="
47
+ entriesService.clickItem.emit({
48
+ element: node,
49
+ source: ClickSource.Preview
50
+ })
51
+ "
52
+ >
53
+ <es-preview-image
54
+ *ngIf="!(nodeHelper.isNodeCollection(node) && node.preview.isIcon)"
55
+ [node]="node"
56
+ [playAnimation]="playAnimation"
57
+ ></es-preview-image>
58
+ <div
59
+ *ngIf="nodeHelper.isNodeCollection(node) && node.preview.isIcon"
60
+ class="card-collection-image"
61
+ >
62
+ <i esIcon="layers"></i>
63
+ </div>
64
+ </div>
65
+ </ng-template>
66
+ <ng-template #meta>
67
+ <div
68
+ class="card-meta"
69
+ (click)="
70
+ entriesService.clickItem.emit({
71
+ element: node,
72
+ source: ClickSource.Metadata
73
+ })
74
+ "
75
+ >
76
+ <div
77
+ *ngFor="let displayPart of entriesService.columns; let first = first"
78
+ class="card-meta-row"
79
+ >
80
+ <ng-container *ngIf="first">
81
+ <es-node-url
82
+ *ngIf="entriesService.elementInteractionType === InteractionType.DefaultActionLink"
83
+ [node]="node"
84
+ #link
85
+ >
86
+ <es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
87
+ </es-node-url>
88
+ <div *ngIf="entriesService.elementInteractionType !== InteractionType.DefaultActionLink">
89
+ <es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
90
+ </div>
91
+ </ng-container>
92
+ <ng-container *ngIf="!first">
93
+ <es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
94
+ </ng-container>
95
+ </div>
96
+ </div>
97
+ <ng-container
98
+ *ngTemplateOutlet="templatesService.actionArea; context: { element: node }"
99
+ ></ng-container>
100
+ </ng-template>
@@ -0,0 +1,46 @@
1
+ $imageHeight: 100px;
2
+ $entriesCardPaddingHorizontal: 15px;
3
+ $entriesCardPaddingVertical: 8px;
4
+ $topBarHeight: 40px;
5
+ $collectionIconPadding: 25px;
6
+ $collectionIconSize: 25px;
7
+
8
+ @import '../node-entries-card/node-entries-card.main';
9
+
10
+ .grid-card .card-meta-row ::ng-deep es-node-url {
11
+ a {
12
+ &.cdk-keyboard-focused {
13
+ // Normal focusColor might have insufficient contrast on color-matching background.
14
+ outline-color: var(--palette-primary-400);
15
+ }
16
+ }
17
+ }
18
+
19
+ .grid-card {
20
+ cursor: pointer;
21
+ &.grid-card-collection {
22
+ // default fallback color
23
+ background-color: rgb(var(--palette-primary-200));
24
+ }
25
+ .card-meta {
26
+ background-color: rgba(255, 255, 255, 0.8);
27
+ // grid-template-columns: 50%;
28
+ padding-bottom: $entriesCardPaddingVertical;
29
+
30
+ .card-meta-row {
31
+ &:nth-child(1) {
32
+ grid-column: 1/3;
33
+ }
34
+
35
+ &:nth-child(2) {
36
+ justify-self: flex-start;
37
+ }
38
+
39
+ &:nth-child(2),
40
+ &:nth-child(3) {
41
+ grid-row: 2;
42
+ font-size: var(--fontSizeXSmall);
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,40 @@
1
+ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
2
+ import { Target } from '../../types/option-item';
3
+ import { ClickSource, InteractionType } from '../entries-model';
4
+
5
+ import { NodeEntriesTemplatesService } from '../node-entries-templates.service';
6
+ import { NodeEntriesService } from '../../services/node-entries.service';
7
+ import { NodeHelperService } from '../../services/node-helper.service';
8
+ import { Node } from 'ngx-edu-sharing-api';
9
+
10
+ @Component({
11
+ selector: 'es-node-entries-card-small',
12
+ templateUrl: 'node-entries-card-small.component.html',
13
+ styleUrls: ['node-entries-card-small.component.scss'],
14
+ })
15
+ export class NodeEntriesCardSmallComponent<T extends Node> implements OnChanges {
16
+ readonly ClickSource = ClickSource;
17
+ readonly InteractionType = InteractionType;
18
+ readonly Target = Target;
19
+ @Input() node: T;
20
+ constructor(
21
+ public entriesService: NodeEntriesService<T>,
22
+ public nodeHelper: NodeHelperService,
23
+ public templatesService: NodeEntriesTemplatesService,
24
+ ) {}
25
+
26
+ ngOnChanges(changes: SimpleChanges): void {}
27
+ optionsOnCard() {
28
+ const options = this.entriesService.options[Target.List];
29
+ const always = options.filter((o) => o.showAlways);
30
+ if (always.some((o) => o.showCallback(this.node))) {
31
+ return always;
32
+ }
33
+ return options.filter((o) => o.showAsAction && o.showCallback(this.node)).slice(0, 3);
34
+ }
35
+
36
+ openContextmenu(event: MouseEvent | Event) {
37
+ event.preventDefault();
38
+ event.stopPropagation();
39
+ }
40
+ }
@@ -0,0 +1,23 @@
1
+ <div
2
+ *ngIf="entriesService.globalOptions?.length"
3
+ role="listitem"
4
+ class="global-options"
5
+ [class.global-options-small]="displayType === NodeEntriesDisplayType.SmallGrid"
6
+ [class.global-options-table]="displayType === NodeEntriesDisplayType.Table"
7
+ >
8
+ <button
9
+ mat-button
10
+ *ngFor="let option of entriesService.globalOptions"
11
+ (click)="option.callback()"
12
+ class="global-option-btn"
13
+ attr.data-test="card-button-{{ option.name }}"
14
+ >
15
+ <ng-container *ngTemplateOutlet="globalOption; context: { option: this.option }"></ng-container>
16
+ </button>
17
+ </div>
18
+ <ng-template #globalOption let-option="option">
19
+ <span class="global-option">
20
+ <i [esIcon]="option.icon"></i>
21
+ <span class="label">{{ option.name | translate }}</span>
22
+ </span>
23
+ </ng-template>
@@ -0,0 +1,58 @@
1
+ @import '../../../../assets/scss/variables';
2
+ @import '../../../../assets/scss/mixins';
3
+
4
+ .global-options {
5
+ display: grid;
6
+ grid-template-columns: auto;
7
+ grid-template-rows: repeat(auto-fit, minmax(70px, 1fr));
8
+ grid-row-gap: 20px;
9
+ height: 100%;
10
+ min-height: 250px;
11
+ &.global-options-small {
12
+ min-height: 130px;
13
+ }
14
+ &.global-options-table {
15
+ min-height: 100px;
16
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
17
+ grid-column-gap: 20px;
18
+ }
19
+ .global-option-btn {
20
+ display: flex;
21
+ height: 100%;
22
+ padding: 0;
23
+ }
24
+ .global-option {
25
+ cursor: pointer;
26
+ display: flex;
27
+ @include materialShadow();
28
+ width: 100%;
29
+ height: 100%;
30
+ flex-direction: column;
31
+ align-items: center;
32
+ justify-content: center;
33
+ border: 3px dashed var(--primary);
34
+ color: var(--primary);
35
+ > i {
36
+ font-size: 32px;
37
+ margin-bottom: 5px;
38
+ }
39
+ > .label {
40
+ cursor: pointer;
41
+ font-weight: bold;
42
+ }
43
+ &:hover,
44
+ &:focus {
45
+ background-color: rgb(var(--palette-primary-50));
46
+ }
47
+ }
48
+ }
49
+ :host ::ng-deep {
50
+ .global-options {
51
+ .global-option-btn {
52
+ .mdc-button__label {
53
+ width: 100%;
54
+ height: 100%;
55
+ }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,16 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+ import { NodeEntriesDisplayType } from '../entries-model';
4
+ import { NodeEntriesService } from '../../services/node-entries.service';
5
+
6
+ @Component({
7
+ selector: 'es-node-entries-global-options',
8
+ templateUrl: './node-entries-global-options.component.html',
9
+ styleUrls: ['./node-entries-global-options.component.scss'],
10
+ changeDetection: ChangeDetectionStrategy.OnPush,
11
+ })
12
+ export class NodeEntriesGlobalOptionsComponent<T extends Node> {
13
+ readonly NodeEntriesDisplayType = NodeEntriesDisplayType;
14
+ @Input() displayType: NodeEntriesDisplayType;
15
+ constructor(public entriesService: NodeEntriesService<T>) {}
16
+ }