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,32 @@
1
+ @import '../../../assets/scss/mixins';
2
+
3
+ :host {
4
+ // Draw focus highlight above content background
5
+ position: relative;
6
+ // Make `overflow: hidden` for ripples work for `a`
7
+ display: flex;
8
+ & > * {
9
+ flex-grow: 1;
10
+ }
11
+ > a {
12
+ text-decoration: none;
13
+ }
14
+ }
15
+
16
+ // Handle keyboard focus.
17
+ //
18
+ // If doing this here becomes a problem, consider introducing an input parameter to disable this.
19
+ .cdk-keyboard-focused {
20
+ @include setGlobalKeyboardFocus();
21
+ }
22
+
23
+ button {
24
+ background-color: unset;
25
+ border: unset;
26
+ padding: unset;
27
+ text-align: unset;
28
+ }
29
+
30
+ div.node-url-wrapper {
31
+ cursor: pointer;
32
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ AfterViewInit,
3
+ Component,
4
+ ElementRef,
5
+ EventEmitter,
6
+ Input,
7
+ Output,
8
+ ViewChild,
9
+ } from '@angular/core';
10
+ import { Node } from 'ngx-edu-sharing-api';
11
+ import { NodeHelperService } from '../services/node-helper.service';
12
+
13
+ // TODO: Decide if providing focus highlights and ripples with this component is a good idea. When
14
+ // using `app-node-url` for cards, we might need highlights and ripples for the whole card while
15
+ // `app-node-url` should only wrap the title since links with lots of content confuse screen
16
+ // readers.
17
+
18
+ const NODE_URL_TAG_NAME = 'es-node-url';
19
+
20
+ @Component({
21
+ selector: NODE_URL_TAG_NAME,
22
+ templateUrl: 'node-url.component.html',
23
+ styleUrls: ['node-url.component.scss'],
24
+ })
25
+ export class NodeUrlComponent implements AfterViewInit {
26
+ @ViewChild('link') link: ElementRef<HTMLAnchorElement>;
27
+
28
+ @Input() node: Node;
29
+ @Input() nodes: Node[];
30
+ @Input() target: string;
31
+ @Input() scope: string;
32
+ /**
33
+ * custom query params to include
34
+ */
35
+ @Input() queryParams: { [key: string]: string | number | boolean } = {};
36
+ /**
37
+ * link: a element
38
+ * button: button element
39
+ * wrapper: div element with behavior "like" a link
40
+ */
41
+ @Input() mode: 'link' | 'button' | 'wrapper' = 'link';
42
+ @Input() disabled = false;
43
+ /**
44
+ * Show the ripple effect even when disabled.
45
+ *
46
+ * @deprecated Temporary workaround for list-table, which *sometimes* uses it's on click
47
+ * bindings.
48
+ */
49
+ @Input() alwaysRipple = false;
50
+ @Input('aria-describedby') ariaDescribedby: string;
51
+ @Input('aria-label') ariaLabel = true;
52
+
53
+ @Output() buttonClick = new EventEmitter<MouseEvent>();
54
+
55
+ /**
56
+ * Whether this instance of `NodeUrl` is nested inside another `NodeUrl`.
57
+ */
58
+ // We use nested `NodeUrl`s for a11y where we have a `NodeUrl` in wrapper mode to maximize
59
+ // clickable area and one in link mode that only contains the title. We only want the outmost
60
+ // `NodeUrl` to apply the ripple effect.
61
+ //
62
+ // Note that nesting `NodeUrl`s is only necessary when we want to provide hover effects on parts
63
+ // of the outer `NodeUrl`. If we don't need that, it would be easier to attach a pseudo `:after`
64
+ // element to the inner `NodeUrl` that expands its click area.
65
+ isNested: boolean;
66
+
67
+ constructor(
68
+ private nodeHelper: NodeHelperService,
69
+ private elementRef: ElementRef<HTMLElement>,
70
+ ) {}
71
+
72
+ ngAfterViewInit(): void {
73
+ setTimeout(() => {
74
+ this.isNested = this.getIsNested();
75
+ });
76
+ }
77
+
78
+ getState() {
79
+ return {
80
+ scope: this.scope,
81
+ };
82
+ }
83
+
84
+ get(mode: 'routerLink' | 'queryParams'): any {
85
+ const result: any = this.nodeHelper.getNodeLink(mode, this.node);
86
+ if (mode === 'queryParams' && this.queryParams) {
87
+ Object.keys(this.queryParams).forEach((k) => (result[k] = this.queryParams[k]));
88
+ }
89
+ return result;
90
+ }
91
+
92
+ focus(): void {
93
+ this.link.nativeElement.focus();
94
+ }
95
+
96
+ clickWrapper(event: MouseEvent) {
97
+ const eventCopy = copyClickEvent(event);
98
+ this.link.nativeElement.dispatchEvent(eventCopy);
99
+ event.preventDefault();
100
+ }
101
+
102
+ private getIsNested(): boolean {
103
+ let ancestor = this.elementRef.nativeElement.parentElement;
104
+ while (ancestor) {
105
+ if (ancestor.tagName === NODE_URL_TAG_NAME.toUpperCase()) {
106
+ return true;
107
+ }
108
+ ancestor = ancestor.parentElement;
109
+ }
110
+ return false;
111
+ }
112
+ }
113
+
114
+ function copyClickEvent(event: MouseEvent): MouseEvent {
115
+ // On Firefox, a middle click via neither the 'click', nor the 'auxclick' event cause a new tab
116
+ // to be opened when triggered programmatically. As a workaround, we simulate a ctrl click
117
+ // instead of a middle click. This matches Firefox's defaults, but we cannot account for
118
+ // changed middle-click behavior.
119
+ if (event.type === 'auxclick' && event.button === 1 && isFirefox()) {
120
+ return copyClickEvent({ ...event, type: 'click', ctrlKey: true, button: 0 });
121
+ }
122
+ // It would seem better to use `event.type` instead of hard-coding 'click', but that doesn't
123
+ // have the desired effect for non-click events when dispatched.
124
+ return new MouseEvent('click', {
125
+ cancelable: true,
126
+ button: event.button,
127
+ ctrlKey: event.ctrlKey,
128
+ shiftKey: event.shiftKey,
129
+ altKey: event.altKey,
130
+ metaKey: event.metaKey,
131
+ });
132
+ }
133
+
134
+ function isFirefox(): boolean {
135
+ return navigator.userAgent.includes('Firefox');
136
+ }
@@ -0,0 +1,24 @@
1
+ import { PipeTransform, Pipe } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+
4
+ @Pipe({ name: 'formatSize' })
5
+ export class FormatSizePipe implements PipeTransform {
6
+ constructor(private translate: TranslateService) {}
7
+
8
+ transform(value: any, args: string[] = null): string {
9
+ let names = ['bytes', 'KB', 'MB', 'GB', 'TB'];
10
+ let i = 0;
11
+ if (isNaN(value)) return value;
12
+ if (value == null) value = 0;
13
+ while (value >= 1024 && i < names.length) {
14
+ value /= 1024;
15
+ i++;
16
+ }
17
+ //return value+" "+names[i];
18
+ return (
19
+ value.toLocaleString(this.translate.currentLang, { maximumFractionDigits: 1 }) +
20
+ ' ' +
21
+ names[i]
22
+ );
23
+ }
24
+ }
@@ -0,0 +1,39 @@
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 { map } from 'rxjs/operators';
6
+ import { DateHelper, FormatOptions } from '../util/DateHelper';
7
+
8
+ @Pipe({ name: 'formatDate' })
9
+ export class FormatDatePipe implements PipeTransform {
10
+ constructor(private translate: TranslateService) {}
11
+
12
+ transform(
13
+ value: Date | number | string,
14
+ args?: { time?: boolean; date?: boolean; relative?: boolean },
15
+ ): string;
16
+ transform(
17
+ value: Date | number | string,
18
+ args: { time?: boolean; date?: boolean; relative?: boolean; async: true },
19
+ ): Observable<string>;
20
+ transform(
21
+ value: Date | number | string,
22
+ args: { time?: boolean; date?: boolean; relative?: boolean; async?: boolean } = null,
23
+ ): string | Observable<string> {
24
+ if (!value) {
25
+ return args?.async ? rxjs.of('') : '';
26
+ }
27
+ let options = new FormatOptions();
28
+ if (args && args.time != null) options.showAlwaysTime = args.time;
29
+ if (args && args.date != null) options.showDate = args.date;
30
+ if (args && args.relative !== null) options.useRelativeLabels = args.relative;
31
+ if (args?.async) {
32
+ return this.translate
33
+ .get('dummy') // Wait for the translation service to be ready
34
+ .pipe(map(() => DateHelper.formatDate(this.translate, value, options)));
35
+ } else {
36
+ return DateHelper.formatDate(this.translate, value, options);
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,11 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+ import { RepoUrlService } from '../services/repo-url.service';
4
+
5
+ @Pipe({ name: 'esNodeIcon' })
6
+ export class NodeIconPipe implements PipeTransform {
7
+ constructor(private repoUrlService: RepoUrlService) {}
8
+ transform(node: Node) {
9
+ return this.repoUrlService.getRepoUrl(node.iconURL, node);
10
+ }
11
+ }
@@ -0,0 +1,18 @@
1
+ import { PipeTransform, Pipe } from '@angular/core';
2
+ import { Node, RestConstants } from 'ngx-edu-sharing-api';
3
+
4
+ @Pipe({ name: 'NodeImageSize' })
5
+ export class NodeImageSizePipe implements PipeTransform {
6
+ transform(node: Node, args: any = null): string {
7
+ const width = parseFloat(node.properties[RestConstants.CCM_PROP_WIDTH]?.[0]);
8
+ const height = parseFloat(node.properties[RestConstants.CCM_PROP_HEIGHT]?.[0]);
9
+ const megapixel = Math.round((width * height) / 1000000);
10
+ if (width && height) {
11
+ if (megapixel > 1) {
12
+ return megapixel + ' Megapixel';
13
+ }
14
+ return Math.round(width) + 'x' + Math.round(height);
15
+ }
16
+ return '';
17
+ }
18
+ }
@@ -0,0 +1,71 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
3
+ import { NetworkService } from 'ngx-edu-sharing-api';
4
+ import * as rxjs from 'rxjs';
5
+ import { Observable } from 'rxjs';
6
+ import { map, switchMap } from 'rxjs/operators';
7
+ import { NodeHelperService } from '../services/node-helper.service';
8
+ import { Node } from 'ngx-edu-sharing-api';
9
+ import { RepoUrlService } from '../services/repo-url.service';
10
+ import { RestConstants } from 'ngx-edu-sharing-api';
11
+
12
+ interface NodeImagePreferences {
13
+ crop?: boolean;
14
+ maxWidth?: number;
15
+ maxHeight?: number;
16
+ width?: number;
17
+ height?: number;
18
+ }
19
+
20
+ @Pipe({ name: 'esNodeImage' })
21
+ export class NodeImagePipe implements PipeTransform {
22
+ constructor(
23
+ private nodeHelper: NodeHelperService,
24
+ private sanitizer: DomSanitizer,
25
+ private repoUrlService: RepoUrlService,
26
+ private networkApi: NetworkService,
27
+ ) {}
28
+
29
+ transform(node: Node, preferences: NodeImagePreferences): Observable<SafeResourceUrl> {
30
+ if (this.nodeHelper.isNodeCollection(node) && node.preview.isIcon) {
31
+ return null;
32
+ } else if (node.preview.data) {
33
+ return rxjs.of(
34
+ this.sanitizer.bypassSecurityTrustResourceUrl(
35
+ 'data:' + node.preview.mimetype + ';base64,' + node.preview.data,
36
+ ),
37
+ );
38
+ } else {
39
+ return this.getPreviewUrl(node, preferences);
40
+ }
41
+ }
42
+
43
+ private getPreviewUrl(node: Node, preferences: NodeImagePreferences): Observable<string> {
44
+ return this.isEduSharingNode(node).pipe(
45
+ switchMap(async (isEduSharingNode) => {
46
+ let url = await this.repoUrlService.getRepoUrl(node.preview.url, node);
47
+ if (isEduSharingNode) {
48
+ url += Object.entries(preferences)
49
+ .map(([key, value]) => `&${key}=${value}`)
50
+ .join('');
51
+ }
52
+ return url;
53
+ }),
54
+ );
55
+ }
56
+
57
+ private isEduSharingNode(node: Node): Observable<boolean> {
58
+ return rxjs
59
+ .forkJoin([
60
+ this.networkApi.isFromHomeRepository(node),
61
+ this.networkApi.getRepositoryOfNode(node),
62
+ ])
63
+ .pipe(
64
+ map(
65
+ ([isFromHomeRepository, repository]) =>
66
+ isFromHomeRepository ||
67
+ repository?.repositoryType === RestConstants.REPOSITORY_TYPE_ALFRESCO,
68
+ ),
69
+ );
70
+ }
71
+ }
@@ -0,0 +1,41 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { ConfigService, Person } from 'ngx-edu-sharing-api';
3
+
4
+ @Pipe({ name: 'nodePersonName' })
5
+ export class NodePersonNamePipe implements PipeTransform {
6
+ constructor(private config: ConfigService) {}
7
+ transform(person: Person | any, args: any = null): string {
8
+ let field = this.config.instant('userDisplayName', 'fullName');
9
+ if (person == null) return null;
10
+ if (field == 'authorityName') {
11
+ if (person.authorityName == null) field = 'fullName';
12
+ else return person.authorityName;
13
+ }
14
+ if (field == 'fullName') {
15
+ if (person.profile) {
16
+ return (
17
+ (person.profile.firstName ? person.profile.firstName : '') +
18
+ ' ' +
19
+ (person.profile.lastName ? person.profile.lastName : '')
20
+ ).trim();
21
+ }
22
+ return (
23
+ (person.firstName ? person.firstName : '') +
24
+ ' ' +
25
+ (person.lastName ? person.lastName : '')
26
+ ).trim();
27
+ }
28
+ if (field == 'firstName' || field == 'lastName') {
29
+ if (person.profile) {
30
+ return person.profile[field];
31
+ }
32
+ return person[field];
33
+ }
34
+ if (field == 'email') {
35
+ if (person.profile && person.profile.email) return person.profile.email;
36
+ if (person.email == null) return person.mailbox;
37
+ return person.email;
38
+ }
39
+ return person[field];
40
+ }
41
+ }
@@ -0,0 +1,12 @@
1
+ import { PipeTransform, Pipe } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+ import { Node } from 'ngx-edu-sharing-api';
4
+ import { RestHelper } from '../util/rest-helper';
5
+
6
+ @Pipe({ name: 'nodeTitle' })
7
+ export class NodeTitlePipe implements PipeTransform {
8
+ transform(node: Node, args: string[] = null): string {
9
+ return RestHelper.getTitle(node);
10
+ }
11
+ constructor(private translate: TranslateService) {}
12
+ }
@@ -0,0 +1,32 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+ import { OptionItem } from '../types/option-item';
4
+
5
+ @Pipe({ name: 'optionTooltip' })
6
+ export class OptionTooltipPipe implements PipeTransform {
7
+ constructor(private translate: TranslateService) {}
8
+
9
+ transform(option: OptionItem, args: string[] = null): string {
10
+ return (
11
+ this.translate.instant(option.name) +
12
+ (option.keyboardShortcut ? ' (' + this.getKeyInfo(option) + ')' : '')
13
+ );
14
+ }
15
+
16
+ getKeyInfo(option: OptionItem) {
17
+ if (!option.keyboardShortcut) {
18
+ return '';
19
+ }
20
+ const modifiers = [];
21
+ if (option.keyboardShortcut.modifiers?.includes('Shift')) {
22
+ modifiers.push(this.translate.instant('KEY_MODIFIER.SHIFT'));
23
+ }
24
+ if (option.keyboardShortcut.modifiers?.includes('Ctrl/Cmd')) {
25
+ modifiers.push(this.translate.instant('KEY_MODIFIER.CTRL'));
26
+ }
27
+ return (
28
+ (modifiers.length ? modifiers.join(' + ') + ' + ' : '') +
29
+ option.keyboardShortcut.keyCode.replace('Key', '')
30
+ );
31
+ }
32
+ }
@@ -0,0 +1,21 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+
4
+ @Pipe({ name: 'replaceChars' })
5
+ export class ReplaceCharsPipe implements PipeTransform {
6
+ transform(value: string, args: any): string {
7
+ let i = 0;
8
+ if (!Array.isArray(args.search)) {
9
+ args.search = [args.search];
10
+ }
11
+ if (args.replace && !Array.isArray(args.replace)) {
12
+ args.replace = [args.replace];
13
+ }
14
+ for (let arg of args.search) {
15
+ value = value.split(arg).join(args.replace ? args.replace[i] : '');
16
+ i++;
17
+ }
18
+ return value;
19
+ }
20
+ constructor(private translate: TranslateService) {}
21
+ }
@@ -0,0 +1,11 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+ import { VCard } from '../util/VCard';
4
+
5
+ @Pipe({ name: 'vcardName' })
6
+ export class VCardNamePipe implements PipeTransform {
7
+ constructor(private translate: TranslateService) {}
8
+ transform(authority: string, args: string[] = null): string {
9
+ return authority ? new VCard(authority).getDisplayName() : '';
10
+ }
11
+ }
@@ -0,0 +1,4 @@
1
+ export abstract class AppService {
2
+ abstract isRunningApp(): boolean;
3
+ abstract getLanguage(): Promise<string>;
4
+ }
@@ -0,0 +1,10 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { KeyboardShortcut } from '../../types/keyboard-shortcuts';
3
+ import { Observable } from 'rxjs';
4
+
5
+ @Injectable()
6
+ export abstract class KeyboardShortcutsService {
7
+ abstract register(shortcuts: KeyboardShortcut[], { until }: { until: Observable<void> }): void;
8
+
9
+ abstract shouldIgnoreShortcut(event: KeyboardEvent): boolean;
10
+ }
@@ -0,0 +1,29 @@
1
+ import { OptionData, OptionsHelperComponents } from '../options-helper-data.service';
2
+ import { OptionItem, Target } from '../../types/option-item';
3
+ import { Node } from 'ngx-edu-sharing-api';
4
+
5
+ export abstract class OptionsHelperService {
6
+ abstract wrapOptionCallbacks(data: OptionData): OptionData;
7
+
8
+ abstract refreshComponents(
9
+ components: OptionsHelperComponents,
10
+ data: OptionData,
11
+ refreshListOptions: boolean,
12
+ ): void;
13
+
14
+ abstract getAvailableOptions(
15
+ target: Target,
16
+ objects: Node[],
17
+ components: OptionsHelperComponents,
18
+ data: OptionData,
19
+ ): OptionItem[];
20
+
21
+ abstract pasteNode(components: OptionsHelperComponents, data: OptionData, nodes: Node[]): void;
22
+
23
+ abstract filterOptions(
24
+ options: OptionItem[],
25
+ target: Target,
26
+ data: OptionData,
27
+ objects: Node[] | any,
28
+ ): OptionItem[];
29
+ }
@@ -0,0 +1,5 @@
1
+ export abstract class Toast {
2
+ abstract toast(message: string, translationParameters?: any): void;
3
+
4
+ abstract error(errorObject: any, message?: string): void;
5
+ }
@@ -0,0 +1,101 @@
1
+ import { Injectable } from '@angular/core';
2
+ import * as rxjs from 'rxjs';
3
+ import { Observable } from 'rxjs';
4
+ import { first, map } from 'rxjs/operators';
5
+ import { AccessibilitySettings } from '../types/accessibillity';
6
+ import { SessionStorageService } from 'ngx-edu-sharing-api';
7
+
8
+ @Injectable({
9
+ providedIn: 'root',
10
+ })
11
+ export class AccessibilityService {
12
+ private static readonly STORAGE_PREFIX = 'accessibility_';
13
+
14
+ constructor(private storage: SessionStorageService) {}
15
+
16
+ async set(accessibilitySettings: Partial<AccessibilitySettings>): Promise<void> {
17
+ const currentValues = await this.observeAll().pipe(first()).toPromise();
18
+ const values = Object.entries(accessibilitySettings)
19
+ .filter(
20
+ ([key, value]) =>
21
+ // check if value has been modified
22
+ (currentValues as any)[key] !== value,
23
+ )
24
+ .reduce((acc, [key, value]) => {
25
+ acc[AccessibilityService.STORAGE_PREFIX + key] = value;
26
+ return acc;
27
+ }, {} as { [key: string]: any });
28
+ if (Object.keys(values).length > 0) {
29
+ return this.storage.setValues(values);
30
+ }
31
+ }
32
+
33
+ observe<K extends keyof AccessibilitySettings>(key: K): Observable<AccessibilitySettings[K]>;
34
+ observe<K extends keyof AccessibilitySettings>(
35
+ keys: K[],
36
+ ): Observable<Pick<AccessibilitySettings, K>>;
37
+ observe(): Observable<AccessibilitySettings>;
38
+ observe<K extends keyof AccessibilitySettings>(
39
+ keyOrKeys?: K | K[],
40
+ ): Observable<AccessibilitySettings[K] | Pick<AccessibilitySettings, K>> {
41
+ if (typeof keyOrKeys === 'string') {
42
+ return this.observeSingle(keyOrKeys);
43
+ } else if (Array.isArray(keyOrKeys)) {
44
+ return this.observeMultiple(keyOrKeys);
45
+ } else {
46
+ return this.observeAll();
47
+ }
48
+ }
49
+
50
+ private observeSingle<K extends keyof AccessibilitySettings>(
51
+ key: K,
52
+ ): Observable<AccessibilitySettings[K]> {
53
+ const defaultValues = new AccessibilitySettings();
54
+ return this.storage.observe(AccessibilityService.STORAGE_PREFIX + key, defaultValues[key]);
55
+ }
56
+
57
+ private observeMultiple<K extends keyof AccessibilitySettings>(
58
+ keys: K[],
59
+ ): Observable<Pick<AccessibilitySettings, K>> {
60
+ const defaultValues = new AccessibilitySettings();
61
+ return combineLatest(
62
+ keys.reduce((acc, key) => {
63
+ acc[key] = this.storage.observe(
64
+ AccessibilityService.STORAGE_PREFIX + key,
65
+ defaultValues[key],
66
+ );
67
+ return acc;
68
+ }, {} as { [key in keyof AccessibilitySettings]: Observable<AccessibilitySettings[key]> }),
69
+ );
70
+ }
71
+
72
+ private observeAll(): Observable<AccessibilitySettings> {
73
+ return this.observeMultiple(
74
+ Object.keys(new AccessibilitySettings()) as Array<keyof AccessibilitySettings>,
75
+ );
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Rxjs.combineLatest on objects instead of arrays.
81
+ *
82
+ * This is a standard function in Rxjs 7, but we wrap it here for Rxjs 6.
83
+ */
84
+ function combineLatest<T, K extends keyof T>(sources: {
85
+ [key in K]: Observable<T[key]>;
86
+ }): Observable<T> {
87
+ return rxjs
88
+ .combineLatest(
89
+ Object.entries(sources).map(([key, source]) =>
90
+ (source as Observable<T[K]>).pipe(map((result) => ({ key, result }))),
91
+ ),
92
+ )
93
+ .pipe(
94
+ map((results) =>
95
+ results.reduce((acc, { key, result }) => {
96
+ acc[key as K] = result;
97
+ return acc;
98
+ }, {} as T),
99
+ ),
100
+ );
101
+ }
@@ -0,0 +1,29 @@
1
+ import { EventEmitter, Injectable } from '@angular/core';
2
+ import { Node } from 'ngx-edu-sharing-api';
3
+
4
+ /**
5
+ * An application-wide event broker.
6
+ */
7
+ @Injectable({
8
+ providedIn: 'root',
9
+ })
10
+ export class LocalEventsService {
11
+ /**
12
+ * The metadata of one or more nodes have been updated.
13
+ *
14
+ * The emitted value is the array of updated nodes.
15
+ *
16
+ * The emitter should not be triggered with an empty array or null.
17
+ */
18
+ readonly nodesChanged = new EventEmitter<Node[]>();
19
+ /**
20
+ * One or more nodes have been moved to the recycle bin.
21
+ *
22
+ * The emitted value is the array of former nodes.
23
+ *
24
+ * The emitter should not be triggered with an empty array or null.
25
+ */
26
+ // FIXME: Maybe a `nodesMoved` emitter would make for sense for updating lists that used to
27
+ // include a node and lists that the node was moved to.
28
+ readonly nodesDeleted = new EventEmitter<Node[]>();
29
+ }