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,16 @@
1
+ import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+ import { ListItem } from '../../types/list-item';
4
+
5
+ @Component({
6
+ selector: 'es-node-row',
7
+ templateUrl: 'node-row.component.html',
8
+ styleUrls: ['node-row.component.scss'],
9
+ })
10
+ export class NodeRowComponent {
11
+ @ContentChild('customMetadata') customMetadataRef: TemplateRef<any>;
12
+ @Input() node: Node;
13
+ @Input() columns: ListItem[];
14
+
15
+ constructor() {}
16
+ }
@@ -0,0 +1,48 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { NetworkService, Repository } from 'ngx-edu-sharing-api';
3
+ import { NodeHelperService } from '../services/node-helper.service';
4
+
5
+ @Pipe({ name: 'appNodeSource' })
6
+ export class NodeSourcePipe implements PipeTransform {
7
+ private homeRepository: Repository;
8
+
9
+ constructor(private nodeHelper: NodeHelperService, private networkApi: NetworkService) {
10
+ this.networkApi.getHomeRepository().subscribe((homeRepository) => {
11
+ this.homeRepository = homeRepository;
12
+ });
13
+ }
14
+
15
+ transform(
16
+ replicationSource: string,
17
+ args: {
18
+ mode: 'text' | 'url' | 'escaped';
19
+ },
20
+ ): string {
21
+ const rawSrc = replicationSource ? replicationSource.toString().trim() : 'home';
22
+ if (args.mode === 'text') {
23
+ if (rawSrc === 'home') {
24
+ // FIXME: This will fix the pipe's return value to 'home' for calls before
25
+ // `this.homeRepository` was populated (although that doesn't seem to happen).
26
+ return this.homeRepository?.title || 'home';
27
+ }
28
+ return rawSrc;
29
+ } else if (args.mode === 'url') {
30
+ const src = this.escape(rawSrc);
31
+ return this.nodeHelper.getSourceIconPath(src);
32
+ } else if (args.mode === 'escaped') {
33
+ return this.escape(rawSrc);
34
+ }
35
+ return null;
36
+ }
37
+
38
+ private escape(src: string) {
39
+ if (!src) {
40
+ return src;
41
+ }
42
+ src = src.substring(src.lastIndexOf(':') + 1).toLowerCase();
43
+ src = src.replace(/\s/g, '_');
44
+ src = src.replace(/\./g, '_');
45
+ src = src.replace(/\//g, '_');
46
+ return src;
47
+ }
48
+ }
@@ -0,0 +1,51 @@
1
+ import { Observable, of } from 'rxjs';
2
+ import { NodeDataSource } from './node-data-source';
3
+ import { Node, GenericAuthority } from 'ngx-edu-sharing-api';
4
+
5
+ /**
6
+ * data source which joins multiple underlying data sources
7
+ * used for the "all" search
8
+ */
9
+ export class CombinedDataSource<T extends Node | GenericAuthority> extends NodeDataSource<T> {
10
+ constructor(private dataSources: NodeDataSource<T>[]) {
11
+ super();
12
+ }
13
+
14
+ connect(): Observable<T[]> {
15
+ // @TODO: Using a forkJoin and real connect would be better
16
+ return of(this.getData());
17
+ /*
18
+ return forkJoin(this.dataSources.map(ds => ds.connect())).pipe(
19
+ switchMap(d => d)
20
+ ) as Observable<T[]>;
21
+ */
22
+ }
23
+
24
+ disconnect() {}
25
+ hasMore() {
26
+ return this.dataSources.filter((ds) => ds.hasMore()).length > 0;
27
+ }
28
+
29
+ getData() {
30
+ return [].concat.apply(
31
+ [],
32
+ this.dataSources.map((ds) => ds.getData()),
33
+ );
34
+ }
35
+
36
+ isEmpty(): boolean {
37
+ return this.getData()?.length === 0;
38
+ }
39
+
40
+ getTotal() {
41
+ return this.dataSources.map((ds) => ds.getTotal()).reduce((a, b) => a + b);
42
+ }
43
+
44
+ isFullyLoaded() {
45
+ return this.getTotal() <= this.getData()?.length;
46
+ }
47
+
48
+ getDatasource(position: number) {
49
+ return this.dataSources[position];
50
+ }
51
+ }
@@ -0,0 +1,6 @@
1
+ import { NodeDataSource } from './node-data-source';
2
+
3
+ /**
4
+ * this is a special data source to provide custom card layouts into the node-entries components
5
+ */
6
+ export class CustomTemplatesDataSource extends NodeDataSource<any> {}
@@ -0,0 +1,6 @@
1
+ <div class="drag-preview" [matBadge]="selected.length > 1 ? selected.length : null">
2
+ <div class="drag-preview-icon">
3
+ <img *ngIf="node.iconURL" [src]="node | esNodeIcon" />
4
+ </div>
5
+ <es-list-text class="drag-preview-text" [node]="node" [item]="item"></es-list-text>
6
+ </div>
@@ -0,0 +1,35 @@
1
+ @use 'sass:color';
2
+ @import '../../../../assets/scss/mixins';
3
+
4
+ .drag-preview {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 16px;
8
+ height: 48px;
9
+ padding: 0 12px;
10
+ background-color: var(--palette-primary-50);
11
+ }
12
+
13
+ .drag-preview-icon {
14
+ width: 30px;
15
+ height: 30px;
16
+ padding: 3px;
17
+ margin: 1px 0;
18
+ background-color: #fff;
19
+ border-radius: 50%;
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ @include materialShadowSmall();
24
+ > img {
25
+ width: 18px;
26
+ height: auto;
27
+ }
28
+ }
29
+
30
+ .drag-preview-text {
31
+ max-width: 300px;
32
+ white-space: nowrap;
33
+ overflow: hidden;
34
+ text-overflow: ellipsis;
35
+ }
@@ -0,0 +1,15 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { ListItem } from '../../types/list-item';
3
+ import { Node } from 'ngx-edu-sharing-api';
4
+ import { NodeEntriesDataType } from '../entries-model';
5
+
6
+ @Component({
7
+ selector: 'es-drag-preview',
8
+ templateUrl: './drag-preview.component.html',
9
+ styleUrls: ['./drag-preview.component.scss'],
10
+ })
11
+ export class DragPreviewComponent<T extends NodeEntriesDataType> {
12
+ @Input() node: Node;
13
+ @Input() selected: T[];
14
+ @Input() item: ListItem;
15
+ }
@@ -0,0 +1,120 @@
1
+ import { Sort } from '@angular/material/sort';
2
+
3
+ import { SelectionModel } from '@angular/cdk/collections';
4
+ import { CustomOptions, OptionItem, Target } from '../types/option-item';
5
+ import { ListItem, ListItemSort } from '../types/list-item';
6
+ import { CanDrop, DragData, DropAction } from '../types/drag-drop';
7
+ import { Node, GenericAuthority } from 'ngx-edu-sharing-api';
8
+ import { ActionbarComponent } from '../actionbar/actionbar.component';
9
+
10
+ export type NodeRoot =
11
+ | 'MY_FILES'
12
+ | 'SHARED_FILES'
13
+ | 'MY_SHARED_FILES'
14
+ | 'TO_ME_SHARED_FILES'
15
+ | 'WORKFLOW_RECEIVE'
16
+ | 'RECYCLE'
17
+ | 'ALL_FILES';
18
+
19
+ export enum NodeEntriesDisplayType {
20
+ Table,
21
+ Grid,
22
+ SmallGrid,
23
+ }
24
+
25
+ export enum InteractionType {
26
+ // create router link
27
+ DefaultActionLink,
28
+ // emit an event
29
+ Emitter,
30
+ None,
31
+ }
32
+
33
+ export type ListOptions = { [key in Target]?: OptionItem[] };
34
+ export type ListOptionsConfig = {
35
+ actionbar?: ActionbarComponent;
36
+ parent?: Node;
37
+ customOptions?: CustomOptions;
38
+ };
39
+
40
+ export interface ListSortConfig extends Sort {
41
+ columns: ListItemSort[];
42
+ allowed?: boolean;
43
+ customSortingInProgress?: boolean;
44
+ }
45
+
46
+ export type DropTarget = Node | NodeRoot;
47
+
48
+ export interface DropSource<T extends NodeEntriesDataType> {
49
+ element: T[];
50
+ // sourceList: ListEventInterface<T>;
51
+ mode: DropAction;
52
+ }
53
+
54
+ export interface ListDragGropConfig<T extends NodeEntriesDataType> {
55
+ dragAllowed: boolean;
56
+ dropAllowed?: (dragData: DragData<T>) => CanDrop;
57
+ dropped?: (target: Node, source: DropSource<NodeEntriesDataType>) => void;
58
+ }
59
+
60
+ export enum ClickSource {
61
+ Preview,
62
+ Icon,
63
+ Metadata,
64
+ Comments,
65
+ Overlay,
66
+ }
67
+
68
+ export type NodeClickEvent<T extends NodeEntriesDataType> = {
69
+ element: T;
70
+ source: ClickSource;
71
+ attribute?: ListItem; // only when source === Metadata
72
+ };
73
+ export type FetchEvent = {
74
+ offset: number;
75
+ amount?: number;
76
+ /**
77
+ * is a reset of the current data required?
78
+ * this should be true if this was a pagination request
79
+ */
80
+ reset?: boolean;
81
+ };
82
+ export type NodeEntriesDataType = Node | GenericAuthority;
83
+ export type GridLayout = 'grid' | 'scroll';
84
+ export type GridConfig = {
85
+ /**
86
+ * max amount of rows that should be visible, unset for no limit
87
+ */
88
+ maxRows?: number;
89
+ /**
90
+ * layout, defaults to 'grid'
91
+ * 'scroll' may only be used when maxRows is not set
92
+ */
93
+ layout?: GridLayout;
94
+ };
95
+
96
+ export interface ListEventInterface<T extends NodeEntriesDataType> {
97
+ updateNodes(nodes: void | T[]): void;
98
+
99
+ getDisplayType(): NodeEntriesDisplayType;
100
+
101
+ setDisplayType(displayType: NodeEntriesDisplayType): void;
102
+
103
+ showReorderColumnsDialog(): void;
104
+
105
+ addVirtualNodes(virtual: T[]): void;
106
+
107
+ setOptions(options: ListOptions): void;
108
+
109
+ /**
110
+ * activate option (dropdown) generation
111
+ */
112
+ initOptionsGenerator(config: ListOptionsConfig): void | Promise<void>;
113
+
114
+ getSelection(): SelectionModel<T>;
115
+
116
+ /**
117
+ * triggered when nodes/objects are deleted and should not be shown in the list anymore
118
+ */
119
+ deleteNodes(objects: T[]): void;
120
+ }
@@ -0,0 +1,54 @@
1
+ import * as rxjs from 'rxjs';
2
+ import { BehaviorSubject, Observable } from 'rxjs';
3
+ import { distinctUntilChanged, map } from 'rxjs/operators';
4
+
5
+ export class ItemsCap<T> {
6
+ /** The number of items to which the data should be capped. */
7
+ get cap(): number | null {
8
+ return this._cap.value;
9
+ }
10
+ set cap(value: number | null) {
11
+ this._cap.next(value ?? null);
12
+ }
13
+ private _cap = new BehaviorSubject<number | null>(null);
14
+
15
+ /** Whether to temporarily disable capping. */
16
+ get disabled(): boolean {
17
+ return this._disabled.value;
18
+ }
19
+ set disabled(value: boolean) {
20
+ this._disabled.next(value);
21
+ }
22
+ private _disabled = new BehaviorSubject(false);
23
+
24
+ /** Whether there is more data available that is currently being capped. */
25
+ get isActivelyCapping(): boolean {
26
+ return this._isActivelyCapping;
27
+ }
28
+ private _isActivelyCapping = false;
29
+
30
+ private _effectiveCap: Observable<number | null> = rxjs
31
+ .combineLatest([this._cap, this._disabled])
32
+ .pipe(
33
+ map(([cap, disabled]) => (disabled ? null : cap)),
34
+ distinctUntilChanged(),
35
+ );
36
+
37
+ connect(dataStream: Observable<T[]>): Observable<T[]> {
38
+ return rxjs.combineLatest([this._effectiveCap, dataStream]).pipe(
39
+ map(([effectiveCap, originalData]) => {
40
+ const needToCap = this._needToCap(effectiveCap, originalData);
41
+ this._isActivelyCapping = needToCap;
42
+ if (needToCap) {
43
+ return originalData.slice(0, effectiveCap);
44
+ } else {
45
+ return originalData;
46
+ }
47
+ }),
48
+ );
49
+ }
50
+
51
+ private _needToCap(effectiveCap: number | null, originalData: T[]): boolean {
52
+ return effectiveCap && originalData?.length > effectiveCap;
53
+ }
54
+ }
@@ -0,0 +1,28 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+ import * as rxjs from 'rxjs';
4
+ import { Observable } from 'rxjs';
5
+ import { ListItem } from '../types/list-item';
6
+
7
+ @Pipe({ name: 'esListItemLabel' })
8
+ export class ListItemLabelPipe implements PipeTransform {
9
+ constructor(private translate: TranslateService) {}
10
+
11
+ transform(item: ListItem, args = { fallback: item.name }): Observable<string> {
12
+ const mapping = {
13
+ NODE: 'NODE',
14
+ COLLECTION: 'NODE',
15
+ NODE_PROPOSAL: 'NODE_PROPOSAL',
16
+ ORG: 'ORG',
17
+ GROUP: 'GROUP',
18
+ USER: 'USER',
19
+ };
20
+ if (item.label) {
21
+ return rxjs.of(item.label);
22
+ } else {
23
+ return this.translate.get(mapping[item.type] + '.' + item.name, {
24
+ fallback: args.fallback,
25
+ });
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,23 @@
1
+ @import '../../../assets/scss/mixins';
2
+
3
+ @mixin card-top-bar-badge {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ user-select: none;
8
+ border-radius: 50%;
9
+ background-color: #fff;
10
+ padding: 5px;
11
+ position: relative;
12
+ z-index: 1;
13
+ @include materialShadow();
14
+ i {
15
+ // TODO: do we want to style based on collection type?
16
+ font-size: 18px;
17
+ color: #333;
18
+ }
19
+ img {
20
+ width: 18px;
21
+ height: 18px;
22
+ }
23
+ }
@@ -0,0 +1,199 @@
1
+ import { NodeCache } from './node-cache';
2
+
3
+ const ARRAY_0_5 = [0, 1, 2, 3, 4];
4
+ const ARRAY_5_10 = [5, 6, 7, 8, 9];
5
+ const ARRAY_0_10 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
6
+
7
+ describe('NodeCache', () => {
8
+ let nodeCache: NodeCache<number>;
9
+
10
+ beforeEach(() => {
11
+ nodeCache = new NodeCache();
12
+ });
13
+
14
+ it('should find an exact match from 0', () => {
15
+ nodeCache.add({
16
+ startIndex: 0,
17
+ endIndex: 10,
18
+ data: ARRAY_0_10,
19
+ });
20
+ expect(nodeCache.get({ startIndex: 0, endIndex: 10 })).toEqual(ARRAY_0_10);
21
+ });
22
+
23
+ it('should find an exact match from 10', () => {
24
+ nodeCache.add({
25
+ startIndex: 10,
26
+ endIndex: 20,
27
+ data: ARRAY_0_10,
28
+ });
29
+ expect(nodeCache.get({ startIndex: 10, endIndex: 20 })).toEqual(ARRAY_0_10);
30
+ });
31
+
32
+ it('should find an exact match among multiple slices', () => {
33
+ nodeCache.add({
34
+ startIndex: 0,
35
+ endIndex: 10,
36
+ data: ARRAY_0_10,
37
+ });
38
+ nodeCache.add({
39
+ startIndex: 20,
40
+ endIndex: 30,
41
+ data: ARRAY_0_10,
42
+ });
43
+ expect(nodeCache.get({ startIndex: 20, endIndex: 30 })).toEqual(ARRAY_0_10);
44
+ });
45
+
46
+ it('should find a partial match', () => {
47
+ nodeCache.add({
48
+ startIndex: 0,
49
+ endIndex: 10,
50
+ data: ARRAY_0_10,
51
+ });
52
+ expect(nodeCache.get({ startIndex: 3, endIndex: 7 })).toEqual([3, 4, 5, 6]);
53
+ });
54
+
55
+ it('should merge two connected slices', () => {
56
+ nodeCache.add({
57
+ startIndex: 0,
58
+ endIndex: 5,
59
+ data: ARRAY_0_5,
60
+ });
61
+ nodeCache.add({
62
+ startIndex: 5,
63
+ endIndex: 10,
64
+ data: ARRAY_5_10,
65
+ });
66
+ expect(nodeCache.get({ startIndex: 0, endIndex: 10 })).toEqual(ARRAY_0_10);
67
+ });
68
+
69
+ it('should remove residual slices', () => {
70
+ nodeCache.add({
71
+ startIndex: 0,
72
+ endIndex: 5,
73
+ data: ARRAY_0_5,
74
+ });
75
+ nodeCache.add({
76
+ startIndex: 5,
77
+ endIndex: 10,
78
+ data: ARRAY_5_10,
79
+ });
80
+ expect(nodeCache['_slices'].length).toEqual(1);
81
+ });
82
+
83
+ it('should merge two reverse-connected slices', () => {
84
+ nodeCache.add({
85
+ startIndex: 5,
86
+ endIndex: 10,
87
+ data: ARRAY_5_10,
88
+ });
89
+ nodeCache.add({
90
+ startIndex: 0,
91
+ endIndex: 5,
92
+ data: ARRAY_0_5,
93
+ });
94
+ expect(nodeCache.get({ startIndex: 0, endIndex: 10 })).toEqual(ARRAY_0_10);
95
+ });
96
+
97
+ it('should merge two overlapping slices', () => {
98
+ nodeCache.add({
99
+ startIndex: 0,
100
+ endIndex: 7,
101
+ data: [0, 1, 2, 3, 4, 5, 6],
102
+ });
103
+ nodeCache.add({
104
+ startIndex: 3,
105
+ endIndex: 8,
106
+ data: [3, 4, 5, 6, 7],
107
+ });
108
+ expect(nodeCache.get({ startIndex: 0, endIndex: 8 })).toEqual([0, 1, 2, 3, 4, 5, 6, 7]);
109
+ });
110
+
111
+ it('should fill a gap', () => {
112
+ nodeCache.add({
113
+ startIndex: 0,
114
+ endIndex: 3,
115
+ data: [0, 1, 2],
116
+ });
117
+ nodeCache.add({
118
+ startIndex: 6,
119
+ endIndex: 9,
120
+ data: [6, 7, 8],
121
+ });
122
+ nodeCache.add({
123
+ startIndex: 3,
124
+ endIndex: 6,
125
+ data: [3, 4, 5],
126
+ });
127
+ expect(nodeCache.get({ startIndex: 0, endIndex: 9 })).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]);
128
+ });
129
+
130
+ it('should find an exact missing range at the beginning', () => {
131
+ nodeCache.add({
132
+ startIndex: 6,
133
+ endIndex: 9,
134
+ data: [6, 7, 8],
135
+ });
136
+ expect(nodeCache.getMissingRange({ startIndex: 0, endIndex: 9 })).toEqual({
137
+ startIndex: 0,
138
+ endIndex: 6,
139
+ });
140
+ });
141
+
142
+ it('should find an exact missing range at the end', () => {
143
+ nodeCache.add({
144
+ startIndex: 0,
145
+ endIndex: 3,
146
+ data: [0, 1, 2],
147
+ });
148
+ expect(nodeCache.getMissingRange({ startIndex: 0, endIndex: 9 })).toEqual({
149
+ startIndex: 3,
150
+ endIndex: 9,
151
+ });
152
+ });
153
+
154
+ it('should find an exact missing range in the middle', () => {
155
+ nodeCache.add({
156
+ startIndex: 0,
157
+ endIndex: 3,
158
+ data: [0, 1, 2],
159
+ });
160
+ nodeCache.add({
161
+ startIndex: 6,
162
+ endIndex: 9,
163
+ data: [6, 7, 8],
164
+ });
165
+ expect(nodeCache.getMissingRange({ startIndex: 0, endIndex: 9 })).toEqual({
166
+ startIndex: 3,
167
+ endIndex: 6,
168
+ });
169
+ });
170
+
171
+ it('should find an overlapped missing range at the beginning', () => {
172
+ nodeCache.add({
173
+ startIndex: 6,
174
+ endIndex: 9,
175
+ data: [6, 7, 8],
176
+ });
177
+ expect(nodeCache.getMissingRange({ startIndex: 0, endIndex: 7 })).toEqual({
178
+ startIndex: 0,
179
+ endIndex: 6,
180
+ });
181
+ });
182
+
183
+ it('should find an overlapped missing range in the middle', () => {
184
+ nodeCache.add({
185
+ startIndex: 0,
186
+ endIndex: 3,
187
+ data: [0, 1, 2],
188
+ });
189
+ nodeCache.add({
190
+ startIndex: 6,
191
+ endIndex: 9,
192
+ data: [6, 7, 8],
193
+ });
194
+ expect(nodeCache.getMissingRange({ startIndex: 2, endIndex: 7 })).toEqual({
195
+ startIndex: 3,
196
+ endIndex: 6,
197
+ });
198
+ });
199
+ });
@@ -0,0 +1,81 @@
1
+ import { notNull } from '../util/functions';
2
+
3
+ export interface NodeCacheRange {
4
+ startIndex: number;
5
+ endIndex: number;
6
+ }
7
+
8
+ export interface NodeCacheSlice<T> extends NodeCacheRange {
9
+ data: readonly T[];
10
+ }
11
+
12
+ export class NodeCache<T> {
13
+ private _slices: readonly NodeCacheSlice<T>[] = [];
14
+
15
+ add(slice: NodeCacheSlice<T>): void {
16
+ if (slice.endIndex - slice.startIndex !== slice.data.length) {
17
+ throw new Error('Tried to add invalid slice to cache: ' + JSON.stringify(slice));
18
+ }
19
+ this._slices = this._normalizeSlices([...this._slices, slice]);
20
+ }
21
+
22
+ clear() {
23
+ this._slices = [];
24
+ }
25
+
26
+ get(range: NodeCacheRange): T[] | null {
27
+ for (const slice of this._slices) {
28
+ if (slice.startIndex <= range.startIndex && slice.endIndex >= range.endIndex) {
29
+ return slice.data.slice(
30
+ range.startIndex - slice.startIndex,
31
+ range.endIndex - slice.startIndex,
32
+ );
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ getMissingRange(requestedRange: NodeCacheRange): NodeCacheRange | null {
39
+ let fromIndex = requestedRange.startIndex;
40
+ let toIndex = requestedRange.endIndex;
41
+ for (const slice of this._slices) {
42
+ if (slice.startIndex <= fromIndex && slice.endIndex > fromIndex) {
43
+ fromIndex = slice.endIndex;
44
+ } else if (slice.startIndex < toIndex && slice.endIndex >= toIndex) {
45
+ toIndex = slice.startIndex;
46
+ }
47
+ if (fromIndex >= toIndex) {
48
+ return null;
49
+ }
50
+ }
51
+ return { startIndex: fromIndex, endIndex: toIndex };
52
+ }
53
+
54
+ private _normalizeSlices(slices: NodeCacheSlice<T>[]): NodeCacheSlice<T>[] {
55
+ slices.sort((lhs, rhs) => lhs.startIndex - rhs.startIndex);
56
+ for (let i = 0; i < slices.length - 1; i++) {
57
+ for (let j = i + 1; j < slices.length; j++) {
58
+ if (this._canMerge(slices[i], slices[j])) {
59
+ slices[i] = this._merge(slices[i], slices[j]);
60
+ slices[j] = null; // Mark for deletion
61
+ }
62
+ }
63
+ }
64
+ return slices.filter(notNull);
65
+ }
66
+
67
+ private _canMerge(lhs: NodeCacheSlice<T>, rhs: NodeCacheSlice<T>): boolean {
68
+ if (!lhs || !rhs) {
69
+ return false;
70
+ }
71
+ return lhs.endIndex >= rhs.startIndex;
72
+ }
73
+
74
+ private _merge(lhs: NodeCacheSlice<T>, rhs: NodeCacheSlice<T>): NodeCacheSlice<T> {
75
+ return {
76
+ startIndex: lhs.startIndex,
77
+ endIndex: rhs.endIndex,
78
+ data: [...lhs.data, ...rhs.data.slice(lhs.endIndex - rhs.startIndex)],
79
+ };
80
+ }
81
+ }