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,33 @@
|
|
|
1
|
+
import { NodeDataSource } from './node-data-source';
|
|
2
|
+
import { GenericAuthority, Node } from 'ngx-edu-sharing-api';
|
|
3
|
+
import { MatTableDataSourcePaginator } from '@angular/material/table';
|
|
4
|
+
import { ActivatedRoute } from '@angular/router';
|
|
5
|
+
import { PaginationStrategy } from './node-entries-global.service';
|
|
6
|
+
import { Sort } from '@angular/material/sort';
|
|
7
|
+
import { SortPanel } from '../types/list-item';
|
|
8
|
+
|
|
9
|
+
export interface PaginationConfig {
|
|
10
|
+
defaultPageSize: number;
|
|
11
|
+
strategy: PaginationStrategy;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export abstract class NodeDataSourceRemote<
|
|
15
|
+
T extends Node | GenericAuthority = Node,
|
|
16
|
+
P extends MatTableDataSourcePaginator = MatTableDataSourcePaginator,
|
|
17
|
+
> extends NodeDataSource<T> {
|
|
18
|
+
abstract get paginator(): P | null;
|
|
19
|
+
abstract set paginator(value: P | null);
|
|
20
|
+
abstract get sortPanel(): SortPanel;
|
|
21
|
+
abstract set sortPanel(value: SortPanel);
|
|
22
|
+
abstract loadMore(): boolean;
|
|
23
|
+
|
|
24
|
+
abstract registerQueryParameters(route: ActivatedRoute): void;
|
|
25
|
+
|
|
26
|
+
abstract init({
|
|
27
|
+
paginationConfig,
|
|
28
|
+
defaultSort,
|
|
29
|
+
}: {
|
|
30
|
+
paginationConfig: PaginationConfig;
|
|
31
|
+
defaultSort: Sort;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { DataSource } from '@angular/cdk/collections';
|
|
2
|
+
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
|
3
|
+
import { ItemsCap } from './items-cap';
|
|
4
|
+
import { Node, GenericAuthority, Pagination } from 'ngx-edu-sharing-api';
|
|
5
|
+
import { Helper } from '../util/helper';
|
|
6
|
+
|
|
7
|
+
export type LoadingState =
|
|
8
|
+
// The data source is loading data for the first time.
|
|
9
|
+
| 'initial'
|
|
10
|
+
// Loading data after change of parameters, i.e., it will replace the current data when done.
|
|
11
|
+
| 'reset'
|
|
12
|
+
// Loading another page with unchanged parameters.
|
|
13
|
+
| 'page'
|
|
14
|
+
// Loading done.
|
|
15
|
+
| false;
|
|
16
|
+
|
|
17
|
+
export class NodeDataSource<T extends Node | GenericAuthority> extends DataSource<T> {
|
|
18
|
+
protected dataStream = new BehaviorSubject<T[]>([]);
|
|
19
|
+
private pagination$ = new BehaviorSubject<Pagination>(null);
|
|
20
|
+
// Include `LoadingState` to be type-compatible to `NodeDataSourceRemote` although not used
|
|
21
|
+
// here.
|
|
22
|
+
public isLoadingSubject = new BehaviorSubject<LoadingState | boolean>(false);
|
|
23
|
+
get isLoading() {
|
|
24
|
+
return this.isLoadingSubject.value;
|
|
25
|
+
}
|
|
26
|
+
set isLoading(isLoading: LoadingState | boolean) {
|
|
27
|
+
this.isLoadingSubject.next(isLoading);
|
|
28
|
+
}
|
|
29
|
+
initialPageLoaded = false;
|
|
30
|
+
protected _itemsCap: ItemsCap<T> | null;
|
|
31
|
+
get itemsCap(): ItemsCap<T> | null {
|
|
32
|
+
return this._itemsCap;
|
|
33
|
+
}
|
|
34
|
+
set itemsCap(value: ItemsCap<T> | null) {
|
|
35
|
+
this._itemsCap = value;
|
|
36
|
+
this.connectRenderData();
|
|
37
|
+
}
|
|
38
|
+
protected renderData = new BehaviorSubject<T[]>([]);
|
|
39
|
+
protected renderDataSubscription: Subscription | null;
|
|
40
|
+
|
|
41
|
+
constructor(initialData: T[] = []) {
|
|
42
|
+
super();
|
|
43
|
+
this.setData(initialData);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
connect(): Observable<T[]> {
|
|
47
|
+
if (!this.renderDataSubscription) {
|
|
48
|
+
this.connectRenderData();
|
|
49
|
+
}
|
|
50
|
+
return this.renderData;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private connectRenderData(): void {
|
|
54
|
+
this.renderDataSubscription?.unsubscribe();
|
|
55
|
+
if (this.itemsCap) {
|
|
56
|
+
this.renderDataSubscription = this.itemsCap
|
|
57
|
+
.connect(this.dataStream)
|
|
58
|
+
.subscribe((data) => this.renderData.next(data));
|
|
59
|
+
} else {
|
|
60
|
+
this.renderDataSubscription = this.dataStream.subscribe((data) =>
|
|
61
|
+
this.renderData.next(data),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
connectPagination(): Observable<Pagination> {
|
|
67
|
+
return this.pagination$;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
disconnect() {}
|
|
71
|
+
|
|
72
|
+
setData(data: T[], pagination: Pagination = null) {
|
|
73
|
+
this.dataStream.next(data);
|
|
74
|
+
this.setPagination(pagination);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
appendData(appendData: T[], location: 'before' | 'after' = 'after') {
|
|
78
|
+
let data = this.getData();
|
|
79
|
+
if (location === 'after') {
|
|
80
|
+
data = data.concat(appendData);
|
|
81
|
+
} else {
|
|
82
|
+
data = appendData.concat(data);
|
|
83
|
+
}
|
|
84
|
+
this.dataStream.next(data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Removes elements from the visible data.
|
|
89
|
+
*/
|
|
90
|
+
removeData(toRemove: T[]): void {
|
|
91
|
+
const newData = this.getData().filter(
|
|
92
|
+
(value) =>
|
|
93
|
+
!toRemove.some((d) => Helper.objectEquals((d as Node).ref, (value as Node).ref)),
|
|
94
|
+
);
|
|
95
|
+
const removedData = this.getData().filter((value) => !newData.includes(value));
|
|
96
|
+
this.dataStream.next(newData);
|
|
97
|
+
if (this.pagination$.value) {
|
|
98
|
+
const pagination = this.pagination$.value;
|
|
99
|
+
this.setPagination({
|
|
100
|
+
count: pagination.count - removedData.length,
|
|
101
|
+
from: pagination.from,
|
|
102
|
+
total: pagination.total - removedData.length,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setPagination(pagination: Pagination) {
|
|
108
|
+
this.pagination$.next(pagination);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
reset() {
|
|
112
|
+
this.setData([]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
hasMore() {
|
|
116
|
+
if (!this.pagination$.value) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return this.pagination$.value.total > this.dataStream.value?.length;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// FIXME: This is somewhat dangerous because we rely on `connect` being called from outside, but
|
|
123
|
+
// this method provides a way to access data without ever calling `connect`.
|
|
124
|
+
getData() {
|
|
125
|
+
return this.renderData.value;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
isEmpty(): boolean {
|
|
129
|
+
return this.dataStream.value?.length === 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getTotal() {
|
|
133
|
+
return this.pagination$.value?.total ?? this.dataStream.value?.length ?? 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
isFullyLoaded() {
|
|
137
|
+
return this.getTotal() <= this.dataStream.value?.length;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* force a refresh of all elements in the current data stream
|
|
142
|
+
* trigger this to enforce a rebuild of the nodes in all sub-components
|
|
143
|
+
* i.e. if data from some nodes has changed
|
|
144
|
+
*/
|
|
145
|
+
refresh() {
|
|
146
|
+
this.dataStream.next(Helper.deepCopy(this.dataStream.value));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="grid-card"
|
|
3
|
+
[class.grid-card-collection]="isCollection"
|
|
4
|
+
[class.grid-card-virtual]="$any(node).virtual"
|
|
5
|
+
[style.background-color]="isCollection ? node.collection.color : null"
|
|
6
|
+
[class.dynamic-single-click]="entriesService.singleClickHint === 'dynamic'"
|
|
7
|
+
[class.selected]="entriesService.selection.isSelected(node)"
|
|
8
|
+
(contextmenu)="openContextmenu($event)"
|
|
9
|
+
(keydown.ContextMenu)="openContextmenu($event)"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
*ngIf="templatesService.overlay"
|
|
13
|
+
class="card-overlay"
|
|
14
|
+
(click)="
|
|
15
|
+
entriesService.clickItem.emit({
|
|
16
|
+
element: node,
|
|
17
|
+
source: ClickSource.Overlay
|
|
18
|
+
})
|
|
19
|
+
"
|
|
20
|
+
>
|
|
21
|
+
<ng-container
|
|
22
|
+
*ngTemplateOutlet="templatesService.overlay; context: { element: node }"
|
|
23
|
+
></ng-container>
|
|
24
|
+
</div>
|
|
25
|
+
<button
|
|
26
|
+
*ngIf="dropdown"
|
|
27
|
+
#menuTrigger="matMenuTrigger"
|
|
28
|
+
mat-button
|
|
29
|
+
class="dropdown-dummy cdk-visually-hidden"
|
|
30
|
+
[style.left.px]="dropdownLeft"
|
|
31
|
+
[style.top.px]="dropdownTop"
|
|
32
|
+
[matMenuTriggerFor]="dropdown.menu"
|
|
33
|
+
tabindex="-1"
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
></button>
|
|
36
|
+
<div class="card-top-bar" [style.background-color]="isCollection ? node.collection.color : null">
|
|
37
|
+
<div class="card-top-bar-collection-color" *ngIf="nodeHelper.isNodeCollection(node)"></div>
|
|
38
|
+
<es-node-type-badge [node]="node"></es-node-type-badge>
|
|
39
|
+
<div *ngIf="isCollection && node.collection.pinned" class="card-top-bar-flag">
|
|
40
|
+
<i esIcon="edu-pin"></i>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="card-top-bar-empty"></div>
|
|
43
|
+
<es-node-stats-badges [node]="node"></es-node-stats-badges>
|
|
44
|
+
<div class="card-top-bar-checkbox" *ngIf="entriesService.checkbox">
|
|
45
|
+
<mat-checkbox
|
|
46
|
+
[checked]="entriesService.selection.isSelected(node)"
|
|
47
|
+
(change)="entriesService.onCheckboxChanged(node, $event.checked)"
|
|
48
|
+
aria-label="{{ 'SELECT' | translate : { element: (node | nodeTitle) } }}"
|
|
49
|
+
></mat-checkbox>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<es-node-url
|
|
53
|
+
*ngIf="entriesService.elementInteractionType === InteractionType.DefaultActionLink"
|
|
54
|
+
mode="wrapper"
|
|
55
|
+
[node]="node"
|
|
56
|
+
esFocusState
|
|
57
|
+
#cardFocusState="esFocusState"
|
|
58
|
+
>
|
|
59
|
+
<ng-container
|
|
60
|
+
*ngTemplateOutlet="
|
|
61
|
+
image;
|
|
62
|
+
context: { playAnimation: cardFocusState.hovering || cardFocusState.hasFocus }
|
|
63
|
+
"
|
|
64
|
+
></ng-container>
|
|
65
|
+
<ng-container *ngTemplateOutlet="meta"></ng-container>
|
|
66
|
+
</es-node-url>
|
|
67
|
+
<div
|
|
68
|
+
*ngIf="entriesService.elementInteractionType !== InteractionType.DefaultActionLink"
|
|
69
|
+
matRipple
|
|
70
|
+
(click)="
|
|
71
|
+
entriesService.onClicked({
|
|
72
|
+
event: $event,
|
|
73
|
+
element: node,
|
|
74
|
+
source: ClickSource.Metadata
|
|
75
|
+
})
|
|
76
|
+
"
|
|
77
|
+
(dblclick)="
|
|
78
|
+
entriesService.dblClickItem.emit({
|
|
79
|
+
element: node,
|
|
80
|
+
source: ClickSource.Metadata
|
|
81
|
+
})
|
|
82
|
+
"
|
|
83
|
+
esFocusState
|
|
84
|
+
#cardFocusState="esFocusState"
|
|
85
|
+
>
|
|
86
|
+
<ng-container
|
|
87
|
+
*ngTemplateOutlet="
|
|
88
|
+
image;
|
|
89
|
+
context: { playAnimation: cardFocusState.hovering || cardFocusState.hasFocus }
|
|
90
|
+
"
|
|
91
|
+
></ng-container>
|
|
92
|
+
<ng-container *ngTemplateOutlet="meta"></ng-container>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="card-options" *ngIf="entriesService.options || showRatings">
|
|
95
|
+
<div class="card-rating-area">
|
|
96
|
+
<es-node-rating [node]="node"></es-node-rating>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="card-options-area">
|
|
99
|
+
<es-option-button
|
|
100
|
+
*ngFor="let option of optionsOnCard()"
|
|
101
|
+
class="card-options-always"
|
|
102
|
+
[option]="option"
|
|
103
|
+
[node]="node"
|
|
104
|
+
></es-option-button>
|
|
105
|
+
<div class="card-options-spacer"></div>
|
|
106
|
+
<button
|
|
107
|
+
*ngIf="dropdown"
|
|
108
|
+
mat-icon-button
|
|
109
|
+
color="primary"
|
|
110
|
+
(click)="openMenu(node)"
|
|
111
|
+
[matMenuTriggerFor]="dropdown.menu"
|
|
112
|
+
[attr.aria-label]="'OPTIONS_FOR' | translate : { element: (node | nodeTitle) }"
|
|
113
|
+
data-test="card-options-button"
|
|
114
|
+
>
|
|
115
|
+
<i esIcon="more_vert"></i>
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
<ng-template #image let-playAnimation="playAnimation">
|
|
120
|
+
<div
|
|
121
|
+
class="card-image-area"
|
|
122
|
+
[style.background-color]="isCollection ? node.collection.color : null"
|
|
123
|
+
>
|
|
124
|
+
<ng-container *ngIf="getTemplate(CustomFieldSpecialType.preview) as ref">
|
|
125
|
+
<ng-container *ngTemplateOutlet="ref; context: { node: this.node }"></ng-container>
|
|
126
|
+
</ng-container>
|
|
127
|
+
<ng-container *ngIf="!getTemplate(CustomFieldSpecialType.preview)">
|
|
128
|
+
<es-preview-image
|
|
129
|
+
*ngIf="!(isCollection && node.preview.isIcon)"
|
|
130
|
+
[node]="node"
|
|
131
|
+
[playAnimation]="playAnimation"
|
|
132
|
+
></es-preview-image>
|
|
133
|
+
<div *ngIf="isCollection && node.preview.isIcon" class="card-collection-image">
|
|
134
|
+
<i esIcon="layers"></i>
|
|
135
|
+
</div>
|
|
136
|
+
</ng-container>
|
|
137
|
+
</div>
|
|
138
|
+
</ng-template>
|
|
139
|
+
<ng-template #meta let-link="link">
|
|
140
|
+
<div class="card-meta">
|
|
141
|
+
<div
|
|
142
|
+
*ngFor="let displayPart of getVisibleColumns(); let first = first"
|
|
143
|
+
class="card-meta-row card-meta-row-{{ displayPart.name | lowercase }}"
|
|
144
|
+
[class.card-meta-row-primary]="first"
|
|
145
|
+
>
|
|
146
|
+
<ng-container *ngIf="first">
|
|
147
|
+
<es-node-url
|
|
148
|
+
*ngIf="entriesService.elementInteractionType === InteractionType.DefaultActionLink"
|
|
149
|
+
[node]="node"
|
|
150
|
+
#link
|
|
151
|
+
>
|
|
152
|
+
<es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
|
|
153
|
+
</es-node-url>
|
|
154
|
+
<div *ngIf="entriesService.elementInteractionType !== InteractionType.DefaultActionLink">
|
|
155
|
+
<es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
|
|
156
|
+
</div>
|
|
157
|
+
</ng-container>
|
|
158
|
+
<ng-container *ngIf="!first">
|
|
159
|
+
<label>
|
|
160
|
+
{{ displayPart | esListItemLabel | async }}
|
|
161
|
+
</label>
|
|
162
|
+
<es-list-base [item]="displayPart" [node]="node" [provideLabel]="false"> </es-list-base>
|
|
163
|
+
</ng-container>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</ng-template>
|
|
167
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
$imageHeight: 150px;
|
|
2
|
+
$entriesCardPaddingHorizontal: 20px;
|
|
3
|
+
$entriesCardPaddingVertical: 10px;
|
|
4
|
+
$topBarHeight: 40px;
|
|
5
|
+
$collectionIconPadding: 40px;
|
|
6
|
+
$collectionIconSize: 40px;
|
|
7
|
+
|
|
8
|
+
@import 'node-entries-card.main';
|
|
9
|
+
|
|
10
|
+
.grid-card {
|
|
11
|
+
grid-template-rows: $topBarHeight auto $optionBarHeight;
|
|
12
|
+
.card-meta {
|
|
13
|
+
.card-meta-row {
|
|
14
|
+
> es-list-base {
|
|
15
|
+
@include limitLineCount(2, 1.25);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
:host ::ng-deep {
|
|
21
|
+
.grid-card {
|
|
22
|
+
es-node-url {
|
|
23
|
+
.node-url-wrapper {
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplicationRef,
|
|
3
|
+
Component,
|
|
4
|
+
Input,
|
|
5
|
+
OnChanges,
|
|
6
|
+
OnInit,
|
|
7
|
+
Optional,
|
|
8
|
+
SimpleChanges,
|
|
9
|
+
ViewChild,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import { MatMenuTrigger } from '@angular/material/menu';
|
|
12
|
+
import { ClickSource, InteractionType } from '../entries-model';
|
|
13
|
+
import { NodeEntriesTemplatesService } from '../node-entries-templates.service';
|
|
14
|
+
import { CustomFieldSpecialType, NodeEntriesGlobalService } from '../node-entries-global.service';
|
|
15
|
+
import { Target } from '../../types/option-item';
|
|
16
|
+
import { NodeEntriesService } from '../../services/node-entries.service';
|
|
17
|
+
import { NodeHelperService } from '../../services/node-helper.service';
|
|
18
|
+
import { AuthenticationService, ConfigService, Node, RestConstants } from 'ngx-edu-sharing-api';
|
|
19
|
+
import { ColorHelper, PreferredColor } from '../../util/color-helper';
|
|
20
|
+
import { take } from 'rxjs/operators';
|
|
21
|
+
import { DropdownComponent } from '../../dropdown/dropdown.component';
|
|
22
|
+
import { Toast } from '../../services/abstract/toast.service';
|
|
23
|
+
|
|
24
|
+
@Component({
|
|
25
|
+
selector: 'es-node-entries-card',
|
|
26
|
+
templateUrl: 'node-entries-card.component.html',
|
|
27
|
+
styleUrls: ['node-entries-card.component.scss'],
|
|
28
|
+
})
|
|
29
|
+
export class NodeEntriesCardComponent<T extends Node> implements OnChanges, OnInit {
|
|
30
|
+
readonly InteractionType = InteractionType;
|
|
31
|
+
readonly Target = Target;
|
|
32
|
+
readonly ClickSource = ClickSource;
|
|
33
|
+
readonly CustomFieldSpecialType = CustomFieldSpecialType;
|
|
34
|
+
@Input() dropdown: DropdownComponent;
|
|
35
|
+
|
|
36
|
+
@ViewChild('menuTrigger') menuTrigger: MatMenuTrigger;
|
|
37
|
+
|
|
38
|
+
@Input() node: T;
|
|
39
|
+
dropdownLeft: number;
|
|
40
|
+
dropdownTop: number;
|
|
41
|
+
showRatings: boolean;
|
|
42
|
+
isCollection: boolean;
|
|
43
|
+
constructor(
|
|
44
|
+
public entriesService: NodeEntriesService<T>,
|
|
45
|
+
public nodeHelper: NodeHelperService,
|
|
46
|
+
public applicationRef: ApplicationRef,
|
|
47
|
+
public configService: ConfigService,
|
|
48
|
+
public authenticationService: AuthenticationService,
|
|
49
|
+
public templatesService: NodeEntriesTemplatesService,
|
|
50
|
+
private nodeEntriesGlobalService: NodeEntriesGlobalService,
|
|
51
|
+
@Optional() private toast: Toast,
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
55
|
+
this.isCollection = this.nodeHelper.isNodeCollection(changes.node?.currentValue);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getTextColor() {
|
|
59
|
+
return ColorHelper.getPreferredColor(this.node.collection.color) === PreferredColor.Black
|
|
60
|
+
? '#000'
|
|
61
|
+
: '#fff';
|
|
62
|
+
}
|
|
63
|
+
optionsOnCard() {
|
|
64
|
+
const options = this.entriesService.options[Target.List];
|
|
65
|
+
const always = options.filter((o) => o.showAlways);
|
|
66
|
+
if (always.some((o) => o.showCallback(this.node))) {
|
|
67
|
+
return always;
|
|
68
|
+
}
|
|
69
|
+
// we do NOT show any additional actions
|
|
70
|
+
return [];
|
|
71
|
+
// return options.filter((o) => o.showAsAction && o.showCallback(this.node)).slice(0, 3);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
openContextmenu(event: MouseEvent | Event) {
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
if (!this.dropdown) {
|
|
78
|
+
// Call `preventDefault()` even when there is no menu, so we can use `cdkDrag` with a
|
|
79
|
+
// start delay without being interrupted by the standard long-tap action.
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (event instanceof MouseEvent) {
|
|
83
|
+
({ clientX: this.dropdownLeft, clientY: this.dropdownTop } = event);
|
|
84
|
+
} else {
|
|
85
|
+
({ x: this.dropdownLeft, y: this.dropdownTop } = (
|
|
86
|
+
event.target as HTMLElement
|
|
87
|
+
).getBoundingClientRect());
|
|
88
|
+
}
|
|
89
|
+
if (!this.entriesService.selection.selected.includes(this.node)) {
|
|
90
|
+
this.entriesService.selection.clear();
|
|
91
|
+
this.entriesService.selection.select(this.node);
|
|
92
|
+
}
|
|
93
|
+
// Wait for the menu to reflect changed options.
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (this.dropdown.canShowDropdown()) {
|
|
96
|
+
this.menuTrigger.openMenu();
|
|
97
|
+
} else {
|
|
98
|
+
this.toast.toast('NO_AVAILABLE_OPTIONS');
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getVisibleColumns() {
|
|
104
|
+
return this.entriesService.columns?.filter((c) => c.visible);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async openMenu(node: T) {
|
|
108
|
+
this.entriesService.selection.clear();
|
|
109
|
+
this.entriesService.selection.select(node);
|
|
110
|
+
await this.applicationRef.tick();
|
|
111
|
+
this.dropdown.menu.focusFirstItem();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async ngOnInit() {
|
|
115
|
+
await this.configService.observeConfig().pipe(take(1)).toPromise();
|
|
116
|
+
this.showRatings =
|
|
117
|
+
this.configService.instant('', 'none') !== 'none' &&
|
|
118
|
+
(await this.authenticationService.hasToolpermission(
|
|
119
|
+
RestConstants.TOOLPERMISSION_RATE_READ,
|
|
120
|
+
));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getTemplate(name: CustomFieldSpecialType) {
|
|
124
|
+
return this.nodeEntriesGlobalService.getCustomFieldTemplate(
|
|
125
|
+
{
|
|
126
|
+
type: 'NODE',
|
|
127
|
+
name,
|
|
128
|
+
},
|
|
129
|
+
this.node as Node,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|