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.
- package/.browserslistrc +16 -0
- package/.eslintrc.json +44 -0
- package/README.md +40 -0
- package/assets/scss/mixins.scss +95 -0
- package/assets/scss/variables.scss +33 -0
- package/karma.conf.js +42 -0
- package/ng-package.json +10 -0
- package/package.json +19 -0
- package/src/lib/actionbar/actionbar.component.html +59 -0
- package/src/lib/actionbar/actionbar.component.scss +123 -0
- package/src/lib/actionbar/actionbar.component.ts +174 -0
- package/src/lib/common/edu-sharing-ui-common.module.ts +80 -0
- package/src/lib/directives/border-box-observer.directive.ts +75 -0
- package/src/lib/directives/check-text-overflow.directive.ts +61 -0
- package/src/lib/directives/drag-nodes/drag-nodes.ts +32 -0
- package/src/lib/directives/drag-nodes/nodes-drag-source.directive.ts +79 -0
- package/src/lib/directives/drag-nodes/nodes-drag.directive.ts +43 -0
- package/src/lib/directives/drag-nodes/nodes-drop-target.directive.ts +116 -0
- package/src/lib/directives/focus-state.directive.ts +34 -0
- package/src/lib/directives/icon.directive.ts +142 -0
- package/src/lib/directives/nodes-drop-target-legacy.directive.ts +155 -0
- package/src/lib/dropdown/dropdown.component.html +32 -0
- package/src/lib/dropdown/dropdown.component.scss +67 -0
- package/src/lib/dropdown/dropdown.component.ts +71 -0
- package/src/lib/edu-sharing-ui-configuration.ts +47 -0
- package/src/lib/edu-sharing-ui.module.ts +49 -0
- package/src/lib/list-items/available-widgets.ts +30 -0
- package/src/lib/list-items/format-duration.pipe.ts +17 -0
- package/src/lib/list-items/list-base/list-base.component.html +52 -0
- package/src/lib/list-items/list-base/list-base.component.ts +44 -0
- package/src/lib/list-items/list-collection-info/list-collection-info.component.html +48 -0
- package/src/lib/list-items/list-collection-info/list-collection-info.component.scss +8 -0
- package/src/lib/list-items/list-collection-info/list-collection-info.component.ts +24 -0
- package/src/lib/list-items/list-counts/list-counts.component.html +1 -0
- package/src/lib/list-items/list-counts/list-counts.component.scss +3 -0
- package/src/lib/list-items/list-counts/list-counts.component.ts +59 -0
- package/src/lib/list-items/list-items.module.ts +33 -0
- package/src/lib/list-items/list-node-license/list-node-license.component.html +8 -0
- package/src/lib/list-items/list-node-license/list-node-license.component.ts +47 -0
- package/src/lib/list-items/list-node-replication-source/list-node-replication-source.component.html +11 -0
- package/src/lib/list-items/list-node-replication-source/list-node-replication-source.component.ts +60 -0
- package/src/lib/list-items/list-node-workflow/list-node-workflow.component.html +3 -0
- package/src/lib/list-items/list-node-workflow/list-node-workflow.component.ts +21 -0
- package/src/lib/list-items/list-text/list-text.component.html +176 -0
- package/src/lib/list-items/list-text/list-text.component.scss +3 -0
- package/src/lib/list-items/list-text/list-text.component.ts +107 -0
- package/src/lib/list-items/list-widget.ts +52 -0
- package/src/lib/list-items/node-row/node-row.component.html +31 -0
- package/src/lib/list-items/node-row/node-row.component.scss +50 -0
- package/src/lib/list-items/node-row/node-row.component.ts +16 -0
- package/src/lib/list-items/node-source.pipe.ts +48 -0
- package/src/lib/node-entries/combined-data-source.ts +51 -0
- package/src/lib/node-entries/custom-templates-data-source.ts +6 -0
- package/src/lib/node-entries/drag-preview/drag-preview.component.html +6 -0
- package/src/lib/node-entries/drag-preview/drag-preview.component.scss +35 -0
- package/src/lib/node-entries/drag-preview/drag-preview.component.ts +15 -0
- package/src/lib/node-entries/entries-model.ts +120 -0
- package/src/lib/node-entries/items-cap.ts +54 -0
- package/src/lib/node-entries/list-item-label.pipe.ts +28 -0
- package/src/lib/node-entries/mixins.scss +23 -0
- package/src/lib/node-entries/node-cache.spec.ts +199 -0
- package/src/lib/node-entries/node-cache.ts +81 -0
- package/src/lib/node-entries/node-data-source-remote.ts +33 -0
- package/src/lib/node-entries/node-data-source.ts +148 -0
- package/src/lib/node-entries/node-entries-card/node-entries-card.component.html +167 -0
- package/src/lib/node-entries/node-entries-card/node-entries-card.component.scss +28 -0
- package/src/lib/node-entries/node-entries-card/node-entries-card.component.ts +132 -0
- package/src/lib/node-entries/node-entries-card/node-entries-card.main.scss +261 -0
- package/src/lib/node-entries/node-entries-card-grid/node-entries-card-grid.component.html +205 -0
- package/src/lib/node-entries/node-entries-card-grid/node-entries-card-grid.component.scss +181 -0
- package/src/lib/node-entries/node-entries-card-grid/node-entries-card-grid.component.ts +361 -0
- package/src/lib/node-entries/node-entries-card-small/node-entries-card-small.component.html +100 -0
- package/src/lib/node-entries/node-entries-card-small/node-entries-card-small.component.scss +46 -0
- package/src/lib/node-entries/node-entries-card-small/node-entries-card-small.component.ts +40 -0
- package/src/lib/node-entries/node-entries-global-options/node-entries-global-options.component.html +23 -0
- package/src/lib/node-entries/node-entries-global-options/node-entries-global-options.component.scss +58 -0
- package/src/lib/node-entries/node-entries-global-options/node-entries-global-options.component.ts +16 -0
- package/src/lib/node-entries/node-entries-global.service.ts +79 -0
- package/src/lib/node-entries/node-entries-table/column-chooser/column-chooser.component.html +25 -0
- package/src/lib/node-entries/node-entries-table/column-chooser/column-chooser.component.scss +32 -0
- package/src/lib/node-entries/node-entries-table/column-chooser/column-chooser.component.ts +31 -0
- package/src/lib/node-entries/node-entries-table/node-entries-table.component.html +270 -0
- package/src/lib/node-entries/node-entries-table/node-entries-table.component.scss +169 -0
- package/src/lib/node-entries/node-entries-table/node-entries-table.component.ts +333 -0
- package/src/lib/node-entries/node-entries-templates.service.ts +31 -0
- package/src/lib/node-entries/node-entries-wrapper.component.ts +363 -0
- package/src/lib/node-entries/node-entries.component.html +33 -0
- package/src/lib/node-entries/node-entries.component.scss +13 -0
- package/src/lib/node-entries/node-entries.component.ts +151 -0
- package/src/lib/node-entries/node-entries.module.ts +93 -0
- package/src/lib/node-entries/node-rating/node-rating.component.html +53 -0
- package/src/lib/node-entries/node-rating/node-rating.component.scss +31 -0
- package/src/lib/node-entries/node-rating/node-rating.component.ts +105 -0
- package/src/lib/node-entries/node-stats-badges/node-stats-badges.component.html +39 -0
- package/src/lib/node-entries/node-stats-badges/node-stats-badges.component.scss +44 -0
- package/src/lib/node-entries/node-stats-badges/node-stats-badges.component.ts +43 -0
- package/src/lib/node-entries/node-type-badge/node-type-badge.component.html +31 -0
- package/src/lib/node-entries/node-type-badge/node-type-badge.component.scss +5 -0
- package/src/lib/node-entries/node-type-badge/node-type-badge.component.ts +36 -0
- package/src/lib/node-entries/option-button/option-button.component.ts +42 -0
- package/src/lib/node-entries/preview-image/preview-image.component.html +19 -0
- package/src/lib/node-entries/preview-image/preview-image.component.scss +31 -0
- package/src/lib/node-entries/preview-image/preview-image.component.ts +47 -0
- package/src/lib/node-entries/sort-select-panel/sort-select-panel.component.html +27 -0
- package/src/lib/node-entries/sort-select-panel/sort-select-panel.component.scss +9 -0
- package/src/lib/node-entries/sort-select-panel/sort-select-panel.component.ts +26 -0
- package/src/lib/node-url/node-url.component.html +66 -0
- package/src/lib/node-url/node-url.component.scss +32 -0
- package/src/lib/node-url/node-url.component.ts +136 -0
- package/src/lib/pipes/file-size.pipe.ts +24 -0
- package/src/lib/pipes/format-date.pipe.ts +39 -0
- package/src/lib/pipes/node-icon.pipe.ts +11 -0
- package/src/lib/pipes/node-image-size.pipe.ts +18 -0
- package/src/lib/pipes/node-image.pipe.ts +71 -0
- package/src/lib/pipes/node-person-name.pipe.ts +41 -0
- package/src/lib/pipes/node-title.pipe.ts +12 -0
- package/src/lib/pipes/option-tooltip.pipe.ts +32 -0
- package/src/lib/pipes/replace-chars.pipe.ts +21 -0
- package/src/lib/pipes/vcard-name.pipe.ts +11 -0
- package/src/lib/services/abstract/app.service.ts +4 -0
- package/src/lib/services/abstract/keyboard-shortcuts.service.ts +10 -0
- package/src/lib/services/abstract/options-helper.service.ts +29 -0
- package/src/lib/services/abstract/toast.service.ts +5 -0
- package/src/lib/services/accessibility.service.ts +101 -0
- package/src/lib/services/local-events.service.ts +29 -0
- package/src/lib/services/node-entries.service.ts +172 -0
- package/src/lib/services/node-helper.service.ts +239 -0
- package/src/lib/services/nodes-drag-drop.service.ts +165 -0
- package/src/lib/services/options-helper-data.service.ts +186 -0
- package/src/lib/services/repo-url.service.ts +46 -0
- package/src/lib/services/temporary-storage.service.ts +58 -0
- package/src/lib/services/ui.service.ts +182 -0
- package/src/lib/sort-dropdown/sort-dropdown.component.html +22 -0
- package/src/lib/sort-dropdown/sort-dropdown.component.scss +47 -0
- package/src/lib/sort-dropdown/sort-dropdown.component.ts +42 -0
- package/src/lib/spinner/spinner.component.html +14 -0
- package/src/lib/spinner/spinner.component.scss +141 -0
- package/src/lib/spinner/spinner.component.ts +12 -0
- package/src/lib/translations/README.md +44 -0
- package/src/lib/translations/fallback-translation-handler.ts +7 -0
- package/src/lib/translations/languages.ts +6 -0
- package/src/lib/translations/translation-loader.spec.ts +352 -0
- package/src/lib/translations/translation-loader.ts +189 -0
- package/src/lib/translations/translation-source.ts +9 -0
- package/src/lib/translations/translations.module.ts +49 -0
- package/src/lib/translations/translations.service.spec.ts +152 -0
- package/src/lib/translations/translations.service.ts +188 -0
- package/src/lib/types/accessibillity.ts +15 -0
- package/src/lib/types/api-models.ts +4 -0
- package/src/lib/types/drag-drop.ts +22 -0
- package/src/lib/types/keyboard-shortcuts.ts +29 -0
- package/src/lib/types/list-item.ts +67 -0
- package/src/lib/types/option-item.ts +247 -0
- package/src/lib/types/workflow.ts +35 -0
- package/src/lib/util/DateHelper.spec.ts +112 -0
- package/src/lib/util/DateHelper.ts +197 -0
- package/src/lib/util/VCard.ts +277 -0
- package/src/lib/util/color-helper.ts +125 -0
- package/src/lib/util/duration-helper.spec.ts +35 -0
- package/src/lib/util/duration-helper.ts +98 -0
- package/src/lib/util/functions.ts +15 -0
- package/src/lib/util/helper.ts +60 -0
- package/src/lib/util/isNumeric.ts +13 -0
- package/src/lib/util/rest-helper.ts +28 -0
- package/src/lib/util/ui-animation.ts +154 -0
- package/src/lib/util/ui-constants.ts +20 -0
- package/src/module.ts +76 -0
- package/src/test.ts +28 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +10 -0
- 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,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,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
|
+
}
|