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,363 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ ComponentRef,
6
+ ContentChild,
7
+ ElementRef,
8
+ EventEmitter,
9
+ Input,
10
+ NgZone,
11
+ OnChanges,
12
+ OnDestroy,
13
+ OnInit,
14
+ Output,
15
+ SimpleChange,
16
+ TemplateRef,
17
+ Type,
18
+ ViewChild,
19
+ ViewContainerRef,
20
+ } from '@angular/core';
21
+ import { Subject } from 'rxjs';
22
+ import { NodeEntriesTemplatesService } from './node-entries-templates.service';
23
+ import { NodeEntriesComponent } from './node-entries.component';
24
+ import {
25
+ FetchEvent,
26
+ GridConfig,
27
+ InteractionType,
28
+ ListDragGropConfig,
29
+ ListEventInterface,
30
+ ListOptions,
31
+ ListOptionsConfig,
32
+ ListSortConfig,
33
+ NodeClickEvent,
34
+ NodeEntriesDataType,
35
+ NodeEntriesDisplayType,
36
+ } from './entries-model';
37
+ import { NodeDataSource } from './node-data-source';
38
+ import { Helper } from '../util/helper';
39
+ import { NodeEntriesService } from '../services/node-entries.service';
40
+ import { OptionItem, Scope } from '../types/option-item';
41
+ import { NodeHelperService } from '../services/node-helper.service';
42
+ import { ListItem } from '../types/list-item';
43
+ import { TemporaryStorageService } from '../services/temporary-storage.service';
44
+ import { CollectionReference, Node, User } from 'ngx-edu-sharing-api';
45
+ import { VirtualNode } from '../types/api-models';
46
+ import { OptionsHelperDataService } from '../services/options-helper-data.service';
47
+ import { UIService } from '../services/ui.service';
48
+
49
+ @Component({
50
+ selector: 'es-node-entries-wrapper',
51
+ template: `<es-node-entries
52
+ #nodeEntriesComponent
53
+ *ngIf="!customNodeListComponent"
54
+ ></es-node-entries>`,
55
+ providers: [NodeEntriesService, OptionsHelperDataService, NodeEntriesTemplatesService],
56
+ })
57
+ export class NodeEntriesWrapperComponent<T extends NodeEntriesDataType>
58
+ implements AfterViewInit, OnInit, OnChanges, OnDestroy, ListEventInterface<T>
59
+ {
60
+ /**
61
+ * title (above) the table/grid
62
+ */
63
+ @ContentChild('title') titleRef: TemplateRef<any>;
64
+ /**
65
+ * data shown when data source is empty
66
+ */
67
+ @ContentChild('empty') emptyRef: TemplateRef<any>;
68
+ /**
69
+ * custom area for actions only for NodeEntriesDisplayType.SmallGrid (per card at the bottom)
70
+ */
71
+ @ContentChild('actionArea') actionAreaRef: TemplateRef<any>;
72
+ /**
73
+ * custom area for an overlay "above" each card (i.e. to show disabled infos), only for NodeEntriesDisplayType.SmallGrid & odeEntriesDisplayType.Grid
74
+ */
75
+ @ContentChild('overlay') overlayRef: TemplateRef<any>;
76
+ @ViewChild('nodeEntriesComponent') nodeEntriesComponentRef: NodeEntriesComponent<T>;
77
+ @Input() dataSource: NodeDataSource<T>;
78
+ @Input() scope: Scope;
79
+ @Input() columns: ListItem[];
80
+ @Input() configureColumns: boolean;
81
+ @Input() checkbox = true;
82
+ @Output() columnsChange = new EventEmitter<ListItem[]>();
83
+ @Input() globalOptions: OptionItem[];
84
+ @Input() displayType = NodeEntriesDisplayType.Grid;
85
+ @Output() displayTypeChange = new EventEmitter<NodeEntriesDisplayType>();
86
+ @Input() elementInteractionType = InteractionType.DefaultActionLink;
87
+ @Input() sort: ListSortConfig;
88
+ @Input() dragDrop: ListDragGropConfig<T>;
89
+ @Input() gridConfig: GridConfig;
90
+ /**
91
+ * this can be set instead of calling initOptionsGenerator()
92
+ */
93
+ @Input() initConfig: ListOptionsConfig;
94
+ /**
95
+ * Whether this node-entries instance represents the page's main content.
96
+ *
97
+ * Only set to true for one instance per page.
98
+ *
99
+ * If true, this instance will
100
+ * - handle page-wide keyboard shortcuts
101
+ * - take control of the `page` and `pageSize` query parameters for pagination
102
+ */
103
+ @Input() primaryInstance: boolean;
104
+ /**
105
+ * UI hints for whether a single click will cause a dynamic action.
106
+ *
107
+ * This does not configure the actual behavior but only UI hints to the user. Hints include
108
+ * hover effects and a changed cursor.
109
+ *
110
+ * - When choosing 'static', the `clickItem` event should trigger some stationary action like
111
+ * selecting the element or displaying information in a complementary page area. The
112
+ * `dblClickItem` event can be used for a more disruptive action.
113
+ * - When choosing 'dynamic', the `clickItem` event should trigger a major action like
114
+ * navigating to a new page or closing a dialog.
115
+ */
116
+ // TODO: Consider controlling the ui hints and the actual behavior with a single option.
117
+ @Input() singleClickHint: 'dynamic' | 'static' = 'dynamic';
118
+ /**
119
+ * Do not load more data on scroll.
120
+ */
121
+ @Input() disableInfiniteScroll = false;
122
+
123
+ @Output() fetchData = new EventEmitter<FetchEvent>();
124
+ @Output() clickItem = new EventEmitter<NodeClickEvent<T>>();
125
+ @Output() dblClickItem = new EventEmitter<NodeClickEvent<T>>();
126
+ @Output() sortChange = new EventEmitter<ListSortConfig>();
127
+ @Output() virtualNodesAdded;
128
+ @Output() displayTypeChanged;
129
+
130
+ customNodeListComponent: Type<NodeEntriesComponent<T>>;
131
+ private componentRef: ComponentRef<NodeEntriesComponent<T>>;
132
+ private options: ListOptions;
133
+ private destroyed = new Subject<void>();
134
+
135
+ constructor(
136
+ private viewContainerRef: ViewContainerRef,
137
+ private temporaryStorageService: TemporaryStorageService,
138
+ private ngZone: NgZone,
139
+ private entriesService: NodeEntriesService<T>,
140
+ public optionsHelper: OptionsHelperDataService,
141
+ private nodeHelperService: NodeHelperService,
142
+ private uiService: UIService,
143
+ // @TODO
144
+ // private mainNav: MainNavService,
145
+ private templatesService: NodeEntriesTemplatesService,
146
+ private changeDetectorRef: ChangeDetectorRef,
147
+ private elementRef: ElementRef,
148
+ ) {
149
+ // regulary re-bind template since it might have updated without ngChanges trigger
150
+ /*
151
+ ngZone.runOutsideAngular(() =>
152
+ setInterval(() => this.componentRef.instance.emptyRef = this.emptyRef)
153
+ );
154
+ */
155
+ this.virtualNodesAdded = this.optionsHelper.virtualNodesAdded;
156
+ this.displayTypeChanged = this.optionsHelper.displayTypeChanged;
157
+ this.entriesService.selection.changed.subscribe(() => {
158
+ if (this.optionsHelper.getData()) {
159
+ this.optionsHelper.getData().selectedObjects =
160
+ this.entriesService.selection.selected;
161
+ this.optionsHelper.getData().activeObjects = this.entriesService.selection.selected;
162
+ } else {
163
+ console.warn('optionsHelper is not initalized correctly; data is empty');
164
+ }
165
+ this.optionsHelper.refreshComponents();
166
+ });
167
+ }
168
+
169
+ ngOnInit(): void {
170
+ if (this.primaryInstance) {
171
+ this.optionsHelper.registerGlobalKeyboardShortcuts();
172
+ }
173
+ }
174
+
175
+ ngOnChanges(changes: { [key: string]: SimpleChange } = {}) {
176
+ if (!this.componentRef) {
177
+ this.init();
178
+ }
179
+ this.entriesService.list = this;
180
+ this.entriesService.dataSource = this.dataSource;
181
+ this.entriesService.scope = this.scope;
182
+ this.entriesService.columns = this.columns;
183
+ this.entriesService.configureColumns = this.configureColumns;
184
+ this.entriesService.checkbox = this.checkbox;
185
+ this.entriesService.columnsChange = this.columnsChange;
186
+ this.entriesService.displayType = this.displayType;
187
+ this.entriesService.elementInteractionType = this.elementInteractionType;
188
+ this.entriesService.gridConfig = this.gridConfig;
189
+ this.entriesService.options = this.options;
190
+ this.entriesService.globalOptions = this.globalOptions;
191
+ this.entriesService.sort = this.sort;
192
+ this.entriesService.sortChange = this.sortChange;
193
+ this.entriesService.dragDrop = this.dragDrop;
194
+ this.entriesService.clickItem = this.clickItem;
195
+ this.entriesService.dblClickItem = this.dblClickItem;
196
+ this.entriesService.fetchData = this.fetchData;
197
+ this.entriesService.primaryInstance = this.primaryInstance;
198
+ this.entriesService.singleClickHint = this.singleClickHint;
199
+ this.entriesService.disableInfiniteScroll = this.disableInfiniteScroll;
200
+
201
+ if (changes['initConfig']) {
202
+ this.initOptionsGenerator(this.initConfig);
203
+ }
204
+ if (this.componentRef) {
205
+ this.componentRef.instance.changeDetectorRef?.detectChanges();
206
+ }
207
+ // This might need wrapping with `setTimeout`.
208
+ this.updateTemplates();
209
+ }
210
+
211
+ ngOnDestroy(): void {
212
+ this.destroyed.next();
213
+ this.destroyed.complete();
214
+ }
215
+
216
+ /**
217
+ * Replaces this wrapper with the configured custom-node-list component.
218
+ */
219
+ private init(): void {
220
+ this.customNodeListComponent = this.temporaryStorageService.get(
221
+ TemporaryStorageService.CUSTOM_NODE_ENTRIES_COMPONENT,
222
+ null,
223
+ );
224
+ if (this.customNodeListComponent == null) {
225
+ return;
226
+ }
227
+ this.componentRef = this.uiService.injectAngularComponent(
228
+ this.viewContainerRef,
229
+ this.customNodeListComponent,
230
+ this.elementRef.nativeElement,
231
+ // Input bindings are initialized in `ngOnChanges`.
232
+ this.getOutputBindings(),
233
+ );
234
+ }
235
+ /**
236
+ * Creates a simple map of the output bindings defined in this component.
237
+ */
238
+ private getOutputBindings(): { [key: string]: EventEmitter<any> } {
239
+ const outputBindings: { [key: string]: any } = {};
240
+ for (const key of Object.keys(this)) {
241
+ const value = (this as any)[key];
242
+ if (value instanceof EventEmitter) {
243
+ outputBindings[key] = value;
244
+ }
245
+ }
246
+ return outputBindings;
247
+ }
248
+
249
+ getDisplayType(): NodeEntriesDisplayType {
250
+ return this.displayType;
251
+ }
252
+
253
+ setDisplayType(displayType: NodeEntriesDisplayType): void {
254
+ this.displayType = displayType;
255
+ this.entriesService.displayType = displayType;
256
+ this.ngOnChanges();
257
+ this.displayTypeChange.emit(displayType);
258
+ }
259
+
260
+ updateNodes(nodes: void | T[]) {
261
+ if (!nodes) {
262
+ return;
263
+ }
264
+ this.dataSource.getData().forEach((d) => {
265
+ let hits = (nodes as T[]).filter((n) => (n as Node).ref.id === (d as Node).ref.id);
266
+ if (hits.length === 0) {
267
+ // handle if the original has changed (for collection refs)
268
+ hits = (nodes as T[]).filter(
269
+ (n) => (n as Node).ref.id === (d as unknown as CollectionReference).originalId,
270
+ );
271
+ }
272
+ if (hits.length === 1) {
273
+ this.nodeHelperService.copyDataToNode(d as Node, hits[0] as Node);
274
+ }
275
+ });
276
+ // trigger rebuild
277
+ if (this.dataSource instanceof NodeDataSource) {
278
+ (this.dataSource as NodeDataSource<T>).refresh();
279
+ }
280
+ const oldSelection = this.entriesService.selection.selected;
281
+ this.entriesService.selection.clear();
282
+ this.entriesService.selection.select(
283
+ ...oldSelection.map(
284
+ (o) => this.dataSource.getData().filter((d) => Helper.objectEquals(o, d))?.[0],
285
+ ),
286
+ );
287
+ this.changeDetectorRef.detectChanges();
288
+ }
289
+
290
+ showReorderColumnsDialog(): void {}
291
+
292
+ addVirtualNodes(virtual: T[]): void {
293
+ virtual = virtual.map((o) => {
294
+ (o as VirtualNode).virtual = true;
295
+ return o;
296
+ });
297
+ virtual.forEach((v) => {
298
+ const contains = this.dataSource
299
+ .getData()
300
+ .some((d) =>
301
+ (d as Node).ref
302
+ ? (d as Node).ref?.id === (v as Node).ref?.id
303
+ : (d as User).authorityName === (v as User).authorityName,
304
+ );
305
+ if (contains) {
306
+ this.updateNodes([v]);
307
+ } else {
308
+ this.dataSource.appendData([v], 'before');
309
+ }
310
+ });
311
+ this.entriesService.selection.clear();
312
+ this.entriesService.selection.select(...virtual);
313
+ this.virtualNodesAdded.emit(virtual as Node[]);
314
+ this.changeDetectorRef.detectChanges();
315
+ }
316
+
317
+ setOptions(options: ListOptions): void {
318
+ this.options = options;
319
+ this.ngOnChanges();
320
+ }
321
+
322
+ getSelection() {
323
+ return this.entriesService.selection;
324
+ }
325
+
326
+ async initOptionsGenerator(config: ListOptionsConfig) {
327
+ await this.optionsHelper.initComponents(config.actionbar, this);
328
+ this.optionsHelper.setData({
329
+ scope: this.entriesService.scope,
330
+ activeObjects: this.entriesService.selection.selected,
331
+ selectedObjects: this.entriesService.selection.selected,
332
+ allObjects: this.dataSource.getData(),
333
+ parent: config.parent,
334
+ customOptions: config.customOptions,
335
+ });
336
+ this.optionsHelper.refreshComponents();
337
+ }
338
+
339
+ ngAfterViewInit(): void {
340
+ // Prevent changed-after-checked error
341
+ Promise.resolve().then(() => this.updateTemplates());
342
+ }
343
+
344
+ private updateTemplates(): void {
345
+ this.templatesService.title = this.titleRef;
346
+ this.templatesService.empty = this.emptyRef;
347
+ this.templatesService.actionArea = this.actionAreaRef;
348
+ this.templatesService.overlay = this.overlayRef;
349
+ }
350
+
351
+ /**
352
+ * reset the pagination to the first page
353
+ * hint: this will do nothing in case the paginationStrategy !== Pagination
354
+ */
355
+ resetPagination() {
356
+ this.nodeEntriesComponentRef?.paginator?.firstPage();
357
+ }
358
+
359
+ deleteNodes(objects: T[]): void {
360
+ this.dataSource.removeData(objects);
361
+ this.getSelection().clear();
362
+ }
363
+ }
@@ -0,0 +1,33 @@
1
+ <div class="top-matter">
2
+ <div class="title">
3
+ <ng-container *ngTemplateOutlet="templatesService.title"></ng-container>
4
+ </div>
5
+ <ng-container *ngTemplateOutlet="templatesService.entriesTopMatter"></ng-container>
6
+ </div>
7
+ <ng-container *ngIf="entriesService.dataSource">
8
+ <ng-container *ngIf="entriesService.displayType === NodeEntriesDisplayType.Table">
9
+ <es-node-entries-table></es-node-entries-table>
10
+ </ng-container>
11
+ <ng-container
12
+ *ngIf="
13
+ entriesService.displayType === NodeEntriesDisplayType.SmallGrid ||
14
+ entriesService.displayType === NodeEntriesDisplayType.Grid
15
+ "
16
+ >
17
+ <es-node-entries-card-grid [displayType]="entriesService.displayType">
18
+ </es-node-entries-card-grid>
19
+ </ng-container>
20
+ <ng-container *ngIf="entriesService.dataSource.isEmpty() && !entriesService.dataSource.isLoading">
21
+ <ng-container *ngTemplateOutlet="templatesService.empty"></ng-container>
22
+ </ng-container>
23
+ </ng-container>
24
+ <mat-paginator
25
+ #paginator
26
+ [pageSizeOptions]="entriesGlobalService.getPaginatorSizeOptions(entriesService.scope)"
27
+ *ngIf="entriesService.paginationStrategy === 'paginator'"
28
+ [class.display-none]="
29
+ (entriesService.dataSource.isLoading && entriesService.dataSource.isLoading !== 'page') ||
30
+ entriesService.dataSource.isEmpty()
31
+ "
32
+ (page)="openPage($event)"
33
+ ></mat-paginator>
@@ -0,0 +1,13 @@
1
+ .top-matter {
2
+ display: flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ gap: 12px 0;
6
+ position: relative;
7
+ // Don't draw the vertical scroll buttons over the top matter
8
+ z-index: 1;
9
+ .title {
10
+ display: flex;
11
+ flex-grow: 1;
12
+ }
13
+ }
@@ -0,0 +1,151 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ OnDestroy,
6
+ OnInit,
7
+ Optional,
8
+ ViewChild,
9
+ } from '@angular/core';
10
+ import { MatPaginator, PageEvent } from '@angular/material/paginator';
11
+ import { ActivatedRoute } from '@angular/router';
12
+ import { TranslateService } from '@ngx-translate/core';
13
+ import { Subject } from 'rxjs';
14
+ import { first, takeUntil } from 'rxjs/operators';
15
+ import { NodeEntriesDataType, NodeEntriesDisplayType } from './entries-model';
16
+ import { NodeEntriesGlobalService } from './node-entries-global.service';
17
+ import { NodeEntriesTemplatesService } from './node-entries-templates.service';
18
+ import { NodeEntriesService } from '../services/node-entries.service';
19
+ import { Node, GenericAuthority } from 'ngx-edu-sharing-api';
20
+ import { KeyboardShortcutsService } from '../services/abstract/keyboard-shortcuts.service';
21
+ import { NodeDataSourceRemote } from './node-data-source-remote';
22
+
23
+ @Component({
24
+ selector: 'es-node-entries',
25
+ templateUrl: 'node-entries.component.html',
26
+ styleUrls: ['node-entries.component.scss'],
27
+ })
28
+ export class NodeEntriesComponent<T extends NodeEntriesDataType>
29
+ implements OnInit, AfterViewInit, OnDestroy
30
+ {
31
+ readonly NodeEntriesDisplayType = NodeEntriesDisplayType;
32
+
33
+ @ViewChild(MatPaginator) paginator: MatPaginator;
34
+
35
+ private readonly destroyed = new Subject<void>();
36
+
37
+ constructor(
38
+ public changeDetectorRef: ChangeDetectorRef,
39
+ public entriesGlobalService: NodeEntriesGlobalService,
40
+ public entriesService: NodeEntriesService<T>,
41
+ public templatesService: NodeEntriesTemplatesService,
42
+ @Optional() private globalKeyboardShortcuts: KeyboardShortcutsService,
43
+ private route: ActivatedRoute,
44
+ private translate: TranslateService,
45
+ ) {}
46
+
47
+ ngOnInit(): void {
48
+ if (this.entriesService.primaryInstance) {
49
+ this.registerGlobalKeyboardShortcuts();
50
+ }
51
+ if (this.entriesService.dataSource instanceof NodeDataSourceRemote) {
52
+ // We don't require `sort` to be defined, but if it is set but not yet ready (`null`),
53
+ // we wait for its value before initializing the data source, so the first request is
54
+ // sent with the correct sort configuration. That is why we explicitly do not drop
55
+ // `undefined` with the `first` operator below.
56
+ this.entriesService.sortSubject
57
+ .pipe(first((sort) => sort !== null))
58
+ .subscribe(() =>
59
+ this.initRemoteDataSource(
60
+ this.entriesService.dataSource as NodeDataSourceRemote<T>,
61
+ ),
62
+ );
63
+ }
64
+ }
65
+
66
+ ngAfterViewInit() {
67
+ if (this.paginator) {
68
+ this.initPaginator(this.paginator);
69
+ this.changeDetectorRef.detectChanges();
70
+ }
71
+ }
72
+
73
+ ngOnDestroy(): void {
74
+ this.destroyed.next();
75
+ this.destroyed.complete();
76
+ }
77
+
78
+ private registerGlobalKeyboardShortcuts() {
79
+ this.globalKeyboardShortcuts.register(
80
+ [
81
+ {
82
+ modifiers: ['Ctrl/Cmd'],
83
+ keyCode: 'KeyA',
84
+ ignoreWhen: (event) =>
85
+ // SmallGrid doesn't support selection
86
+ this.entriesService.displayType === NodeEntriesDisplayType.SmallGrid,
87
+ callback: () => this.entriesService.toggleSelectAll(),
88
+ },
89
+ ],
90
+ { until: this.destroyed },
91
+ );
92
+ }
93
+
94
+ private initRemoteDataSource(dataSource: NodeDataSourceRemote<T>): void {
95
+ const pageSize = this.entriesGlobalService.getPaginatorSizeOptions(
96
+ this.entriesService.scope,
97
+ )[0];
98
+ dataSource.init({
99
+ paginationConfig: {
100
+ defaultPageSize: pageSize,
101
+ strategy: this.entriesService.paginationStrategy,
102
+ },
103
+ defaultSort: this.entriesService.sort,
104
+ });
105
+ if (this.entriesService.primaryInstance) {
106
+ // Automatic query-params handling is only supported by node-data-source-remote.
107
+ dataSource.registerQueryParameters(this.route);
108
+ }
109
+ }
110
+
111
+ private async initPaginator(paginator: MatPaginator) {
112
+ paginator._intl.itemsPerPageLabel = await this.translate
113
+ .get('PAGINATOR.itemsPerPageLabel')
114
+ .toPromise();
115
+ paginator._intl.nextPageLabel = await this.translate
116
+ .get('PAGINATOR.nextPageLabel')
117
+ .toPromise();
118
+ paginator._intl.previousPageLabel = await this.translate
119
+ .get('PAGINATOR.previousPageLabel')
120
+ .toPromise();
121
+ paginator._intl.getRangeLabel = (page, pageSize, length) =>
122
+ this.translate.instant('PAGINATOR.getRangeLabel', {
123
+ page: page + 1,
124
+ pageSize,
125
+ length,
126
+ pageCount: Math.ceil(length / pageSize),
127
+ });
128
+ // Connect data source.
129
+ this.entriesService.dataSource$.pipe(takeUntil(this.destroyed)).subscribe((dataSource) => {
130
+ if (dataSource instanceof NodeDataSourceRemote) {
131
+ (dataSource as NodeDataSourceRemote).paginator = paginator;
132
+ } else {
133
+ paginator.length = dataSource?.getTotal();
134
+ dataSource
135
+ ?.connectPagination()
136
+ .pipe(takeUntil(this.destroyed))
137
+ .subscribe(() => {
138
+ paginator.length = dataSource.getTotal();
139
+ });
140
+ }
141
+ });
142
+ }
143
+
144
+ openPage(page: PageEvent) {
145
+ this.entriesService.fetchData.emit({
146
+ offset: page.pageIndex * page.pageSize,
147
+ amount: page.pageSize,
148
+ reset: true,
149
+ });
150
+ }
151
+ }
@@ -0,0 +1,93 @@
1
+ import { A11yModule } from '@angular/cdk/a11y';
2
+ import { DragDropModule } from '@angular/cdk/drag-drop';
3
+ import { OverlayModule } from '@angular/cdk/overlay';
4
+ import { CommonModule } from '@angular/common';
5
+ import { NgModule } from '@angular/core';
6
+ import { FormsModule } from '@angular/forms';
7
+ import { MatBadgeModule } from '@angular/material/badge';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import { MatCheckboxModule } from '@angular/material/checkbox';
10
+ import { MatRippleModule } from '@angular/material/core';
11
+ import { MatMenuModule } from '@angular/material/menu';
12
+ import { MatPaginatorModule } from '@angular/material/paginator';
13
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
14
+ import { MatSortModule } from '@angular/material/sort';
15
+ import { MatTableModule } from '@angular/material/table';
16
+ import { MatTooltipModule } from '@angular/material/tooltip';
17
+ import { TranslateModule } from '@ngx-translate/core';
18
+ import { EduSharingUiCommonModule } from '../common/edu-sharing-ui-common.module';
19
+ import { NodesDragSourceDirective } from '../directives/drag-nodes/nodes-drag-source.directive';
20
+ import { NodesDragDirective } from '../directives/drag-nodes/nodes-drag.directive';
21
+ import { NodesDropTargetDirective } from '../directives/drag-nodes/nodes-drop-target.directive';
22
+ import { ListItemsModule } from '../list-items/list-items.module';
23
+ import { DragPreviewComponent } from './drag-preview/drag-preview.component';
24
+ import { ListItemLabelPipe } from './list-item-label.pipe';
25
+ import { NodeEntriesCardGridComponent } from './node-entries-card-grid/node-entries-card-grid.component';
26
+ import { NodeEntriesCardSmallComponent } from './node-entries-card-small/node-entries-card-small.component';
27
+ import { NodeEntriesCardComponent } from './node-entries-card/node-entries-card.component';
28
+ import { NodeEntriesGlobalOptionsComponent } from './node-entries-global-options/node-entries-global-options.component';
29
+ import { ColumnChooserComponent } from './node-entries-table/column-chooser/column-chooser.component';
30
+ import { NodeEntriesTableComponent } from './node-entries-table/node-entries-table.component';
31
+ import { NodeEntriesWrapperComponent } from './node-entries-wrapper.component';
32
+ import { NodeEntriesComponent } from './node-entries.component';
33
+ import { NodeRatingComponent } from './node-rating/node-rating.component';
34
+ import { NodeStatsBadgesComponent } from './node-stats-badges/node-stats-badges.component';
35
+ import { NodeTypeBadgeComponent } from './node-type-badge/node-type-badge.component';
36
+ import { OptionButtonComponent } from './option-button/option-button.component';
37
+ import { PreviewImageComponent } from './preview-image/preview-image.component';
38
+ import { SortSelectPanelComponent } from './sort-select-panel/sort-select-panel.component';
39
+
40
+ @NgModule({
41
+ declarations: [
42
+ ColumnChooserComponent,
43
+ DragPreviewComponent,
44
+ ListItemLabelPipe,
45
+ NodeEntriesCardComponent,
46
+ NodeEntriesCardGridComponent,
47
+ NodeEntriesCardSmallComponent,
48
+ NodeEntriesTableComponent,
49
+ NodeRatingComponent,
50
+ PreviewImageComponent,
51
+ NodeEntriesComponent,
52
+ NodeEntriesWrapperComponent,
53
+ NodeRatingComponent,
54
+ NodeTypeBadgeComponent,
55
+ OptionButtonComponent,
56
+ PreviewImageComponent,
57
+ SortSelectPanelComponent,
58
+ NodesDragDirective,
59
+ NodesDragSourceDirective,
60
+ NodesDropTargetDirective,
61
+ NodeEntriesGlobalOptionsComponent,
62
+ NodeStatsBadgesComponent,
63
+ ],
64
+ imports: [
65
+ CommonModule,
66
+ FormsModule,
67
+ A11yModule,
68
+ OverlayModule,
69
+ DragDropModule,
70
+ EduSharingUiCommonModule,
71
+ ListItemsModule,
72
+ MatCheckboxModule,
73
+ MatButtonModule,
74
+ MatBadgeModule,
75
+ MatMenuModule,
76
+ MatTableModule,
77
+ MatCheckboxModule,
78
+ MatPaginatorModule,
79
+ MatRippleModule,
80
+ MatSlideToggleModule,
81
+ MatSortModule,
82
+ MatTooltipModule,
83
+ TranslateModule,
84
+ ],
85
+ exports: [
86
+ NodeEntriesWrapperComponent,
87
+ NodesDragDirective,
88
+ NodesDragSourceDirective,
89
+ NodesDropTargetDirective,
90
+ ListItemLabelPipe,
91
+ ],
92
+ })
93
+ export class NodeEntriesModule {}