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,53 @@
|
|
|
1
|
+
<ng-container *ngIf="hasPermission && node.type === 'ccm:io' && isFromHomeRepo(node)">
|
|
2
|
+
<div *ngIf="mode === 'likes'" class="likes">
|
|
3
|
+
<button
|
|
4
|
+
(click)="toogleLike()"
|
|
5
|
+
[disabled]="(this.authenticationService.observeLoginInfo() | async)?.isGuest"
|
|
6
|
+
mat-icon-button
|
|
7
|
+
color="primary"
|
|
8
|
+
matTooltip="{{ 'RATING.LIKES' | translate }}"
|
|
9
|
+
>
|
|
10
|
+
<i [esIcon]="'edu-thumb_up' + (!node.rating?.user ? '_off' : '')"></i>
|
|
11
|
+
</button>
|
|
12
|
+
<span matTooltip="{{ 'RATING.COUNT_LIKES' | translate }}">{{
|
|
13
|
+
node.rating?.overall.count
|
|
14
|
+
}}</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div *ngIf="mode === 'stars'" class="stars" mat-icon-button color="primary">
|
|
17
|
+
<button
|
|
18
|
+
*ngFor="let rating of [1, 2, 3, 4, 5]"
|
|
19
|
+
mat-icon-button
|
|
20
|
+
color="primary"
|
|
21
|
+
class="star"
|
|
22
|
+
(click)="setRating(rating)"
|
|
23
|
+
[disabled]="(this.authenticationService.observeLoginInfo() | async)?.isGuest"
|
|
24
|
+
(focus)="hoverStar = rating"
|
|
25
|
+
(mouseenter)="hoverStar = rating"
|
|
26
|
+
(mouseleave)="hoverStar = 0"
|
|
27
|
+
(blur)="hoverStar = 0"
|
|
28
|
+
[class.star-hover]="hoverStar >= rating"
|
|
29
|
+
>
|
|
30
|
+
<i
|
|
31
|
+
[esIcon]="
|
|
32
|
+
'star' +
|
|
33
|
+
((hoverStar ? hoverStar >= rating : getPrimaryRating() >= rating) ? '' : '_border')
|
|
34
|
+
"
|
|
35
|
+
matTooltip="{{ 'RATING.STARS_RATE' | translate : { rating: rating } }}"
|
|
36
|
+
></i>
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
*ngIf="!!node.rating?.user"
|
|
40
|
+
mat-icon-button
|
|
41
|
+
color="primary"
|
|
42
|
+
class="delete"
|
|
43
|
+
matTooltip="{{ 'RATING.DELETE_RATING' | translate }}"
|
|
44
|
+
(click)="deleteRating()"
|
|
45
|
+
[disabled]="(this.authenticationService.observeLoginInfo() | async)?.isGuest"
|
|
46
|
+
>
|
|
47
|
+
<i esIcon="clear"></i>
|
|
48
|
+
</button>
|
|
49
|
+
<span matTooltip="{{ 'RATING.COUNT_RATINGS' | translate }}"
|
|
50
|
+
>({{ node.rating?.overall.count }})</span
|
|
51
|
+
>
|
|
52
|
+
</div>
|
|
53
|
+
</ng-container>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.stars {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
span {
|
|
5
|
+
padding-left: 8px;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
.likes {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
}
|
|
12
|
+
.mat-icon-button.mat-primary {
|
|
13
|
+
color: var(--textLight);
|
|
14
|
+
&.likes {
|
|
15
|
+
}
|
|
16
|
+
&.star,
|
|
17
|
+
&.delete {
|
|
18
|
+
min-width: unset;
|
|
19
|
+
width: 18px;
|
|
20
|
+
height: 18px;
|
|
21
|
+
::ng-deep {
|
|
22
|
+
i {
|
|
23
|
+
font-size: 17px;
|
|
24
|
+
line-height: 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
&.star-hover {
|
|
28
|
+
color: var(--colorStarActive);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
Node,
|
|
4
|
+
ConfigService,
|
|
5
|
+
AuthenticationService,
|
|
6
|
+
RestConstants,
|
|
7
|
+
NetworkService,
|
|
8
|
+
} from 'ngx-edu-sharing-api';
|
|
9
|
+
import { Toast } from '../../services/abstract/toast.service';
|
|
10
|
+
import { take, takeUntil } from 'rxjs/operators';
|
|
11
|
+
import { RestHelper } from '../../util/rest-helper';
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'es-node-rating',
|
|
14
|
+
templateUrl: 'node-rating.component.html',
|
|
15
|
+
styleUrls: ['node-rating.component.scss'],
|
|
16
|
+
})
|
|
17
|
+
export class NodeRatingComponent<T extends Node> implements OnInit {
|
|
18
|
+
// @TODO
|
|
19
|
+
ratingService: any = {};
|
|
20
|
+
@Input() node: T;
|
|
21
|
+
mode: RatingMode;
|
|
22
|
+
hasPermission: boolean;
|
|
23
|
+
hoverStar: number;
|
|
24
|
+
constructor(
|
|
25
|
+
public toast: Toast,
|
|
26
|
+
public configService: ConfigService,
|
|
27
|
+
private networkApi: NetworkService,
|
|
28
|
+
public authenticationService: AuthenticationService,
|
|
29
|
+
public changeDetectorRef: ChangeDetectorRef, // @TODO // public ratingService: RestRatingService,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
async ngOnInit() {
|
|
33
|
+
await this.configService.observeConfig().pipe(take(1)).toPromise();
|
|
34
|
+
this.mode = this.configService.instant('rating.mode', 'none');
|
|
35
|
+
this.hasPermission = await this.authenticationService.hasToolpermission(
|
|
36
|
+
RestConstants.TOOLPERMISSION_RATE_READ,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async toogleLike() {
|
|
41
|
+
const name = RestHelper.getTitle(this.node);
|
|
42
|
+
if (this.node.rating?.user) {
|
|
43
|
+
try {
|
|
44
|
+
await this.ratingService.deleteNodeRating(this.node.ref.id).toPromise();
|
|
45
|
+
this.toast.toast('RATING.TOAST.LIKE_REMOVED', { name });
|
|
46
|
+
this.node.rating.user = 0;
|
|
47
|
+
this.node.rating.overall.count--;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
this.toast.error(e);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
try {
|
|
53
|
+
await this.ratingService.updateNodeRating(this.node.ref.id, 5).toPromise();
|
|
54
|
+
this.toast.toast('RATING.TOAST.LIKED', { name });
|
|
55
|
+
this.node.rating.user = 5;
|
|
56
|
+
this.node.rating.overall.count++;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
this.toast.error(e);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.changeDetectorRef.detectChanges();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getPrimaryRating() {
|
|
65
|
+
if (!this.node.rating) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
if (this.node.rating.user) {
|
|
69
|
+
return this.node.rating.user;
|
|
70
|
+
}
|
|
71
|
+
return this.node.rating.overall.sum / this.node.rating.overall.count;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async setRating(rating: number) {
|
|
75
|
+
const name = RestHelper.getTitle(this.node);
|
|
76
|
+
try {
|
|
77
|
+
await this.ratingService.updateNodeRating(this.node.ref.id, rating).toPromise();
|
|
78
|
+
this.toast.toast('RATING.TOAST.RATED', { name, rating });
|
|
79
|
+
this.node.rating.overall.count += this.node.rating.user ? 0 : 1;
|
|
80
|
+
this.node.rating.user = rating;
|
|
81
|
+
this.changeDetectorRef.detectChanges();
|
|
82
|
+
} catch (e) {
|
|
83
|
+
this.toast.error(e);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async deleteRating() {
|
|
88
|
+
const name = RestHelper.getTitle(this.node);
|
|
89
|
+
try {
|
|
90
|
+
await this.ratingService.deleteNodeRating(this.node.ref.id).toPromise();
|
|
91
|
+
this.toast.toast('RATING.TOAST.RATING_REMOVED', { name });
|
|
92
|
+
this.node.rating.overall.count--;
|
|
93
|
+
this.node.rating.overall.sum -= this.node.rating.user;
|
|
94
|
+
this.node.rating.user = 0;
|
|
95
|
+
this.changeDetectorRef.detectChanges();
|
|
96
|
+
} catch (e) {
|
|
97
|
+
this.toast.error(e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
isFromHomeRepo(node: Node) {
|
|
102
|
+
return this.networkApi.isFromHomeRepository(node);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export type RatingMode = 'none' | 'likes' | 'stars';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
<div
|
|
3
|
+
*ngIf="node.aspects?.includes('ccm:published')"
|
|
4
|
+
class="card-top-bar-published-copy"
|
|
5
|
+
>
|
|
6
|
+
<div class="node-published-copy">
|
|
7
|
+
<i
|
|
8
|
+
esIcon="content_copy"
|
|
9
|
+
[matTooltip]="'PUBLISHED_COPY_TOOLTIP' | translate"
|
|
10
|
+
></i>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
-->
|
|
14
|
+
<div class="card-top-bar-comments">
|
|
15
|
+
<div
|
|
16
|
+
class="node-comments"
|
|
17
|
+
*ngIf="node.type === 'ccm:io'"
|
|
18
|
+
(click)="
|
|
19
|
+
entriesService?.onClicked({
|
|
20
|
+
event: $event,
|
|
21
|
+
element: node,
|
|
22
|
+
source: ClickSource.Comments
|
|
23
|
+
})
|
|
24
|
+
"
|
|
25
|
+
[matTooltip]="('COMMENTS_MULTIPLE' | translate) + ': ' + node.commentCount"
|
|
26
|
+
>
|
|
27
|
+
<i esIcon="comment"></i>
|
|
28
|
+
<span>{{ node.commentCount }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="card-top-bar-childobjects" *ngIf="childObjectCount > 0">
|
|
32
|
+
<div
|
|
33
|
+
class="childobject-count"
|
|
34
|
+
matTooltip="{{ 'CHILDOBJECT_COUNT' | translate : { count: childObjectCount + 1 } }}"
|
|
35
|
+
>
|
|
36
|
+
<span>{{ childObjectCount + 1 }}</span
|
|
37
|
+
><i esIcon="filter_none"></i>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 15px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.card-top-bar-comments,
|
|
7
|
+
.card-top-bar-published-copy,
|
|
8
|
+
.card-top-bar-childobjects {
|
|
9
|
+
> div {
|
|
10
|
+
:host[backgroundStyle='lighten'] & {
|
|
11
|
+
background-color: rgba(255, 255, 255, 0.75);
|
|
12
|
+
}
|
|
13
|
+
:host[backgroundStyle='darken'] & {
|
|
14
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
15
|
+
}
|
|
16
|
+
border-radius: 15px;
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
min-width: 35px;
|
|
20
|
+
min-height: 22px;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
cursor: default;
|
|
23
|
+
user-select: none;
|
|
24
|
+
padding: 2px 8px;
|
|
25
|
+
&.childobject-count {
|
|
26
|
+
> i {
|
|
27
|
+
font-size: 13px;
|
|
28
|
+
margin-left: 4px;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
&.node-published-copy {
|
|
32
|
+
min-width: auto;
|
|
33
|
+
> i {
|
|
34
|
+
font-size: 13px;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
&.node-comments {
|
|
38
|
+
> i {
|
|
39
|
+
font-size: 13px;
|
|
40
|
+
margin-right: 4px;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Component, HostBinding, Input, Optional } from '@angular/core';
|
|
2
|
+
import { Node } from 'ngx-edu-sharing-api';
|
|
3
|
+
import { ClickSource } from '../entries-model';
|
|
4
|
+
import { NodeEntriesService } from '../../services/node-entries.service';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'es-node-stats-badges',
|
|
8
|
+
templateUrl: './node-stats-badges.component.html',
|
|
9
|
+
styleUrls: ['./node-stats-badges.component.scss'],
|
|
10
|
+
})
|
|
11
|
+
export class NodeStatsBadgesComponent {
|
|
12
|
+
readonly ClickSource = ClickSource;
|
|
13
|
+
|
|
14
|
+
childObjectCount = 0;
|
|
15
|
+
|
|
16
|
+
private _node: Node;
|
|
17
|
+
@Input()
|
|
18
|
+
get node(): Node {
|
|
19
|
+
return this._node;
|
|
20
|
+
}
|
|
21
|
+
set node(node: Node) {
|
|
22
|
+
this._node = node;
|
|
23
|
+
this.childObjectCount = this.getChildObjectCount(node);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@HostBinding('attr.backgroundStyle')
|
|
27
|
+
@Input()
|
|
28
|
+
backgroundStyle: 'darken' | 'lighten' = 'lighten';
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
@Optional()
|
|
32
|
+
public entriesService: NodeEntriesService<Node>,
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
private getChildObjectCount(node: Node): number {
|
|
36
|
+
const value = node.properties?.['virtual:childobjectcount']?.[0];
|
|
37
|
+
if (value) {
|
|
38
|
+
return parseInt(value);
|
|
39
|
+
} else {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<ng-container *ngIf="isCollection">
|
|
2
|
+
<i
|
|
3
|
+
[matTooltip]="
|
|
4
|
+
'COLLECTION.SCOPE.' + nodeHelper.getCollectionScopeInfo($any(node)).scopeName | translate
|
|
5
|
+
"
|
|
6
|
+
[altText]="
|
|
7
|
+
'COLLECTION.SCOPE.' + nodeHelper.getCollectionScopeInfo($any(node)).scopeName | translate
|
|
8
|
+
"
|
|
9
|
+
[esIcon]="nodeHelper.getCollectionScopeInfo($any(node)).icon"
|
|
10
|
+
></i>
|
|
11
|
+
</ng-container>
|
|
12
|
+
<ng-container *ngIf="!isCollection">
|
|
13
|
+
<ng-container *ngIf="getCustomTemplate() as ref">
|
|
14
|
+
<ng-container *ngTemplateOutlet="ref; context: { node: this.node }"></ng-container>
|
|
15
|
+
</ng-container>
|
|
16
|
+
<ng-container *ngIf="!getCustomTemplate()">
|
|
17
|
+
<img
|
|
18
|
+
[src]="node | esNodeIcon | async"
|
|
19
|
+
[alt]="
|
|
20
|
+
node.mediatype
|
|
21
|
+
? ('NODE.mediatype' | translate) + ': ' + ('MEDIATYPE.' + node.mediatype | translate)
|
|
22
|
+
: ''
|
|
23
|
+
"
|
|
24
|
+
[matTooltip]="
|
|
25
|
+
node.mediatype
|
|
26
|
+
? ('NODE.mediatype' | translate) + ': ' + ('MEDIATYPE.' + node.mediatype | translate)
|
|
27
|
+
: ''
|
|
28
|
+
"
|
|
29
|
+
/>
|
|
30
|
+
</ng-container>
|
|
31
|
+
</ng-container>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Component, Input, OnChanges } from '@angular/core';
|
|
2
|
+
import { Node } from 'ngx-edu-sharing-api';
|
|
3
|
+
import { CustomFieldSpecialType, NodeEntriesGlobalService } from '../node-entries-global.service';
|
|
4
|
+
import { NodeHelperService } from '../../services/node-helper.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A small circular badge that depicts the node's type.
|
|
8
|
+
*
|
|
9
|
+
* To be used in the top bar of a card or similar places.
|
|
10
|
+
*/
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'es-node-type-badge',
|
|
13
|
+
templateUrl: './node-type-badge.component.html',
|
|
14
|
+
styleUrls: ['./node-type-badge.component.scss'],
|
|
15
|
+
})
|
|
16
|
+
export class NodeTypeBadgeComponent implements OnChanges {
|
|
17
|
+
@Input() node: Node;
|
|
18
|
+
|
|
19
|
+
isCollection: boolean;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
public nodeHelper: NodeHelperService,
|
|
23
|
+
private nodeEntriesGlobalService: NodeEntriesGlobalService,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
ngOnChanges(): void {
|
|
27
|
+
this.isCollection = this.nodeHelper.isNodeCollection(this.node);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getCustomTemplate() {
|
|
31
|
+
return this.nodeEntriesGlobalService.getCustomFieldTemplate(
|
|
32
|
+
{ type: 'NODE', name: CustomFieldSpecialType.type },
|
|
33
|
+
this.node,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { Node } from 'ngx-edu-sharing-api';
|
|
3
|
+
import { OptionItem } from '../../types/option-item';
|
|
4
|
+
// TODO: Decide if providing focus highlights and ripples with this component is a good idea. When
|
|
5
|
+
// using `app-node-url` for cards, we might need highlights and ripples for the whole card while
|
|
6
|
+
// `app-node-url` should only wrap the title since links with lots of content confuse screen
|
|
7
|
+
// readers.
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'es-option-button',
|
|
11
|
+
template: `
|
|
12
|
+
<button
|
|
13
|
+
mat-icon-button
|
|
14
|
+
color="primary"
|
|
15
|
+
matTooltip="{{ option.name | translate }}"
|
|
16
|
+
[class.display-none]="!optionIsShown(option, node)"
|
|
17
|
+
[disabled]="!optionIsValid(option, node)"
|
|
18
|
+
(click)="optionIsShown(option, node) ? option.callback(node) : null"
|
|
19
|
+
attr.data-test="option-button-{{ option.name }}"
|
|
20
|
+
>
|
|
21
|
+
<i esIcon="{{ option.icon }}" [aria]="false"></i>
|
|
22
|
+
</button>
|
|
23
|
+
`,
|
|
24
|
+
})
|
|
25
|
+
export class OptionButtonComponent {
|
|
26
|
+
@Input() option: OptionItem;
|
|
27
|
+
@Input() node: Node;
|
|
28
|
+
|
|
29
|
+
optionIsValid(optionItem: OptionItem, node: Node): boolean {
|
|
30
|
+
if (optionItem.enabledCallback) {
|
|
31
|
+
return optionItem.enabledCallback(node);
|
|
32
|
+
}
|
|
33
|
+
return optionItem.isEnabled;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
optionIsShown(optionItem: OptionItem, node: Node): boolean {
|
|
37
|
+
if (optionItem.showCallback) {
|
|
38
|
+
return optionItem.showCallback(node);
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<div class="card-image-backdrop">
|
|
2
|
+
<!-- The blur filter does not work with canvas as it does with img on Chrome, so we go for a black
|
|
3
|
+
background for now. -->
|
|
4
|
+
<!-- <canvas *ngIf="showCanvas" #backdropCanvas role="presentation" aria-hidden="true"></canvas> -->
|
|
5
|
+
<img
|
|
6
|
+
[src]="node | esNodeImage : { maxWidth: 300, maxHeight: 300, crop: true } | async"
|
|
7
|
+
alt=""
|
|
8
|
+
*ngIf="!replacedWithStatic"
|
|
9
|
+
/>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<canvas *ngIf="showCanvas" #canvas role="presentation" aria-hidden="true"></canvas>
|
|
13
|
+
<img
|
|
14
|
+
(load)="onImageLoad($event)"
|
|
15
|
+
#image
|
|
16
|
+
[src]="node | esNodeImage : { maxWidth: 300, maxHeight: 300, crop: true } | async"
|
|
17
|
+
alt=""
|
|
18
|
+
[class.display-none]="replacedWithStatic && !playAnimation"
|
|
19
|
+
/>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@import '../../../../assets/scss/mixins';
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
position: relative;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.card-image-backdrop {
|
|
9
|
+
position: absolute;
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
background-color: black;
|
|
13
|
+
img,
|
|
14
|
+
canvas {
|
|
15
|
+
object-fit: cover;
|
|
16
|
+
@include blurImage(15px);
|
|
17
|
+
opacity: 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:host > img,
|
|
22
|
+
:host > canvas {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
object-fit: contain;
|
|
26
|
+
position: absolute;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.display-none {
|
|
30
|
+
display: none;
|
|
31
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
|
|
2
|
+
import { Node } from 'ngx-edu-sharing-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Displays the preview image of `node`.
|
|
6
|
+
*
|
|
7
|
+
* When `node` is a video, the animated preview image is replaced with a static canvas unless
|
|
8
|
+
* `playAnimation` is set to `true`.
|
|
9
|
+
*/
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'es-preview-image',
|
|
12
|
+
templateUrl: './preview-image.component.html',
|
|
13
|
+
styleUrls: ['./preview-image.component.scss'],
|
|
14
|
+
})
|
|
15
|
+
export class PreviewImageComponent<T extends Node> {
|
|
16
|
+
@Input() node: T;
|
|
17
|
+
@Input() playAnimation = false;
|
|
18
|
+
|
|
19
|
+
@ViewChild('image') imageRef: ElementRef<HTMLImageElement>;
|
|
20
|
+
@ViewChild('canvas') canvasRef: ElementRef<HTMLCanvasElement>;
|
|
21
|
+
// @ViewChild('backdropCanvas') backdropCanvasRef: ElementRef<HTMLCanvasElement>;
|
|
22
|
+
|
|
23
|
+
showCanvas: boolean = false;
|
|
24
|
+
replacedWithStatic: boolean = false;
|
|
25
|
+
|
|
26
|
+
constructor() {}
|
|
27
|
+
|
|
28
|
+
onImageLoad(event: Event): void {
|
|
29
|
+
if (this.node.mimetype?.startsWith('video')) {
|
|
30
|
+
const image = event.target as HTMLImageElement;
|
|
31
|
+
this.showCanvas = true;
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
this.initCanvas(image, this.canvasRef.nativeElement);
|
|
34
|
+
// this.initCanvas(image, this.backdropCanvasRef.nativeElement);
|
|
35
|
+
this.replacedWithStatic = true;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private initCanvas(image: HTMLImageElement, canvas: HTMLCanvasElement): void {
|
|
41
|
+
var width = image.naturalWidth;
|
|
42
|
+
var height = image.naturalHeight;
|
|
43
|
+
canvas.width = width;
|
|
44
|
+
canvas.height = height;
|
|
45
|
+
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<mat-slide-toggle
|
|
2
|
+
*ngIf="active === 'ccm:collection_ordered_position'"
|
|
3
|
+
[(ngModel)]="customSortingInProgress"
|
|
4
|
+
(ngModelChange)="customSortingInProgressChange.emit($event)"
|
|
5
|
+
>
|
|
6
|
+
{{ 'COLLECTIONS.SORT_SLIDER' | translate }}
|
|
7
|
+
</mat-slide-toggle>
|
|
8
|
+
<button
|
|
9
|
+
*ngIf="sortDropdown.menu"
|
|
10
|
+
[matMenuTriggerFor]="sortDropdown.menu"
|
|
11
|
+
mat-button
|
|
12
|
+
color="primary"
|
|
13
|
+
>
|
|
14
|
+
<span *ngIf="active">{{ 'NODE.' + active | translate }}</span>
|
|
15
|
+
<span *ngIf="!active">{{ 'SORT_BY' | translate }}</span>
|
|
16
|
+
<i
|
|
17
|
+
*ngIf="active && active !== 'ccm:collection_ordered_position'"
|
|
18
|
+
[esIcon]="'arrow_' + (direction === 'asc' ? 'upward' : 'downward')"
|
|
19
|
+
></i>
|
|
20
|
+
</button>
|
|
21
|
+
<es-sort-dropdown
|
|
22
|
+
#sortDropdown
|
|
23
|
+
[columns]="columns"
|
|
24
|
+
[sortBy]="active"
|
|
25
|
+
[sortAscending]="direction === 'asc'"
|
|
26
|
+
(onSort)="onSort($event)"
|
|
27
|
+
></es-sort-dropdown>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
import { Sort, SortDirection } from '@angular/material/sort';
|
|
3
|
+
import { ListItemSort, SortEvent, SortPanel } from '../../types/list-item';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'es-sort-select-panel',
|
|
7
|
+
templateUrl: './sort-select-panel.component.html',
|
|
8
|
+
styleUrls: ['./sort-select-panel.component.scss'],
|
|
9
|
+
})
|
|
10
|
+
export class SortSelectPanelComponent implements SortPanel {
|
|
11
|
+
@Input() active: string;
|
|
12
|
+
@Input() direction: SortDirection;
|
|
13
|
+
@Input() columns: ListItemSort[];
|
|
14
|
+
@Output() sortChange = new EventEmitter<Sort>();
|
|
15
|
+
@Input() customSortingInProgress: boolean;
|
|
16
|
+
@Output() customSortingInProgressChange = new EventEmitter<boolean>();
|
|
17
|
+
|
|
18
|
+
constructor() {}
|
|
19
|
+
|
|
20
|
+
onSort(event: SortEvent) {
|
|
21
|
+
this.sortChange.emit({
|
|
22
|
+
active: event.name,
|
|
23
|
+
direction: event.ascending ? 'asc' : 'desc',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<ng-template #content><ng-content></ng-content></ng-template>
|
|
2
|
+
<ng-container *ngIf="mode === 'link'">
|
|
3
|
+
<a
|
|
4
|
+
*ngIf="!disabled"
|
|
5
|
+
#link
|
|
6
|
+
matRipple
|
|
7
|
+
matRippleColor="primary"
|
|
8
|
+
[matRippleDisabled]="isNested"
|
|
9
|
+
[routerLink]="get('routerLink')"
|
|
10
|
+
[state]="getState()"
|
|
11
|
+
[target]="target"
|
|
12
|
+
[queryParams]="get('queryParams')"
|
|
13
|
+
queryParamsHandling="merge"
|
|
14
|
+
cdkMonitorElementFocus
|
|
15
|
+
[attr.aria-label]="ariaLabel ? node.name : null"
|
|
16
|
+
[attr.aria-describedby]="ariaDescribedby"
|
|
17
|
+
>
|
|
18
|
+
<ng-container *ngTemplateOutlet="content"></ng-container>
|
|
19
|
+
</a>
|
|
20
|
+
<!-- We use `div`s instead of `span`s here because the legacy `ListTable` component will insert
|
|
21
|
+
`div` elements into this component's content. However, this will lead to invalid HTML for "button"
|
|
22
|
+
mode. So really, users of this component should only insert flow content. -->
|
|
23
|
+
<div *ngIf="disabled && !alwaysRipple">
|
|
24
|
+
<ng-container *ngTemplateOutlet="content"></ng-container>
|
|
25
|
+
</div>
|
|
26
|
+
<div *ngIf="disabled && alwaysRipple" matRipple matRippleColor="primary">
|
|
27
|
+
<ng-container *ngTemplateOutlet="content"></ng-container>
|
|
28
|
+
</div>
|
|
29
|
+
</ng-container>
|
|
30
|
+
<ng-container *ngIf="mode === 'wrapper'">
|
|
31
|
+
<div
|
|
32
|
+
class="node-url-wrapper"
|
|
33
|
+
#wrapper
|
|
34
|
+
matRipple
|
|
35
|
+
matRippleColor="primary"
|
|
36
|
+
[matRippleDisabled]="disabled && !alwaysRipple"
|
|
37
|
+
(click)="clickWrapper($event)"
|
|
38
|
+
(auxclick)="clickWrapper($event)"
|
|
39
|
+
>
|
|
40
|
+
<ng-container *ngTemplateOutlet="content"></ng-container>
|
|
41
|
+
</div>
|
|
42
|
+
<!-- An invisible link that will by clicked programmatically by `clickWrapper()`. -->
|
|
43
|
+
<a
|
|
44
|
+
#link
|
|
45
|
+
[routerLink]="get('routerLink')"
|
|
46
|
+
[state]="getState()"
|
|
47
|
+
[queryParams]="get('queryParams')"
|
|
48
|
+
queryParamsHandling="merge"
|
|
49
|
+
tabindex="-1"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
class="cdk-visually-hidden"
|
|
52
|
+
></a>
|
|
53
|
+
</ng-container>
|
|
54
|
+
<ng-container *ngIf="mode === 'button'">
|
|
55
|
+
<button
|
|
56
|
+
[disabled]="disabled"
|
|
57
|
+
#link
|
|
58
|
+
matRipple
|
|
59
|
+
matRippleColor="primary"
|
|
60
|
+
[matRippleDisabled]="disabled && !alwaysRipple"
|
|
61
|
+
(click)="buttonClick.emit($event)"
|
|
62
|
+
cdkMonitorElementFocus
|
|
63
|
+
>
|
|
64
|
+
<ng-container *ngTemplateOutlet="content"></ng-container>
|
|
65
|
+
</button>
|
|
66
|
+
</ng-container>
|