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,172 @@
|
|
|
1
|
+
import { SelectionModel } from '@angular/cdk/collections';
|
|
2
|
+
import { EventEmitter, Injectable } from '@angular/core';
|
|
3
|
+
import { BehaviorSubject } from 'rxjs';
|
|
4
|
+
import {
|
|
5
|
+
FetchEvent,
|
|
6
|
+
GridConfig,
|
|
7
|
+
InteractionType,
|
|
8
|
+
ListDragGropConfig,
|
|
9
|
+
ListEventInterface,
|
|
10
|
+
ListOptions,
|
|
11
|
+
ListSortConfig,
|
|
12
|
+
NodeClickEvent,
|
|
13
|
+
NodeEntriesDataType,
|
|
14
|
+
NodeEntriesDisplayType,
|
|
15
|
+
} from '../node-entries/entries-model';
|
|
16
|
+
import { NodeDataSource } from '../node-entries/node-data-source';
|
|
17
|
+
import {
|
|
18
|
+
NodeEntriesGlobalService,
|
|
19
|
+
PaginationStrategy,
|
|
20
|
+
} from '../node-entries/node-entries-global.service';
|
|
21
|
+
|
|
22
|
+
import { OptionItem, Scope } from '../types/option-item';
|
|
23
|
+
import { ListItem } from '../types/list-item';
|
|
24
|
+
import { UIService } from './ui.service';
|
|
25
|
+
import { NodeDataSourceRemote } from '../node-entries/node-data-source-remote';
|
|
26
|
+
|
|
27
|
+
@Injectable()
|
|
28
|
+
export class NodeEntriesService<T extends NodeEntriesDataType> {
|
|
29
|
+
list: ListEventInterface<T>;
|
|
30
|
+
readonly dataSource$ = new BehaviorSubject<NodeDataSource<T> | null>(null);
|
|
31
|
+
/**
|
|
32
|
+
* scope the current list is in, e.g. workspace
|
|
33
|
+
* This is used for additional config injection based on the scope
|
|
34
|
+
*/
|
|
35
|
+
scope: Scope;
|
|
36
|
+
get dataSource(): NodeDataSource<T> {
|
|
37
|
+
return this.dataSource$.value;
|
|
38
|
+
}
|
|
39
|
+
set dataSource(value: NodeDataSource<T>) {
|
|
40
|
+
this.dataSource$.next(value);
|
|
41
|
+
}
|
|
42
|
+
get paginationStrategy(): PaginationStrategy {
|
|
43
|
+
return this.entriesGlobal.getPaginationStrategy(this.scope);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Subject that reflects the current columns configuration.
|
|
47
|
+
*
|
|
48
|
+
* Updated when loading configuration and through user interaction.
|
|
49
|
+
*/
|
|
50
|
+
columnsSubject = new BehaviorSubject<ListItem[]>(null);
|
|
51
|
+
get columns(): ListItem[] {
|
|
52
|
+
return this.columnsSubject.value;
|
|
53
|
+
}
|
|
54
|
+
set columns(value: ListItem[]) {
|
|
55
|
+
this.columnsSubject.next(value);
|
|
56
|
+
}
|
|
57
|
+
configureColumns: boolean;
|
|
58
|
+
/** Emits when the columns configuration changes through user interaction. */
|
|
59
|
+
columnsChange: EventEmitter<ListItem[]>;
|
|
60
|
+
displayType: NodeEntriesDisplayType;
|
|
61
|
+
selection = new SelectionModel<T>(true, []);
|
|
62
|
+
elementInteractionType: InteractionType;
|
|
63
|
+
options$ = new BehaviorSubject<ListOptions>(null);
|
|
64
|
+
get options() {
|
|
65
|
+
return this.options$.value;
|
|
66
|
+
}
|
|
67
|
+
set options(options: ListOptions) {
|
|
68
|
+
this.options$.next(options);
|
|
69
|
+
}
|
|
70
|
+
checkbox: boolean;
|
|
71
|
+
globalOptions: OptionItem[];
|
|
72
|
+
sortSubject = new BehaviorSubject<ListSortConfig>(void 0);
|
|
73
|
+
get sort(): ListSortConfig {
|
|
74
|
+
return this.sortSubject.value;
|
|
75
|
+
}
|
|
76
|
+
set sort(value: ListSortConfig) {
|
|
77
|
+
this.sortSubject.next(value);
|
|
78
|
+
}
|
|
79
|
+
sortChange: EventEmitter<ListSortConfig>;
|
|
80
|
+
dragDrop: ListDragGropConfig<T>;
|
|
81
|
+
clickItem: EventEmitter<NodeClickEvent<T>>;
|
|
82
|
+
dblClickItem: EventEmitter<NodeClickEvent<T>>;
|
|
83
|
+
fetchData: EventEmitter<FetchEvent>;
|
|
84
|
+
readonly gridConfig$ = new BehaviorSubject<GridConfig | null>(null);
|
|
85
|
+
get gridConfig(): GridConfig {
|
|
86
|
+
return this.gridConfig$.value;
|
|
87
|
+
}
|
|
88
|
+
set gridConfig(value: GridConfig) {
|
|
89
|
+
this.gridConfig$.next(value);
|
|
90
|
+
}
|
|
91
|
+
primaryInstance: boolean;
|
|
92
|
+
singleClickHint: 'dynamic' | 'static';
|
|
93
|
+
disableInfiniteScroll: boolean;
|
|
94
|
+
|
|
95
|
+
constructor(private uiService: UIService, private entriesGlobal: NodeEntriesGlobalService) {}
|
|
96
|
+
|
|
97
|
+
onClicked({ event, ...data }: NodeClickEvent<T> & { event: MouseEvent }) {
|
|
98
|
+
if (event.ctrlKey || event.metaKey) {
|
|
99
|
+
this.selection.toggle(data.element);
|
|
100
|
+
} else if (event.shiftKey) {
|
|
101
|
+
this.expandSelectionTo(data.element);
|
|
102
|
+
} else {
|
|
103
|
+
this.clickItem.emit(data);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
onCheckboxChanged(node: T, checked: boolean) {
|
|
108
|
+
console.log(node, checked, this.uiService.shiftKeyPressed);
|
|
109
|
+
if (this.uiService.shiftKeyPressed) {
|
|
110
|
+
this.expandSelectionTo(node);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (checked !== this.selection.isSelected(node)) {
|
|
114
|
+
this.selection.toggle(node);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
toggleSelectAll() {
|
|
119
|
+
if (this.isAllSelected()) {
|
|
120
|
+
this.selection.clear();
|
|
121
|
+
} else {
|
|
122
|
+
this.selectAll();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
loadMore(source: 'button' | 'scroll'): boolean {
|
|
127
|
+
if (this.paginationStrategy !== 'infinite-scroll') {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (source === 'scroll' && this.disableInfiniteScroll) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
// TODO: focus next item when triggered via button.
|
|
134
|
+
if (this.dataSource instanceof NodeDataSourceRemote) {
|
|
135
|
+
return (this.dataSource as NodeDataSourceRemote).loadMore();
|
|
136
|
+
} else {
|
|
137
|
+
if (this.dataSource.hasMore()) {
|
|
138
|
+
this.fetchData.emit({
|
|
139
|
+
offset: this.dataSource.getData().length,
|
|
140
|
+
reset: false,
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
} else {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private selectAll() {
|
|
150
|
+
this.selection.select(...this.dataSource.getData());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private isAllSelected(): boolean {
|
|
154
|
+
return this.dataSource.getData().every((node) => this.selection.isSelected(node));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private expandSelectionTo(node: T) {
|
|
158
|
+
const nodeIndex = this.dataSource.getData().indexOf(node);
|
|
159
|
+
const selectionIndexes = this.selection.selected
|
|
160
|
+
.map((node) => this.dataSource.getData().indexOf(node))
|
|
161
|
+
.filter((index) => index >= 0);
|
|
162
|
+
if (Math.min(...selectionIndexes) < nodeIndex) {
|
|
163
|
+
for (let i = Math.min(...selectionIndexes) + 1; i <= nodeIndex; i++) {
|
|
164
|
+
this.selection.select(this.dataSource.getData()[i]);
|
|
165
|
+
}
|
|
166
|
+
} else if (Math.max(...selectionIndexes) > nodeIndex) {
|
|
167
|
+
for (let i = nodeIndex; i < Math.max(...selectionIndexes); i++) {
|
|
168
|
+
this.selection.select(this.dataSource.getData()[i]);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
ApiHelpersService,
|
|
4
|
+
ConfigService,
|
|
5
|
+
NetworkService,
|
|
6
|
+
Node,
|
|
7
|
+
ProposalNode,
|
|
8
|
+
RestConstants,
|
|
9
|
+
} from 'ngx-edu-sharing-api';
|
|
10
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
11
|
+
import * as Workflow from '../types/workflow';
|
|
12
|
+
import { RepoUrlService } from './repo-url.service';
|
|
13
|
+
import { Params } from '@angular/router';
|
|
14
|
+
import { UIConstants } from '../util/ui-constants';
|
|
15
|
+
@Injectable({
|
|
16
|
+
providedIn: 'root',
|
|
17
|
+
})
|
|
18
|
+
export class NodeHelperService {
|
|
19
|
+
constructor(
|
|
20
|
+
protected translate: TranslateService,
|
|
21
|
+
protected apiHelpersService: ApiHelpersService,
|
|
22
|
+
protected networkService: NetworkService,
|
|
23
|
+
protected configService: ConfigService,
|
|
24
|
+
protected repoUrlService: RepoUrlService,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
public getCollectionScopeInfo(node: Node): { icon: string; scopeName: string } {
|
|
28
|
+
const scope = node.collection ? node.collection.scope : null;
|
|
29
|
+
let icon = 'help';
|
|
30
|
+
let scopeName = 'UNKNOWN';
|
|
31
|
+
if (scope === RestConstants.COLLECTIONSCOPE_MY) {
|
|
32
|
+
icon = 'lock';
|
|
33
|
+
scopeName = 'MY';
|
|
34
|
+
}
|
|
35
|
+
if (
|
|
36
|
+
scope === RestConstants.COLLECTIONSCOPE_ORGA ||
|
|
37
|
+
scope === RestConstants.COLLECTIONSCOPE_CUSTOM
|
|
38
|
+
) {
|
|
39
|
+
icon = 'group';
|
|
40
|
+
scopeName = 'SHARED';
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
scope === RestConstants.COLLECTIONSCOPE_ALL ||
|
|
44
|
+
scope === RestConstants.COLLECTIONSCOPE_CUSTOM_PUBLIC
|
|
45
|
+
) {
|
|
46
|
+
icon = 'language';
|
|
47
|
+
scopeName = 'PUBLIC';
|
|
48
|
+
}
|
|
49
|
+
if (node.collection?.type === RestConstants.COLLECTIONTYPE_EDITORIAL) {
|
|
50
|
+
icon = 'star';
|
|
51
|
+
scopeName = 'TYPE_EDITORIAL';
|
|
52
|
+
}
|
|
53
|
+
if (node.collection?.type === RestConstants.COLLECTIONTYPE_MEDIA_CENTER) {
|
|
54
|
+
icon = 'business';
|
|
55
|
+
scopeName = 'TYPE_MEDIA_CENTER';
|
|
56
|
+
}
|
|
57
|
+
return { icon, scopeName };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Return the license icon of a node
|
|
61
|
+
*/
|
|
62
|
+
async getLicenseIcon(node: Node): Promise<string> {
|
|
63
|
+
// prefer manual mapping instead of backend data to support custom states from local edits
|
|
64
|
+
const license = node.properties?.[RestConstants.CCM_PROP_LICENSE]?.[0];
|
|
65
|
+
if (license) {
|
|
66
|
+
return this.getLicenseIconByString(license);
|
|
67
|
+
}
|
|
68
|
+
return node.license ? this.repoUrlService.getRepoUrl(node.license.icon, node) : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get a license icon by using the property value string
|
|
73
|
+
*/
|
|
74
|
+
public getLicenseIconByString(string: String, useNoneAsFallback = true): string {
|
|
75
|
+
let icon = string.replace(/_/g, '-').toLowerCase();
|
|
76
|
+
if (icon == '') icon = 'none';
|
|
77
|
+
|
|
78
|
+
const LICENSE_ICONS = [
|
|
79
|
+
'cc-0',
|
|
80
|
+
'cc-by-nc',
|
|
81
|
+
'cc-by-nc-nd',
|
|
82
|
+
'cc-by-nc-sa',
|
|
83
|
+
'cc-by-nd',
|
|
84
|
+
'cc-by-sa',
|
|
85
|
+
'cc-by',
|
|
86
|
+
'copyright-free',
|
|
87
|
+
'copyright-license',
|
|
88
|
+
'custom',
|
|
89
|
+
'edu-nc-nd-noDo',
|
|
90
|
+
'edu-nc-nd',
|
|
91
|
+
'edu-p-nr-nd-noDo',
|
|
92
|
+
'edu-p-nr-nd',
|
|
93
|
+
'none',
|
|
94
|
+
'pdm',
|
|
95
|
+
'schulfunk',
|
|
96
|
+
'unterrichts-und-lehrmedien',
|
|
97
|
+
];
|
|
98
|
+
if (LICENSE_ICONS.indexOf(icon) == -1 && !useNoneAsFallback) return null; // icon='none';
|
|
99
|
+
if (icon == 'none' && !useNoneAsFallback) return null;
|
|
100
|
+
const result =
|
|
101
|
+
this.apiHelpersService.getServerUrl() + '/../ccimages/licenses/' + icon + '.svg';
|
|
102
|
+
return this.repoUrlService.withCurrentOrigin(result);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Return a translated name of a license name for a node
|
|
106
|
+
* @param node
|
|
107
|
+
* @param translate
|
|
108
|
+
* @returns {string|any|string|any|string|any|string|any|string|any|string}
|
|
109
|
+
*/
|
|
110
|
+
public getLicenseName(node: Node) {
|
|
111
|
+
let prop = node.properties[RestConstants.CCM_PROP_LICENSE]?.[0];
|
|
112
|
+
if (!prop) prop = '';
|
|
113
|
+
return this.getLicenseNameByString(prop);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Return a translated name for a license string
|
|
118
|
+
* @param string
|
|
119
|
+
* @param translate
|
|
120
|
+
* @returns {any}
|
|
121
|
+
*/
|
|
122
|
+
public getLicenseNameByString(name: string) {
|
|
123
|
+
if (name == '') {
|
|
124
|
+
name = 'NONE';
|
|
125
|
+
}
|
|
126
|
+
return this.translate.instant('LICENSE.NAMES.' + name);
|
|
127
|
+
// return name.replace(/_/g,"-");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* return the License URL (e.g. for CC_BY licenses) for a license string and version
|
|
132
|
+
* @param licenseProperty
|
|
133
|
+
* @param licenseVersion
|
|
134
|
+
*/
|
|
135
|
+
public getLicenseUrlByString(licenseProperty: string, licenseVersion: string) {
|
|
136
|
+
const url = (RestConstants.LICENSE_URLS as any)[licenseProperty];
|
|
137
|
+
if (!url) return null;
|
|
138
|
+
return url.replace('#version', licenseVersion);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public getWorkflowStatusById(id: string) {
|
|
142
|
+
const workflows = this.getWorkflows();
|
|
143
|
+
return workflows.filter((w) => w.id === id)?.[0];
|
|
144
|
+
}
|
|
145
|
+
public getWorkflowStatus(node: Node, useFromConfig = false): Workflow.WorkflowDefinitionStatus {
|
|
146
|
+
let value = node.properties[RestConstants.CCM_PROP_WF_STATUS]?.[0];
|
|
147
|
+
if (!value) {
|
|
148
|
+
return this.getDefaultWorkflowStatus(useFromConfig);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
current: this.getWorkflowStatusById(value),
|
|
152
|
+
initial: this.getWorkflowStatusById(value),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
getDefaultWorkflowStatus(useFromConfig = false): Workflow.WorkflowDefinitionStatus {
|
|
156
|
+
const result = {
|
|
157
|
+
current: null as Workflow.WorkflowDefinition,
|
|
158
|
+
initial: null as Workflow.WorkflowDefinition,
|
|
159
|
+
};
|
|
160
|
+
result.initial = this.getWorkflows()[0];
|
|
161
|
+
let defaultStatus: string = null;
|
|
162
|
+
if (useFromConfig) {
|
|
163
|
+
defaultStatus = this.configService.instant('workflow.defaultStatus');
|
|
164
|
+
}
|
|
165
|
+
if (defaultStatus) {
|
|
166
|
+
result.current = this.getWorkflows().find((w) => w.id === defaultStatus);
|
|
167
|
+
} else {
|
|
168
|
+
result.current = result.initial;
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
getWorkflows(): Workflow.WorkflowDefinition[] {
|
|
173
|
+
return this.configService.instant('workflow.workflows', [
|
|
174
|
+
Workflow.WORKFLOW_STATUS_UNCHECKED,
|
|
175
|
+
Workflow.WORKFLOW_STATUS_TO_CHECK,
|
|
176
|
+
Workflow.WORKFLOW_STATUS_HASFLAWS,
|
|
177
|
+
Workflow.WORKFLOW_STATUS_CHECKED,
|
|
178
|
+
]);
|
|
179
|
+
}
|
|
180
|
+
copyDataToNode<T extends Node>(target: T, source: T) {
|
|
181
|
+
target.properties = source.properties;
|
|
182
|
+
target.name = source.name;
|
|
183
|
+
target.title = source.title;
|
|
184
|
+
}
|
|
185
|
+
isNodeCollection(node: Node): boolean {
|
|
186
|
+
return node.aspects?.includes(RestConstants.CCM_ASPECT_COLLECTION) || !!node.collection;
|
|
187
|
+
}
|
|
188
|
+
public getSourceIconPath(src: string) {
|
|
189
|
+
return 'assets/images/sources/' + src.toLowerCase() + '.png';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getNodeLink(mode: 'routerLink' | 'queryParams', node: Node) {
|
|
193
|
+
if (!node?.ref) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
let data: { routerLink: string; queryParams: Params } = null;
|
|
197
|
+
if (this.isNodeCollection(node)) {
|
|
198
|
+
data = {
|
|
199
|
+
routerLink: UIConstants.ROUTER_PREFIX + 'collections',
|
|
200
|
+
queryParams: { id: node.ref.id },
|
|
201
|
+
};
|
|
202
|
+
} else {
|
|
203
|
+
if (node.isDirectory) {
|
|
204
|
+
let path;
|
|
205
|
+
if (
|
|
206
|
+
node.properties?.[RestConstants.CCM_PROP_EDUSCOPENAME]?.[0] ===
|
|
207
|
+
RestConstants.SAFE_SCOPE
|
|
208
|
+
) {
|
|
209
|
+
path = UIConstants.ROUTER_PREFIX + 'workspace/safe';
|
|
210
|
+
} else {
|
|
211
|
+
path = UIConstants.ROUTER_PREFIX + 'workspace';
|
|
212
|
+
}
|
|
213
|
+
data = {
|
|
214
|
+
routerLink: path,
|
|
215
|
+
queryParams: { id: node.ref.id },
|
|
216
|
+
};
|
|
217
|
+
} else if (node.ref) {
|
|
218
|
+
const fromHome = this.networkService.isFromHomeRepository(node);
|
|
219
|
+
data = {
|
|
220
|
+
routerLink: UIConstants.ROUTER_PREFIX + 'render/' + node.ref.id,
|
|
221
|
+
queryParams: {
|
|
222
|
+
repository: fromHome ? null : node.ref.repo,
|
|
223
|
+
proposal: (node as ProposalNode).proposal?.ref.id,
|
|
224
|
+
proposalCollection: (node as ProposalNode).proposalCollection?.ref.id,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (data === null) {
|
|
230
|
+
return '';
|
|
231
|
+
}
|
|
232
|
+
if (mode === 'routerLink') {
|
|
233
|
+
return '/' + data.routerLink;
|
|
234
|
+
}
|
|
235
|
+
// enforce clearing of parameters which should only be consumed once
|
|
236
|
+
data.queryParams.redirectFromSSO = null;
|
|
237
|
+
return data.queryParams;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Injectable, NgZone } from '@angular/core';
|
|
2
|
+
import * as rxjs from 'rxjs';
|
|
3
|
+
import { BehaviorSubject } from 'rxjs';
|
|
4
|
+
import { distinctUntilChanged, map, pairwise } from 'rxjs/operators';
|
|
5
|
+
import { CanDrop, DropAction } from '../types/drag-drop';
|
|
6
|
+
import { NodesDropTargetDirective } from '../directives/drag-nodes/nodes-drop-target.directive';
|
|
7
|
+
import { Node } from 'ngx-edu-sharing-api';
|
|
8
|
+
import { Toast } from './abstract/toast.service';
|
|
9
|
+
import { UIService } from './ui.service';
|
|
10
|
+
|
|
11
|
+
@Injectable({
|
|
12
|
+
providedIn: 'root',
|
|
13
|
+
})
|
|
14
|
+
export class NodesDragDropService {
|
|
15
|
+
/** The node(s) currently being dragged. */
|
|
16
|
+
private draggedNodesSubject = new BehaviorSubject<Node[]>(null);
|
|
17
|
+
/** The drop target that something is currently dragged above. */
|
|
18
|
+
private dropTargetSubject = new BehaviorSubject<NodesDropTargetDirective>(null);
|
|
19
|
+
/** Whether the current drop target allows dropping the dragged node(s). */
|
|
20
|
+
private canDropSubject = new BehaviorSubject<CanDrop | null>(null);
|
|
21
|
+
/** A target being hovered with the cursor. Does not mean anything is dragged yet. */
|
|
22
|
+
private hoveredTargetSubject = new BehaviorSubject<NodesDropTargetDirective>(null);
|
|
23
|
+
/** Current drop action based on pressed modifier keys. */
|
|
24
|
+
private dropActionSubject = new BehaviorSubject<DropAction>('move');
|
|
25
|
+
/** The current cursor style. */
|
|
26
|
+
private curserSubject = new BehaviorSubject<string>(null);
|
|
27
|
+
|
|
28
|
+
set draggedNodes(nodes: Node[]) {
|
|
29
|
+
this.draggedNodesSubject.next(nodes);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get canDrop() {
|
|
33
|
+
return this.canDropSubject.value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(private ngZone: NgZone, private toast: Toast, private uiService: UIService) {
|
|
37
|
+
this.registerDropTarget();
|
|
38
|
+
this.registerCanDrop();
|
|
39
|
+
this.registerDropActionSubject();
|
|
40
|
+
this.registerCursor();
|
|
41
|
+
this.registerActiveDropTargetStyle();
|
|
42
|
+
// this.draggedNodesSubject.subscribe((draggedNodes) =>
|
|
43
|
+
// console.log('draggedNodes', draggedNodes),
|
|
44
|
+
// );
|
|
45
|
+
// this.dropTargetSubject.subscribe((dropTarget) => console.log('dropTarget', dropTarget));
|
|
46
|
+
// this.canDropSubject.subscribe((canDrop) => console.log('canDrop', canDrop));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Call when the cursor enters a possible drop target. Runs outside ngZone. */
|
|
50
|
+
onMouseEnter(target: NodesDropTargetDirective) {
|
|
51
|
+
this.hoveredTargetSubject.next(target);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Call when the cursor leaves a possible drop target. Runs outside ngZone. */
|
|
55
|
+
onMouseLeave(target: NodesDropTargetDirective) {
|
|
56
|
+
if (this.hoveredTargetSubject.value === target) {
|
|
57
|
+
this.hoveredTargetSubject.next(null);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onDropped(nodes: Node[]) {
|
|
62
|
+
if (this.canDrop?.accept) {
|
|
63
|
+
this.dropTargetSubject.value?.nodeDropped.emit({
|
|
64
|
+
draggedNodes: nodes,
|
|
65
|
+
action: this.dropActionSubject.value,
|
|
66
|
+
target: this.dropTargetSubject.value.target,
|
|
67
|
+
});
|
|
68
|
+
} else if (this.canDrop?.denyReason) {
|
|
69
|
+
this.toast.error(null, this.canDrop.denyReason);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private registerDropTarget() {
|
|
74
|
+
rxjs.combineLatest([this.draggedNodesSubject, this.hoveredTargetSubject])
|
|
75
|
+
.pipe(
|
|
76
|
+
map(([draggedNodes, hoveredNode]) => draggedNodes && hoveredNode),
|
|
77
|
+
distinctUntilChanged(),
|
|
78
|
+
)
|
|
79
|
+
.subscribe((target) => {
|
|
80
|
+
this.ngZone.run(() => {
|
|
81
|
+
this.dropTargetSubject.next(target);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private registerCanDrop() {
|
|
87
|
+
rxjs.combineLatest([this.dropTargetSubject, this.dropActionSubject])
|
|
88
|
+
.pipe(
|
|
89
|
+
map(([dropTarget, dropAction]) =>
|
|
90
|
+
dropTarget?.canDropNodes({
|
|
91
|
+
draggedNodes: this.draggedNodesSubject.value,
|
|
92
|
+
action: dropAction,
|
|
93
|
+
target: dropTarget.target,
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
// FIXME: this won't filter equal objects
|
|
97
|
+
distinctUntilChanged(),
|
|
98
|
+
)
|
|
99
|
+
.subscribe((canDrop) => {
|
|
100
|
+
this.ngZone.run(() => this.canDropSubject.next(canDrop));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private registerCursor() {
|
|
105
|
+
rxjs.combineLatest([
|
|
106
|
+
this.draggedNodesSubject,
|
|
107
|
+
this.canDropSubject,
|
|
108
|
+
this.dropActionSubject,
|
|
109
|
+
]).subscribe(([draggedNodes, canDrop, dropAction]) => {
|
|
110
|
+
this.curserSubject.next(this.getCursor(draggedNodes, canDrop, dropAction));
|
|
111
|
+
});
|
|
112
|
+
let style: HTMLStyleElement;
|
|
113
|
+
this.curserSubject.subscribe((cursor) => {
|
|
114
|
+
if (cursor && !style) {
|
|
115
|
+
style = document.createElement('style');
|
|
116
|
+
document.body.appendChild(style);
|
|
117
|
+
} else if (style && !cursor) {
|
|
118
|
+
document.body.removeChild(style);
|
|
119
|
+
style = null;
|
|
120
|
+
}
|
|
121
|
+
if (cursor) {
|
|
122
|
+
style.innerHTML = `* {cursor: ${cursor} !important; }`;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private registerActiveDropTargetStyle() {
|
|
128
|
+
rxjs.combineLatest([this.dropTargetSubject, this.canDropSubject, this.dropActionSubject])
|
|
129
|
+
.pipe(pairwise())
|
|
130
|
+
.subscribe(([[previous], [current, canDrop, action]]) => {
|
|
131
|
+
// console.log('can drop', canDrop);
|
|
132
|
+
previous?._setActiveDropTarget(null);
|
|
133
|
+
current?._setActiveDropTarget({ canDrop, action });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private registerDropActionSubject() {
|
|
138
|
+
this.uiService
|
|
139
|
+
.observeCtrlOrCmdKeyPressedOutsideZone()
|
|
140
|
+
.pipe(map((ctrlOrCmdPressed) => this.getDropAction(ctrlOrCmdPressed)))
|
|
141
|
+
.subscribe((dropAction) =>
|
|
142
|
+
this.ngZone.run(() => this.dropActionSubject.next(dropAction)),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private getDropAction(ctrlOrCmdPressed: boolean): DropAction {
|
|
147
|
+
if (ctrlOrCmdPressed) {
|
|
148
|
+
return 'copy';
|
|
149
|
+
} else {
|
|
150
|
+
return 'move';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private getCursor(draggedNodes: Node[], canDrop: CanDrop, dropAction: DropAction): string {
|
|
155
|
+
if (!draggedNodes) {
|
|
156
|
+
return null;
|
|
157
|
+
} else if (canDrop?.denyExplicit) {
|
|
158
|
+
return 'no-drop';
|
|
159
|
+
} else if (dropAction === 'copy') {
|
|
160
|
+
return 'copy';
|
|
161
|
+
} else {
|
|
162
|
+
return 'grabbing';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|