@yuuvis/client-components 3.2.2 → 3.3.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/fesm2022/yuuvis-client-components-widget-grid.mjs +586 -418
- package/fesm2022/yuuvis-client-components-widget-grid.mjs.map +1 -1
- package/lib/assets/i18n/de.json +1 -0
- package/lib/assets/i18n/en.json +1 -0
- package/package.json +2 -2
- package/types/yuuvis-client-components-widget-grid.d.ts +162 -127
|
@@ -1,60 +1,87 @@
|
|
|
1
|
-
import * as i1 from '@angular/common';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
1
|
import * as i0 from '@angular/core';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import * as i3$2 from '@angular/material/icon';
|
|
8
|
-
import { MatIconModule } from '@angular/material/icon';
|
|
9
|
-
import { TranslateService, TranslatePipe } from '@yuuvis/client-core';
|
|
10
|
-
import { Gridster, DisplayGrid, GridType, GridsterItem } from 'angular-gridster2';
|
|
11
|
-
import * as i3 from 'ng-dynamic-component';
|
|
12
|
-
import { DynamicIoModule } from 'ng-dynamic-component';
|
|
13
|
-
import { ReplaySubject, Subject } from 'rxjs';
|
|
14
|
-
import * as i3$1 from '@angular/forms';
|
|
15
|
-
import { FormControl, ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
|
|
2
|
+
import { Input, Component, inject, Injectable, input, output, signal, effect, DestroyRef, viewChild, TemplateRef, computed, untracked, NgModule } from '@angular/core';
|
|
3
|
+
import * as i3$2 from '@angular/forms';
|
|
4
|
+
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
|
|
16
5
|
import * as i5 from '@angular/material/button';
|
|
17
6
|
import { MatButtonModule } from '@angular/material/button';
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import { DialogComponent, ConfirmService } from '@yuuvis/client-components/common';
|
|
21
|
-
import { YmtButtonDirective, YmtIconButtonDirective } from '@yuuvis/material';
|
|
22
|
-
import { SignalComponentIoModule } from 'ng-dynamic-component/signal-component-io';
|
|
23
|
-
import * as i4 from '@angular/material/tooltip';
|
|
24
|
-
import { MatTooltipModule, MatTooltip } from '@angular/material/tooltip';
|
|
7
|
+
import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog';
|
|
8
|
+
import * as i5$1 from '@angular/material/divider';
|
|
25
9
|
import { MatDividerModule } from '@angular/material/divider';
|
|
26
10
|
import * as i1$1 from '@angular/material/form-field';
|
|
27
11
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
28
|
-
import * as
|
|
12
|
+
import * as i3 from '@angular/material/icon';
|
|
13
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
14
|
+
import * as i2 from '@angular/material/input';
|
|
29
15
|
import { MatInputModule } from '@angular/material/input';
|
|
30
16
|
import * as i4$1 from '@angular/material/menu';
|
|
31
17
|
import { MatMenuModule } from '@angular/material/menu';
|
|
18
|
+
import * as i4 from '@angular/material/tooltip';
|
|
19
|
+
import { MatTooltipModule, MatTooltip } from '@angular/material/tooltip';
|
|
20
|
+
import { DialogComponent, ConfirmService } from '@yuuvis/client-components/common';
|
|
21
|
+
import { TranslateService, TranslatePipe, Logger } from '@yuuvis/client-core';
|
|
22
|
+
import { YmtButtonDirective, YmtIconButtonDirective } from '@yuuvis/material';
|
|
23
|
+
import * as i1 from '@angular/common';
|
|
24
|
+
import { CommonModule } from '@angular/common';
|
|
25
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
26
|
+
import { Gridster, DisplayGrid, GridType, GridsterItem } from 'angular-gridster2';
|
|
27
|
+
import * as i3$1 from 'ng-dynamic-component';
|
|
28
|
+
import { DynamicIoModule } from 'ng-dynamic-component';
|
|
29
|
+
import { SignalComponentIoModule } from 'ng-dynamic-component/signal-component-io';
|
|
30
|
+
import { ReplaySubject, Subject } from 'rxjs';
|
|
32
31
|
import { TranslatePipe as TranslatePipe$1 } from '@ngx-translate/core';
|
|
33
32
|
|
|
34
|
-
class
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
class WidgetGridUtils {
|
|
34
|
+
static PREF_FUNCTION = 'Function';
|
|
35
|
+
static PREF_RANGEVALUE = 'RangeValue';
|
|
36
|
+
/**
|
|
37
|
+
* Takes a `WidgetGridItemConfig` and stringifies it. The challenge
|
|
38
|
+
* here is that callback functions should also be stringified.
|
|
39
|
+
* @param gridItemConfig the config object
|
|
40
|
+
*/
|
|
41
|
+
static gridConfigStringify(o) {
|
|
42
|
+
return JSON.stringify(o, (key, value) => {
|
|
43
|
+
if (typeof value === 'function') {
|
|
44
|
+
return `/${WidgetGridUtils.PREF_FUNCTION}(${value.toString()})/`;
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Takes a string stringified with `gridConfigStringify()` and parses it
|
|
51
|
+
* to output a proper WidgetGridItemConfig.
|
|
52
|
+
* @param stringifiedConfig stringified widget grid config
|
|
53
|
+
*/
|
|
54
|
+
static gridConfigParse(stringifiedConfig) {
|
|
55
|
+
return JSON.parse(stringifiedConfig, function (key, value) {
|
|
56
|
+
if (typeof value === 'string' &&
|
|
57
|
+
value.startsWith(`/${WidgetGridUtils.PREF_FUNCTION}(`) &&
|
|
58
|
+
// value.startsWith('/Function(') &&
|
|
59
|
+
value.endsWith(')/')) {
|
|
60
|
+
value = value.substring(WidgetGridUtils.PREF_FUNCTION.length + 2, value.length - 2);
|
|
61
|
+
return (0, eval)('(' + value + ')');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
static uuid() {
|
|
67
|
+
return WidgetGridUtils._p8() + WidgetGridUtils._p8(true) + WidgetGridUtils._p8(true) + WidgetGridUtils._p8();
|
|
68
|
+
}
|
|
69
|
+
static _p8(s) {
|
|
70
|
+
const p = (Math.random().toString(16) + '000000000').substr(2, 8);
|
|
71
|
+
return s ? `-${p.substr(0, 4)}-${p.substr(4, 4)}` : p;
|
|
39
72
|
}
|
|
40
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
41
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridEventService, providedIn: 'root' });
|
|
42
73
|
}
|
|
43
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridEventService, decorators: [{
|
|
44
|
-
type: Injectable,
|
|
45
|
-
args: [{
|
|
46
|
-
providedIn: 'root',
|
|
47
|
-
}]
|
|
48
|
-
}] });
|
|
49
74
|
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
76
|
class NoopComponent {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
78
|
widgetConfig;
|
|
52
79
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: NoopComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
53
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: NoopComponent, isStandalone: true, selector: "yuv-noop", inputs: { widgetConfig: "widgetConfig" }, ngImport: i0, template: "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><circle cx=\"15.5\" cy=\"9.5\" r=\"1.5\"/><circle cx=\"8.5\" cy=\"9.5\" r=\"1.5\"/><path d=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z\"/></svg>", styles: [":host{height:100%;display:flex;flex-flow:column;align-items:center;justify-content:center;padding:var(--ymt-spacing-m);background-color:var(--panel-background);opacity:.7}:host svg{width:48px;height:48px;fill:var(--text-color-
|
|
80
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: NoopComponent, isStandalone: true, selector: "yuv-noop", inputs: { widgetConfig: "widgetConfig" }, ngImport: i0, template: "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><circle cx=\"15.5\" cy=\"9.5\" r=\"1.5\"/><circle cx=\"8.5\" cy=\"9.5\" r=\"1.5\"/><path d=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z\"/></svg>", styles: [":host{height:100%;display:flex;flex-flow:column;align-items:center;justify-content:center;padding:var(--ymt-spacing-m);background-color:var(--panel-background);opacity:.7}:host svg{width:48px;height:48px;fill:var(--ymt-text-color-subtle)}:host span{margin-top:var(--ymt-spacing-m)}\n"] });
|
|
54
81
|
}
|
|
55
82
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: NoopComponent, decorators: [{
|
|
56
83
|
type: Component,
|
|
57
|
-
args: [{ selector: 'yuv-noop', template: "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><circle cx=\"15.5\" cy=\"9.5\" r=\"1.5\"/><circle cx=\"8.5\" cy=\"9.5\" r=\"1.5\"/><path d=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z\"/></svg>", styles: [":host{height:100%;display:flex;flex-flow:column;align-items:center;justify-content:center;padding:var(--ymt-spacing-m);background-color:var(--panel-background);opacity:.7}:host svg{width:48px;height:48px;fill:var(--text-color-
|
|
84
|
+
args: [{ selector: 'yuv-noop', template: "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><circle cx=\"15.5\" cy=\"9.5\" r=\"1.5\"/><circle cx=\"8.5\" cy=\"9.5\" r=\"1.5\"/><path d=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z\"/></svg>", styles: [":host{height:100%;display:flex;flex-flow:column;align-items:center;justify-content:center;padding:var(--ymt-spacing-m);background-color:var(--panel-background);opacity:.7}:host svg{width:48px;height:48px;fill:var(--ymt-text-color-subtle)}:host span{margin-top:var(--ymt-spacing-m)}\n"] }]
|
|
58
85
|
}], propDecorators: { widgetConfig: [{
|
|
59
86
|
type: Input
|
|
60
87
|
}] } });
|
|
@@ -73,7 +100,7 @@ class WidgetGridRegistry {
|
|
|
73
100
|
registeredWidgets = [];
|
|
74
101
|
/**
|
|
75
102
|
* Buckets are collection of widget references.
|
|
76
|
-
* You can put any widget registered
|
|
103
|
+
* You can put any widget registered globally into a bucket. Later on you can grab
|
|
77
104
|
* widgets from a certain bucket. This enables apps to structure their widgets when e.g.
|
|
78
105
|
* using multiple widget grids with their own set of available widgets.
|
|
79
106
|
*/
|
|
@@ -102,7 +129,7 @@ class WidgetGridRegistry {
|
|
|
102
129
|
* is not widget registered with the given name
|
|
103
130
|
*/
|
|
104
131
|
getWidgetSetupComponent(widgetName) {
|
|
105
|
-
const widget = this.registeredWidgets.find((
|
|
132
|
+
const widget = this.registeredWidgets.find((widget) => widget.id === widgetName);
|
|
106
133
|
if (!widget)
|
|
107
134
|
throw Error('Widget setup component not found');
|
|
108
135
|
return widget.setupComponent;
|
|
@@ -115,11 +142,24 @@ class WidgetGridRegistry {
|
|
|
115
142
|
* no component registered with the given name
|
|
116
143
|
*/
|
|
117
144
|
getWidgetComponent(widgetName) {
|
|
118
|
-
const widget = this.registeredWidgets.find((
|
|
145
|
+
const widget = this.registeredWidgets.find((widget) => widget.id === widgetName);
|
|
119
146
|
if (!widget)
|
|
120
147
|
console.error('Widget component not found');
|
|
121
148
|
return widget?.widgetComponent || NoopComponent;
|
|
122
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Evaluates whether the widget with the given name is applicable for
|
|
152
|
+
* the current runtime context. Widgets without an `isApplicable`
|
|
153
|
+
* predicate are considered applicable. Unknown widgets are reported
|
|
154
|
+
* as applicable so the noop fallback can still render.
|
|
155
|
+
* @param widgetName The widgets name
|
|
156
|
+
*/
|
|
157
|
+
isWidgetApplicable(widgetName) {
|
|
158
|
+
const widget = this.registeredWidgets.find((widget) => widget.id === widgetName);
|
|
159
|
+
if (!widget)
|
|
160
|
+
return true;
|
|
161
|
+
return widget.isApplicable?.() ?? true;
|
|
162
|
+
}
|
|
123
163
|
/**
|
|
124
164
|
* Adds a new widget to the list of registered widgets. That way
|
|
125
165
|
* you can create custom widgets that are then available to be
|
|
@@ -129,12 +169,12 @@ class WidgetGridRegistry {
|
|
|
129
169
|
* not exist it'll be created.
|
|
130
170
|
*/
|
|
131
171
|
registerGridWidget(widget, buckets) {
|
|
132
|
-
const existingWidget = this.registeredWidgets.find((
|
|
172
|
+
const existingWidget = this.registeredWidgets.find((item) => item.id === widget.id);
|
|
133
173
|
if (!existingWidget)
|
|
134
174
|
this.registeredWidgets.push(widget);
|
|
135
175
|
if (buckets?.length) {
|
|
136
|
-
buckets.forEach((
|
|
137
|
-
this._addToBucket(
|
|
176
|
+
buckets.forEach((bucket) => {
|
|
177
|
+
this._addToBucket(bucket, widget.id);
|
|
138
178
|
});
|
|
139
179
|
}
|
|
140
180
|
}
|
|
@@ -145,24 +185,18 @@ class WidgetGridRegistry {
|
|
|
145
185
|
* not exist it'll be created.
|
|
146
186
|
*/
|
|
147
187
|
registerGridWidgets(widgets, buckets) {
|
|
148
|
-
const alreadyRegisteredWidgetNames = this.registeredWidgets.map((
|
|
149
|
-
const widgetsToRegister = widgets.filter((
|
|
150
|
-
this.registeredWidgets = [
|
|
188
|
+
const alreadyRegisteredWidgetNames = this.registeredWidgets.map((widget) => widget.id);
|
|
189
|
+
const widgetsToRegister = widgets.filter((widget) => !alreadyRegisteredWidgetNames.includes(widget.id));
|
|
190
|
+
this.registeredWidgets = [
|
|
191
|
+
...this.registeredWidgets,
|
|
192
|
+
...widgetsToRegister.map((widget) => this.#translateLabel(widget))
|
|
193
|
+
];
|
|
151
194
|
if (buckets?.length) {
|
|
152
|
-
buckets.forEach((
|
|
195
|
+
buckets.forEach((bucket) => widgets.forEach((widget) => this._addToBucket(bucket, widget.id)));
|
|
153
196
|
}
|
|
154
197
|
}
|
|
155
|
-
#translateLabel(widget) {
|
|
156
|
-
const translatedLabel = this.translate.instant(widget.label);
|
|
157
|
-
return { ...widget, label: translatedLabel && !translatedLabel.startsWith('!missing') ? translatedLabel : widget.label };
|
|
158
|
-
}
|
|
159
|
-
_addToBucket(bucket, widgetName) {
|
|
160
|
-
if (!this.widgetBuckets[bucket])
|
|
161
|
-
this.widgetBuckets[bucket] = [];
|
|
162
|
-
this.widgetBuckets[bucket].push(widgetName);
|
|
163
|
-
}
|
|
164
198
|
removeRegisteredWidget(id) {
|
|
165
|
-
this.registeredWidgets = this.registeredWidgets.filter((
|
|
199
|
+
this.registeredWidgets = this.registeredWidgets.filter((widget) => widget.id !== id);
|
|
166
200
|
}
|
|
167
201
|
clearRegisteredWidget() {
|
|
168
202
|
this.registeredWidgets = [];
|
|
@@ -175,35 +209,55 @@ class WidgetGridRegistry {
|
|
|
175
209
|
* @returns Array of grid widgets
|
|
176
210
|
*/
|
|
177
211
|
getRegisteredWidgets(buckets) {
|
|
212
|
+
const applicable = (widget) => widget.isApplicable?.() ?? true;
|
|
178
213
|
if (buckets?.length) {
|
|
179
214
|
// buckets may contain wildcards ...
|
|
180
215
|
// grab all buckets matching the wildcards
|
|
181
216
|
const matchedBuckets = {};
|
|
182
|
-
buckets.forEach((
|
|
183
|
-
if (
|
|
217
|
+
buckets.forEach((bucket) => {
|
|
218
|
+
if (bucket.match(/[*?]/)) {
|
|
184
219
|
// bucket has a pattern
|
|
185
|
-
Object.keys(this.widgetBuckets).forEach((
|
|
186
|
-
if (this._wildcardMatch(
|
|
187
|
-
matchedBuckets[
|
|
220
|
+
Object.keys(this.widgetBuckets).forEach((widgetBucket) => {
|
|
221
|
+
if (this._wildcardMatch(widgetBucket, bucket))
|
|
222
|
+
matchedBuckets[widgetBucket] = 0;
|
|
188
223
|
});
|
|
189
224
|
}
|
|
190
225
|
else
|
|
191
|
-
matchedBuckets[
|
|
226
|
+
matchedBuckets[bucket] = 0;
|
|
192
227
|
});
|
|
193
228
|
const bucketWidgetNames = {};
|
|
194
|
-
Object.keys(matchedBuckets).forEach((
|
|
229
|
+
Object.keys(matchedBuckets).forEach((bucket) => this.widgetBuckets[bucket].forEach((widgetName) => {
|
|
195
230
|
bucketWidgetNames[widgetName] = 0;
|
|
196
231
|
}));
|
|
197
232
|
const widgetNames = Object.keys(bucketWidgetNames);
|
|
198
|
-
return this.registeredWidgets
|
|
233
|
+
return this.registeredWidgets
|
|
234
|
+
.filter((gridWidget) => widgetNames.includes(gridWidget.id) && applicable(gridWidget))
|
|
235
|
+
.map((widget) => this.#translateLabel(widget));
|
|
199
236
|
}
|
|
200
237
|
else
|
|
201
|
-
return this.registeredWidgets.map((
|
|
238
|
+
return this.registeredWidgets.filter(applicable).map((widget) => this.#translateLabel(widget));
|
|
202
239
|
}
|
|
203
240
|
_wildcardMatch(text, pattern) {
|
|
204
241
|
const regexPattern = new RegExp('^' + pattern.replace(/\?/g, '.').replace(/\*/g, '.*') + '$');
|
|
205
242
|
return regexPattern.test(text);
|
|
206
243
|
}
|
|
244
|
+
_addToBucket(bucket, widgetName) {
|
|
245
|
+
if (!this.widgetBuckets[bucket])
|
|
246
|
+
this.widgetBuckets[bucket] = [];
|
|
247
|
+
this.widgetBuckets[bucket].push(widgetName);
|
|
248
|
+
}
|
|
249
|
+
#translateLabel(widget) {
|
|
250
|
+
return {
|
|
251
|
+
...widget,
|
|
252
|
+
label: this.#tryTranslate(widget.label),
|
|
253
|
+
description: widget.description ? this.#tryTranslate(widget.description) : widget.description,
|
|
254
|
+
group: widget.group ? this.#tryTranslate(widget.group) : widget.group
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
#tryTranslate(value) {
|
|
258
|
+
const translated = this.translate.instant(value);
|
|
259
|
+
return translated && !translated.startsWith('!missing') ? translated : value;
|
|
260
|
+
}
|
|
207
261
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
208
262
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridRegistry, providedIn: 'root' });
|
|
209
263
|
}
|
|
@@ -214,48 +268,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
214
268
|
}]
|
|
215
269
|
}] });
|
|
216
270
|
|
|
217
|
-
class WidgetGridUtils {
|
|
218
|
-
static PREF_FUNCTION = 'Function';
|
|
219
|
-
static PREF_RANGEVALUE = 'RangeValue';
|
|
220
|
-
/**
|
|
221
|
-
* Takes a `WidgetGridItemConfig` and stringifies it. The challenge
|
|
222
|
-
* here is that callback functions should also be stringified.
|
|
223
|
-
* @param gridItemConfig the config object
|
|
224
|
-
*/
|
|
225
|
-
static gridConfigStringify(o) {
|
|
226
|
-
return JSON.stringify(o, (key, value) => {
|
|
227
|
-
if (typeof value === 'function') {
|
|
228
|
-
return `/${WidgetGridUtils.PREF_FUNCTION}(${value.toString()})/`;
|
|
229
|
-
}
|
|
230
|
-
return value;
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Takes a string stringified with `gridConfigStringify()` and parses it
|
|
235
|
-
* to output a proper WidgetGridItemConfig.
|
|
236
|
-
* @param stringifiedConfig stringified widget grid config
|
|
237
|
-
*/
|
|
238
|
-
static gridConfigParse(stringifiedConfig) {
|
|
239
|
-
return JSON.parse(stringifiedConfig, function (key, value) {
|
|
240
|
-
if (typeof value === 'string' &&
|
|
241
|
-
value.startsWith(`/${WidgetGridUtils.PREF_FUNCTION}(`) &&
|
|
242
|
-
// value.startsWith('/Function(') &&
|
|
243
|
-
value.endsWith(')/')) {
|
|
244
|
-
value = value.substring(WidgetGridUtils.PREF_FUNCTION.length + 2, value.length - 2);
|
|
245
|
-
return (0, eval)('(' + value + ')');
|
|
246
|
-
}
|
|
247
|
-
return value;
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
static uuid() {
|
|
251
|
-
return WidgetGridUtils._p8() + WidgetGridUtils._p8(true) + WidgetGridUtils._p8(true) + WidgetGridUtils._p8();
|
|
252
|
-
}
|
|
253
|
-
static _p8(s) {
|
|
254
|
-
const p = (Math.random().toString(16) + '000000000').substr(2, 8);
|
|
255
|
-
return s ? `-${p.substr(0, 4)}-${p.substr(4, 4)}` : p;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
271
|
/**
|
|
260
272
|
* Service managing a widget grid. Also includes a set of labels that will be
|
|
261
273
|
* used by the grid and its widgets. Default labels are provided out of the
|
|
@@ -265,45 +277,48 @@ class WidgetGridUtils {
|
|
|
265
277
|
* consider using those labels as well.
|
|
266
278
|
*/
|
|
267
279
|
class WidgetGridService {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
280
|
+
//#region Dependencies
|
|
281
|
+
#widgetGridRegistry = inject(WidgetGridRegistry);
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region Class properties
|
|
284
|
+
#widgetGrid = [];
|
|
285
|
+
#widgetGridSource = new ReplaySubject();
|
|
286
|
+
widgetGrid$ = this.#widgetGridSource.asObservable();
|
|
287
|
+
#widgetGridUpdateSource = new Subject();
|
|
273
288
|
/**
|
|
274
289
|
* Emitted when the widget grid has been updated
|
|
275
290
|
*/
|
|
276
|
-
widgetGridUpdate$ = this
|
|
291
|
+
widgetGridUpdate$ = this.#widgetGridUpdateSource.asObservable();
|
|
277
292
|
addItemSize = {
|
|
278
293
|
rows: 2,
|
|
279
|
-
cols: 3
|
|
294
|
+
cols: 3
|
|
280
295
|
};
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region Public methods
|
|
284
298
|
setWidgetGrid(gridItemConfig) {
|
|
285
299
|
if (gridItemConfig) {
|
|
286
300
|
// create actual widget grid from widget grid config
|
|
287
|
-
this
|
|
288
|
-
const cmp = this
|
|
289
|
-
const
|
|
301
|
+
this.#widgetGrid = [...gridItemConfig].map((gic) => {
|
|
302
|
+
const cmp = this.#widgetGridRegistry.getWidgetComponent(gic.widgetName);
|
|
303
|
+
const gridItem = {
|
|
290
304
|
...gic,
|
|
291
305
|
widget: cmp,
|
|
292
306
|
widgetConfigMap: {
|
|
293
307
|
widgetConfig: cmp === NoopComponent
|
|
294
308
|
? {
|
|
295
|
-
widgetName: gic.widgetName
|
|
309
|
+
widgetName: gic.widgetName
|
|
296
310
|
}
|
|
297
|
-
: gic.widgetConfig
|
|
311
|
+
: gic.widgetConfig
|
|
298
312
|
},
|
|
313
|
+
isApplicable: this.#widgetGridRegistry.isWidgetApplicable(gic.widgetName)
|
|
299
314
|
};
|
|
300
|
-
return
|
|
315
|
+
return gridItem;
|
|
301
316
|
});
|
|
302
317
|
}
|
|
303
318
|
else {
|
|
304
|
-
this
|
|
319
|
+
this.#widgetGrid = [];
|
|
305
320
|
}
|
|
306
|
-
this
|
|
321
|
+
this.#widgetGridSource.next(this.#widgetGrid);
|
|
307
322
|
}
|
|
308
323
|
/**
|
|
309
324
|
* Update config of an existing widget
|
|
@@ -311,29 +326,30 @@ class WidgetGridService {
|
|
|
311
326
|
* @param setupWidgetConfig The updated configuration for that widget
|
|
312
327
|
*/
|
|
313
328
|
updateWidget(widgetId, setupWidgetConfig) {
|
|
314
|
-
const gridWidget = this
|
|
329
|
+
const gridWidget = this.#widgetGrid.find((widget) => widget.id === widgetId);
|
|
315
330
|
if (gridWidget && setupWidgetConfig) {
|
|
316
331
|
gridWidget.widgetConfig = setupWidgetConfig;
|
|
317
332
|
gridWidget.widgetConfigMap = { widgetConfig: setupWidgetConfig };
|
|
318
333
|
}
|
|
319
|
-
this
|
|
320
|
-
this
|
|
334
|
+
this.#widgetGridSource.next(this.#widgetGrid);
|
|
335
|
+
this.#widgetGridUpdateSource.next(widgetId);
|
|
321
336
|
}
|
|
322
337
|
replaceWidget(widgetId, widgetName, setupWidgetConfig) {
|
|
323
|
-
const gridWidgetIndex = this
|
|
338
|
+
const gridWidgetIndex = this.#widgetGrid.findIndex((widget) => widget.id === widgetId);
|
|
324
339
|
if (gridWidgetIndex !== -1) {
|
|
325
|
-
const
|
|
326
|
-
...this
|
|
340
|
+
const widget = {
|
|
341
|
+
...this.#widgetGrid[gridWidgetIndex],
|
|
327
342
|
...{
|
|
328
343
|
widgetName,
|
|
329
344
|
widgetConfig: setupWidgetConfig || {},
|
|
330
|
-
widget: this
|
|
345
|
+
widget: this.#widgetGridRegistry.getWidgetComponent(widgetName),
|
|
331
346
|
widgetConfigMap: { widgetConfig: setupWidgetConfig || {} },
|
|
332
|
-
|
|
347
|
+
isApplicable: this.#widgetGridRegistry.isWidgetApplicable(widgetName)
|
|
348
|
+
}
|
|
333
349
|
};
|
|
334
|
-
this
|
|
335
|
-
this
|
|
336
|
-
this
|
|
350
|
+
this.#widgetGrid[gridWidgetIndex] = widget;
|
|
351
|
+
this.#widgetGridSource.next(this.#widgetGrid);
|
|
352
|
+
this.#widgetGridUpdateSource.next(widgetId);
|
|
337
353
|
}
|
|
338
354
|
}
|
|
339
355
|
/**
|
|
@@ -346,101 +362,90 @@ class WidgetGridService {
|
|
|
346
362
|
id: WidgetGridUtils.uuid(),
|
|
347
363
|
widgetName,
|
|
348
364
|
widgetConfig: setupWidgetConfig,
|
|
349
|
-
widget: this
|
|
365
|
+
widget: this.#widgetGridRegistry.getWidgetComponent(widgetName),
|
|
350
366
|
widgetConfigMap: { widgetConfig: setupWidgetConfig },
|
|
367
|
+
isApplicable: this.#widgetGridRegistry.isWidgetApplicable(widgetName),
|
|
351
368
|
x: 0,
|
|
352
369
|
y: 0,
|
|
353
370
|
rows: this.addItemSize.rows,
|
|
354
|
-
cols: this.addItemSize.cols
|
|
371
|
+
cols: this.addItemSize.cols
|
|
355
372
|
};
|
|
356
|
-
this
|
|
357
|
-
this
|
|
358
|
-
this
|
|
373
|
+
this.#widgetGrid.push(gridWidget);
|
|
374
|
+
this.#widgetGridSource.next(this.#widgetGrid);
|
|
375
|
+
this.#widgetGridUpdateSource.next(gridWidget.id);
|
|
359
376
|
}
|
|
360
377
|
removeWidget(gridItemId) {
|
|
361
|
-
const idx = this
|
|
362
|
-
this
|
|
363
|
-
this
|
|
364
|
-
this
|
|
378
|
+
const idx = this.#widgetGrid.findIndex((widget) => widget.id === gridItemId);
|
|
379
|
+
this.#widgetGrid.splice(idx, 1);
|
|
380
|
+
this.#widgetGridSource.next(this.#widgetGrid);
|
|
381
|
+
this.#widgetGridUpdateSource.next(gridItemId);
|
|
365
382
|
}
|
|
366
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridService, deps: [
|
|
383
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
367
384
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridService });
|
|
368
385
|
}
|
|
369
386
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridService, decorators: [{
|
|
370
387
|
type: Injectable
|
|
371
|
-
}]
|
|
388
|
+
}] });
|
|
372
389
|
|
|
373
390
|
class WidgetPickerComponent {
|
|
391
|
+
//#region Dependencies
|
|
374
392
|
#widgetGridRegistry = inject(WidgetGridRegistry);
|
|
375
393
|
#widgetGridService = inject(WidgetGridService);
|
|
394
|
+
translate = inject(TranslateService);
|
|
376
395
|
#dialogData = inject(MAT_DIALOG_DATA);
|
|
377
396
|
#dialogRef = inject((MatDialogRef));
|
|
378
|
-
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region Angular stuff
|
|
399
|
+
pickerData = input(this.#dialogData.pickerData, ...(ngDevMode ? [{ debugName: "pickerData" }] : /* istanbul ignore next */ []));
|
|
400
|
+
/**
|
|
401
|
+
* Collection of buckets to load available widgets from. Wildcards are also posssible:
|
|
402
|
+
* `[buckets]="['app.default', '*.public.*', 'app.no?.widgets']"`
|
|
403
|
+
*
|
|
404
|
+
* `*` represents any character 0-n times
|
|
405
|
+
* `?` represents exactly one character
|
|
406
|
+
*/
|
|
407
|
+
buckets = input(this.#dialogData.buckets, ...(ngDevMode ? [{ debugName: "buckets" }] : /* istanbul ignore next */ []));
|
|
408
|
+
picked = output();
|
|
409
|
+
canceled = output();
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region Properties
|
|
412
|
+
#DEFAULT_GROUP_KEY = 'yuv.widget-grid.widget-picker.group.default';
|
|
379
413
|
widgetId;
|
|
380
414
|
selectedWidget;
|
|
415
|
+
// ADR: InputsType (not AttributesMap) - bound via ndcDynamicInputs in the template,
|
|
416
|
+
// so values are component inputs (unknown), not HTML attribute strings.
|
|
381
417
|
widgetConfigMap = {};
|
|
382
418
|
setupWidgetConfig;
|
|
383
419
|
widgetConfigDirty = false;
|
|
384
420
|
widgetConfigState;
|
|
385
421
|
registeredWidgets = [];
|
|
422
|
+
groupedWidgets = [];
|
|
386
423
|
setupWidgetDynamicOutputs = {
|
|
387
424
|
widgetConfigChange: (widgetConfig) => {
|
|
388
|
-
this
|
|
425
|
+
this.#handleSetupConfigChange(widgetConfig);
|
|
389
426
|
},
|
|
390
|
-
widgetConfigStateChange: (
|
|
391
|
-
this.widgetConfigState =
|
|
427
|
+
widgetConfigStateChange: (widgetConfigState) => {
|
|
428
|
+
this.widgetConfigState = widgetConfigState;
|
|
392
429
|
}
|
|
393
430
|
};
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (pd) {
|
|
398
|
-
const gridWidget = this.#widgetGridRegistry
|
|
399
|
-
.getRegisteredWidgets()
|
|
400
|
-
.find((w) => w.id === pd.widgetName);
|
|
401
|
-
this.selectedWidget = gridWidget?.setupComponent ? gridWidget : undefined;
|
|
402
|
-
this.widgetConfigMap = pd.widgetConfigMap || {};
|
|
403
|
-
this.widgetId = pd.widgetId;
|
|
404
|
-
}
|
|
405
|
-
}, ...(ngDevMode ? [{ debugName: "#pickerDataEffect" }] : /* istanbul ignore next */ []));
|
|
406
|
-
/**
|
|
407
|
-
* Collection of buckets to load available widgets from. Wildcards are also posssible:
|
|
408
|
-
* `[buckets]="['app.default', '*.public.*', 'app.no?.widgets']"`
|
|
409
|
-
*
|
|
410
|
-
* `*` represents any character 0-n times
|
|
411
|
-
* `?` represents exactly one character
|
|
412
|
-
*/
|
|
413
|
-
buckets = input(this.#dialogData.buckets, ...(ngDevMode ? [{ debugName: "buckets" }] : /* istanbul ignore next */ []));
|
|
414
|
-
picked = output();
|
|
415
|
-
canceled = output();
|
|
431
|
+
#expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "#expandedGroups" }] : /* istanbul ignore next */ []));
|
|
432
|
+
expandedGroups = this.#expandedGroups.asReadonly();
|
|
433
|
+
//#endregion
|
|
416
434
|
constructor() {
|
|
417
|
-
this
|
|
418
|
-
if (widgets.length)
|
|
419
|
-
this.pick(widgets[0]);
|
|
420
|
-
});
|
|
435
|
+
effect(this.#pickerDataEffect);
|
|
421
436
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
if (this.widgetId) {
|
|
429
|
-
// update existing widget
|
|
430
|
-
this.#widgetGridService.replaceWidget(this.widgetId, widget.id);
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
this.#widgetGridService.addWidget(widget.id);
|
|
434
|
-
}
|
|
435
|
-
this.picked.emit();
|
|
436
|
-
this.#dialogRef.close();
|
|
437
|
-
}
|
|
437
|
+
//#region Lifecycle hooks
|
|
438
|
+
ngOnInit() {
|
|
439
|
+
this.#initRegisteredWidgets();
|
|
440
|
+
this.#initGroupedWidgets();
|
|
438
441
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region UI Methods
|
|
444
|
+
onSetupComponentCancel() {
|
|
445
|
+
this.selectedWidget = undefined;
|
|
446
|
+
this.setupWidgetConfig = undefined;
|
|
442
447
|
}
|
|
443
|
-
|
|
448
|
+
onSetupComponentSave() {
|
|
444
449
|
// TODO: save/emit config
|
|
445
450
|
if (this.widgetId && this.selectedWidget) {
|
|
446
451
|
if (this.pickerData()?.widgetName !== this.selectedWidget.id) {
|
|
@@ -451,27 +456,86 @@ class WidgetPickerComponent {
|
|
|
451
456
|
}
|
|
452
457
|
}
|
|
453
458
|
else {
|
|
459
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
454
460
|
this.#widgetGridService.addWidget(this.selectedWidget.id, this.setupWidgetConfig);
|
|
455
461
|
}
|
|
456
462
|
this.picked.emit();
|
|
457
463
|
this.#dialogRef.close();
|
|
458
464
|
}
|
|
459
|
-
|
|
460
|
-
this.reset();
|
|
461
|
-
}
|
|
462
|
-
cancel() {
|
|
465
|
+
onCancel() {
|
|
463
466
|
this.canceled.emit();
|
|
464
467
|
this.#dialogRef.close();
|
|
465
468
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
+
onToggleGroup(group) {
|
|
470
|
+
const next = new Set(this.#expandedGroups());
|
|
471
|
+
if (next.has(group))
|
|
472
|
+
next.delete(group);
|
|
473
|
+
else
|
|
474
|
+
next.add(group);
|
|
475
|
+
this.#expandedGroups.set(next);
|
|
469
476
|
}
|
|
470
|
-
|
|
477
|
+
onPick(widget) {
|
|
478
|
+
if (widget?.setupComponent) {
|
|
479
|
+
// if the selected widget has a setup component, we'll continue ...
|
|
480
|
+
this.selectedWidget = widget?.setupComponent ? widget : undefined;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
if (this.widgetId) {
|
|
484
|
+
// update existing widget
|
|
485
|
+
this.#widgetGridService.replaceWidget(this.widgetId, widget.id);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
this.#widgetGridService.addWidget(widget.id);
|
|
489
|
+
}
|
|
490
|
+
this.picked.emit();
|
|
491
|
+
this.#dialogRef.close();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region Init
|
|
496
|
+
#initRegisteredWidgets() {
|
|
471
497
|
this.registeredWidgets = this.#widgetGridRegistry.getRegisteredWidgets(this.buckets());
|
|
472
498
|
}
|
|
499
|
+
#initGroupedWidgets() {
|
|
500
|
+
this.groupedWidgets = this.#groupWidgets(this.registeredWidgets);
|
|
501
|
+
}
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region Utilities
|
|
504
|
+
#handleSetupConfigChange(setupConfig) {
|
|
505
|
+
this.widgetConfigDirty = !!setupConfig;
|
|
506
|
+
this.setupWidgetConfig = setupConfig;
|
|
507
|
+
}
|
|
508
|
+
#groupWidgets(widgets) {
|
|
509
|
+
const defaultGroupLabel = this.translate.instant(this.#DEFAULT_GROUP_KEY);
|
|
510
|
+
const order = [];
|
|
511
|
+
const buckets = new Map();
|
|
512
|
+
widgets.forEach((widget) => {
|
|
513
|
+
const key = widget.group || defaultGroupLabel;
|
|
514
|
+
let bucket = buckets.get(key);
|
|
515
|
+
if (!bucket) {
|
|
516
|
+
bucket = [];
|
|
517
|
+
buckets.set(key, bucket);
|
|
518
|
+
order.push(key);
|
|
519
|
+
}
|
|
520
|
+
bucket.push(widget);
|
|
521
|
+
});
|
|
522
|
+
return order.map((group) => ({ group, widgets: buckets.get(group) ?? [] }));
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region Effects
|
|
526
|
+
#pickerDataEffect = () => {
|
|
527
|
+
const pickerData = this.pickerData();
|
|
528
|
+
if (pickerData) {
|
|
529
|
+
const gridWidget = this.#widgetGridRegistry
|
|
530
|
+
.getRegisteredWidgets()
|
|
531
|
+
.find((widget) => widget.id === pickerData.widgetName);
|
|
532
|
+
this.selectedWidget = gridWidget?.setupComponent ? gridWidget : undefined;
|
|
533
|
+
this.widgetConfigMap = pickerData.widgetConfigMap || {};
|
|
534
|
+
this.widgetId = pickerData.widgetId;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
473
537
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
474
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: WidgetPickerComponent, isStandalone: true, selector: "yuv-widget-picker", inputs: { pickerData: { classPropertyName: "pickerData", publicName: "pickerData", isSignal: true, isRequired: false, transformFunction: null }, buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { picked: "picked", canceled: "canceled" }, ngImport: i0, template: "<yuv-dialog [
|
|
538
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: WidgetPickerComponent, isStandalone: true, selector: "yuv-widget-picker", inputs: { pickerData: { classPropertyName: "pickerData", publicName: "pickerData", isSignal: true, isRequired: false, transformFunction: null }, buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { picked: "picked", canceled: "canceled" }, ngImport: i0, template: "<yuv-dialog [headertitle]=\"selectedWidget ? selectedWidget.label : ('yuv.widget-grid.widget-picker.title' | translate)\">\n <!-- <header class=\"dark\" [ngClass]=\"{ listing: !selectedWidget }\">\n @if (selectedWidget) {\n <button mat-icon-button (click)=\"reset()\"><mat-icon>back</mat-icon></button>\n }\n <div class=\"title\">{{ selectedWidget ? selectedWidget.label : ('yuv.widget-grid.widget-picker.title' | translate) }}</div>\n <button mat-icon-button (click)=\"cancel()\"><mat-icon>clear</mat-icon></button>\n </header> -->\n\n <main [class]=\"!selectedWidget ? 'widget-listing' : 'widget-setup'\">\n @if (!selectedWidget) {\n <ul class=\"groups\" role=\"list\">\n @for (g of groupedWidgets; track g.group) {\n @let expanded = expandedGroups().has(g.group);\n <li class=\"group\" [class.group--expanded]=\"expanded\">\n <button type=\"button\" class=\"group-header\" [attr.aria-expanded]=\"expanded\" (click)=\"onToggleGroup(g.group)\">\n <mat-icon class=\"group-chevron ymt-icon--size-s\">\n {{ expanded ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n <span class=\"group-label\">{{ g.group }}</span>\n <span class=\"group-count\">{{ g.widgets.length }}</span>\n </button>\n @if (expanded) {\n <ul class=\"widgets\" role=\"list\">\n @for (w of g.widgets; track w.id) {\n <li>\n <button type=\"button\" class=\"widget-item\" (click)=\"onPick(w)\">\n <span class=\"widget-title\">{{ w.label }}</span>\n @if (w.description) {\n <span class=\"widget-description\">{{ w.description }}</span>\n }\n </button>\n </li>\n }\n </ul>\n }\n </li>\n } @empty {\n <div class=\"empty\">\n {{ 'yuv.widget-grid.widget-picker.empty' | translate }}\n </div>\n }\n </ul>\n } @else {\n <div class=\"component\">\n <ng-container\n *ngComponentOutlet=\"\n selectedWidget!.setupComponent!;\n ndcDynamicInputs: widgetConfigMap;\n ndcDynamicOutputs: setupWidgetDynamicOutputs\n \"\n />\n </div>\n }\n </main>\n <footer>\n @if (selectedWidget) {\n <button ymtButton=\"secondary\" (click)=\"onSetupComponentCancel()\">\n {{ 'yuv.widget-grid.button.cancel' | translate }}\n </button>\n <button\n ymtButton=\"primary\"\n [disabled]=\"!widgetConfigDirty || widgetConfigState === 'INVALID'\"\n (click)=\"onSetupComponentSave()\"\n >\n {{ 'yuv.widget-grid.button.save' | translate }}\n </button>\n } @else {\n <button ymtButton=\"secondary\" (click)=\"onCancel()\">{{ 'yuv.widget-grid.button.cancel' | translate }}</button>\n }\n </footer>\n</yuv-dialog>\n", styles: [":host .empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}:host main.widget-setup{height:100%;display:grid;grid-template-rows:1fr auto;grid-template-columns:1fr;grid-template-areas:\"component\" \"buttons\"}:host main.widget-listing .groups{list-style:none;margin:0;padding:0}:host main.widget-listing .group{border-bottom:1px solid var(--panel-divider-color)}:host main.widget-listing .group-header{display:flex;align-items:center;gap:var(--ymt-spacing-s, 8px);width:100%;padding:var(--app-pane-padding, 12px);background:transparent;border:0;cursor:pointer;color:inherit;font:inherit;text-align:start}:host main.widget-listing .group-header:hover{background-color:var(--item-focus-background-color)}:host main.widget-listing .group-header .group-label{flex:1;font-weight:500}:host main.widget-listing .group-header .group-count{opacity:.6;font-size:.85em}:host main.widget-listing .widgets{list-style:none;margin:0;padding:0 0 var(--ymt-spacing-xs, 4px)}:host main.widget-listing .widget-item{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;padding:var(--ymt-spacing-s, 8px) calc(var(--app-pane-padding, 12px) + 24px);background:transparent;border:0;cursor:pointer;color:inherit;font:inherit;text-align:start}:host main.widget-listing .widget-item:hover{background-color:var(--item-focus-background-color)}:host main.widget-listing .widget-title{font-weight:400}:host main.widget-listing .widget-description{color:var(--ymt-text-color-subtle);font-size:.85em;white-space:normal}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DialogComponent, selector: "yuv-dialog", inputs: ["headertitle", "headertitel"] }, { kind: "directive", type: YmtButtonDirective, selector: "button[ymtButton], a[ymtButton]", inputs: ["ymtButton", "disabled", "aria-disabled", "disableRipple", "disabledInteractive", "button-size"] }, { kind: "ngmodule", type: DynamicIoModule }, { kind: "directive", type: i3$1.ComponentOutletInjectorDirective, selector: "[ngComponentOutlet]", exportAs: ["ndcComponentOutletInjector"] }, { kind: "directive", type: i3$1.ComponentOutletIoDirective, selector: "[ngComponentOutletNdcDynamicInputs],[ngComponentOutletNdcDynamicOutputs]", inputs: ["ngComponentOutletNdcDynamicInputs", "ngComponentOutletNdcDynamicOutputs"], exportAs: ["ndcDynamicIo"] }, { kind: "ngmodule", type: SignalComponentIoModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
475
539
|
}
|
|
476
540
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetPickerComponent, decorators: [{
|
|
477
541
|
type: Component,
|
|
@@ -482,62 +546,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
482
546
|
MatIconModule,
|
|
483
547
|
DialogComponent,
|
|
484
548
|
YmtButtonDirective,
|
|
485
|
-
MatListModule,
|
|
486
549
|
DynamicIoModule,
|
|
487
|
-
ReactiveFormsModule,
|
|
488
550
|
SignalComponentIoModule
|
|
489
|
-
], template: "<yuv-dialog [
|
|
551
|
+
], template: "<yuv-dialog [headertitle]=\"selectedWidget ? selectedWidget.label : ('yuv.widget-grid.widget-picker.title' | translate)\">\n <!-- <header class=\"dark\" [ngClass]=\"{ listing: !selectedWidget }\">\n @if (selectedWidget) {\n <button mat-icon-button (click)=\"reset()\"><mat-icon>back</mat-icon></button>\n }\n <div class=\"title\">{{ selectedWidget ? selectedWidget.label : ('yuv.widget-grid.widget-picker.title' | translate) }}</div>\n <button mat-icon-button (click)=\"cancel()\"><mat-icon>clear</mat-icon></button>\n </header> -->\n\n <main [class]=\"!selectedWidget ? 'widget-listing' : 'widget-setup'\">\n @if (!selectedWidget) {\n <ul class=\"groups\" role=\"list\">\n @for (g of groupedWidgets; track g.group) {\n @let expanded = expandedGroups().has(g.group);\n <li class=\"group\" [class.group--expanded]=\"expanded\">\n <button type=\"button\" class=\"group-header\" [attr.aria-expanded]=\"expanded\" (click)=\"onToggleGroup(g.group)\">\n <mat-icon class=\"group-chevron ymt-icon--size-s\">\n {{ expanded ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n <span class=\"group-label\">{{ g.group }}</span>\n <span class=\"group-count\">{{ g.widgets.length }}</span>\n </button>\n @if (expanded) {\n <ul class=\"widgets\" role=\"list\">\n @for (w of g.widgets; track w.id) {\n <li>\n <button type=\"button\" class=\"widget-item\" (click)=\"onPick(w)\">\n <span class=\"widget-title\">{{ w.label }}</span>\n @if (w.description) {\n <span class=\"widget-description\">{{ w.description }}</span>\n }\n </button>\n </li>\n }\n </ul>\n }\n </li>\n } @empty {\n <div class=\"empty\">\n {{ 'yuv.widget-grid.widget-picker.empty' | translate }}\n </div>\n }\n </ul>\n } @else {\n <div class=\"component\">\n <ng-container\n *ngComponentOutlet=\"\n selectedWidget!.setupComponent!;\n ndcDynamicInputs: widgetConfigMap;\n ndcDynamicOutputs: setupWidgetDynamicOutputs\n \"\n />\n </div>\n }\n </main>\n <footer>\n @if (selectedWidget) {\n <button ymtButton=\"secondary\" (click)=\"onSetupComponentCancel()\">\n {{ 'yuv.widget-grid.button.cancel' | translate }}\n </button>\n <button\n ymtButton=\"primary\"\n [disabled]=\"!widgetConfigDirty || widgetConfigState === 'INVALID'\"\n (click)=\"onSetupComponentSave()\"\n >\n {{ 'yuv.widget-grid.button.save' | translate }}\n </button>\n } @else {\n <button ymtButton=\"secondary\" (click)=\"onCancel()\">{{ 'yuv.widget-grid.button.cancel' | translate }}</button>\n }\n </footer>\n</yuv-dialog>\n", styles: [":host .empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}:host main.widget-setup{height:100%;display:grid;grid-template-rows:1fr auto;grid-template-columns:1fr;grid-template-areas:\"component\" \"buttons\"}:host main.widget-listing .groups{list-style:none;margin:0;padding:0}:host main.widget-listing .group{border-bottom:1px solid var(--panel-divider-color)}:host main.widget-listing .group-header{display:flex;align-items:center;gap:var(--ymt-spacing-s, 8px);width:100%;padding:var(--app-pane-padding, 12px);background:transparent;border:0;cursor:pointer;color:inherit;font:inherit;text-align:start}:host main.widget-listing .group-header:hover{background-color:var(--item-focus-background-color)}:host main.widget-listing .group-header .group-label{flex:1;font-weight:500}:host main.widget-listing .group-header .group-count{opacity:.6;font-size:.85em}:host main.widget-listing .widgets{list-style:none;margin:0;padding:0 0 var(--ymt-spacing-xs, 4px)}:host main.widget-listing .widget-item{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;padding:var(--ymt-spacing-s, 8px) calc(var(--app-pane-padding, 12px) + 24px);background:transparent;border:0;cursor:pointer;color:inherit;font:inherit;text-align:start}:host main.widget-listing .widget-item:hover{background-color:var(--item-focus-background-color)}:host main.widget-listing .widget-title{font-weight:400}:host main.widget-listing .widget-description{color:var(--ymt-text-color-subtle);font-size:.85em;white-space:normal}\n"] }]
|
|
490
552
|
}], ctorParameters: () => [], propDecorators: { pickerData: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickerData", required: false }] }], buckets: [{ type: i0.Input, args: [{ isSignal: true, alias: "buckets", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], canceled: [{ type: i0.Output, args: ["canceled"] }] } });
|
|
491
553
|
|
|
554
|
+
class WidgetGridEventService {
|
|
555
|
+
//#region Properties
|
|
556
|
+
#widgetGridEventSource = new ReplaySubject();
|
|
557
|
+
widgetEvents$ = this.#widgetGridEventSource.asObservable();
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region Public methods
|
|
560
|
+
trigger(evt) {
|
|
561
|
+
this.#widgetGridEventSource.next(evt);
|
|
562
|
+
}
|
|
563
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
564
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridEventService, providedIn: 'root' });
|
|
565
|
+
}
|
|
566
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WidgetGridEventService, decorators: [{
|
|
567
|
+
type: Injectable,
|
|
568
|
+
args: [{
|
|
569
|
+
providedIn: 'root'
|
|
570
|
+
}]
|
|
571
|
+
}] });
|
|
572
|
+
|
|
492
573
|
class YuvWidgetGridComponent {
|
|
574
|
+
//#region Dependencies
|
|
493
575
|
#dialog = inject(MatDialog);
|
|
494
576
|
#widgetGridService = inject(WidgetGridService);
|
|
495
577
|
#widgetGridEventService = inject(WidgetGridEventService);
|
|
578
|
+
#destroyRef = inject(DestroyRef);
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region Angular stuff
|
|
496
581
|
gridster = viewChild(Gridster, ...(ngDevMode ? [{ debugName: "gridster" }] : /* istanbul ignore next */ []));
|
|
497
582
|
widgetPicker = viewChild.required('widgetPicker', { read: TemplateRef });
|
|
498
|
-
options = {
|
|
499
|
-
gridType: GridType.Fit,
|
|
500
|
-
displayGrid: DisplayGrid.None,
|
|
501
|
-
pushItems: false,
|
|
502
|
-
outerMargin: false,
|
|
503
|
-
swap: false,
|
|
504
|
-
draggable: {
|
|
505
|
-
enabled: false,
|
|
506
|
-
ignoreContent: true,
|
|
507
|
-
dragHandleClass: 'dragHandle'
|
|
508
|
-
},
|
|
509
|
-
resizable: {
|
|
510
|
-
enabled: false
|
|
511
|
-
},
|
|
512
|
-
itemChangeCallback: (item, itemComponent) => {
|
|
513
|
-
this.emitChange();
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
583
|
gridConfig = input(...(ngDevMode ? [undefined, { debugName: "gridConfig" }] : /* istanbul ignore next */ []));
|
|
517
|
-
#gridConfigEffect = effect(() => {
|
|
518
|
-
const cfg = this.gridConfig();
|
|
519
|
-
if (cfg?.rows) {
|
|
520
|
-
this.options.minRows = cfg.rows;
|
|
521
|
-
this.options.maxRows = cfg.rows;
|
|
522
|
-
}
|
|
523
|
-
if (cfg?.columns) {
|
|
524
|
-
this.options.minCols = cfg.columns;
|
|
525
|
-
this.options.maxCols = cfg.columns;
|
|
526
|
-
}
|
|
527
|
-
if (cfg?.gap) {
|
|
528
|
-
this.options.margin = cfg.gap;
|
|
529
|
-
}
|
|
530
|
-
if (cfg?.newItemWidth || cfg?.newItemHeight) {
|
|
531
|
-
this.#widgetGridService.addItemSize = {
|
|
532
|
-
cols: cfg?.newItemWidth || -1, // default value -1 means the grid will autoposition the new item
|
|
533
|
-
rows: cfg?.newItemHeight || -1 // default value -1 means the grid will autoposition the new item
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
if (cfg?.gridType) {
|
|
537
|
-
this.options.gridType = cfg.gridType;
|
|
538
|
-
}
|
|
539
|
-
this.gridster()?.api.calculateLayout();
|
|
540
|
-
}, ...(ngDevMode ? [{ debugName: "#gridConfigEffect" }] : /* istanbul ignore next */ []));
|
|
541
584
|
_editMode = signal(false, ...(ngDevMode ? [{ debugName: "_editMode" }] : /* istanbul ignore next */ []));
|
|
542
585
|
/**
|
|
543
586
|
* Whether or not to enable edit mode. In edit mode controls
|
|
@@ -545,23 +588,9 @@ class YuvWidgetGridComponent {
|
|
|
545
588
|
* This mode also enables positioning and resizing of the tiles.
|
|
546
589
|
*/
|
|
547
590
|
editMode = input(false, ...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
|
|
548
|
-
#editModeEffect = effect(() => {
|
|
549
|
-
const e = this.editMode();
|
|
550
|
-
this._editMode.set(e);
|
|
551
|
-
this.options.draggable.enabled = !!e;
|
|
552
|
-
this.options.resizable.enabled = !!e;
|
|
553
|
-
this.options.displayGrid = e ? DisplayGrid.Always : DisplayGrid.None;
|
|
554
|
-
// set timeout to ensure that the grid has been redrawn before notifying the widgets about the change
|
|
555
|
-
setTimeout(() => {
|
|
556
|
-
this.gridster()?.api.calculateLayout();
|
|
557
|
-
}, 0);
|
|
558
|
-
}, ...(ngDevMode ? [{ debugName: "#editModeEffect" }] : /* istanbul ignore next */ []));
|
|
559
591
|
gridItemConfig = input(undefined, ...(ngDevMode ? [{ debugName: "gridItemConfig" }] : /* istanbul ignore next */ []));
|
|
560
|
-
#gridItemConfigEffect = effect(() => {
|
|
561
|
-
this.#widgetGridService.setWidgetGrid(this.gridItemConfig());
|
|
562
|
-
}, ...(ngDevMode ? [{ debugName: "#gridItemConfigEffect" }] : /* istanbul ignore next */ []));
|
|
563
592
|
/**
|
|
564
|
-
* Collection of buckets to load available widgets from. Wildcards are also
|
|
593
|
+
* Collection of buckets to load available widgets from. Wildcards are also possible:
|
|
565
594
|
* `[buckets]="['app.default', '*.public.*', 'app.no?.widgets']"`
|
|
566
595
|
*
|
|
567
596
|
* `*` represents any character 0-n times
|
|
@@ -578,20 +607,48 @@ class YuvWidgetGridComponent {
|
|
|
578
607
|
* Emitted when the widget picker is opened or closed in edit mode
|
|
579
608
|
*/
|
|
580
609
|
widgetPickerOpen = output();
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region Class properties
|
|
612
|
+
/**
|
|
613
|
+
* Held as a signal so gridster's `options` InputSignal sees a new reference
|
|
614
|
+
* whenever edit mode or grid config changes. Mutating the inner object would
|
|
615
|
+
* keep the same reference and gridster's `$options` computed would never
|
|
616
|
+
* recompute — leaving draggable/resizable stuck at their initial `false`.
|
|
617
|
+
*/
|
|
618
|
+
options = signal({
|
|
619
|
+
gridType: GridType.Fit,
|
|
620
|
+
displayGrid: DisplayGrid.None,
|
|
621
|
+
pushItems: false,
|
|
622
|
+
outerMargin: false,
|
|
623
|
+
swap: false,
|
|
624
|
+
draggable: {
|
|
625
|
+
enabled: false,
|
|
626
|
+
ignoreContent: true,
|
|
627
|
+
dragHandleClass: 'dragHandle'
|
|
628
|
+
},
|
|
629
|
+
resizable: {
|
|
630
|
+
enabled: false
|
|
631
|
+
},
|
|
632
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
633
|
+
itemChangeCallback: (item, itemComponent) => {
|
|
634
|
+
this.#emitChange();
|
|
635
|
+
}
|
|
636
|
+
}, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
581
637
|
widgetGrid = [];
|
|
582
638
|
widgetPickerData;
|
|
639
|
+
//#endregion
|
|
583
640
|
constructor() {
|
|
584
|
-
this.#
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
this.#widgetGridEventService.widgetEvents$.pipe(takeUntilDestroyed()).subscribe((evt) => {
|
|
588
|
-
this.gridItemEvent.emit(evt);
|
|
589
|
-
});
|
|
590
|
-
this.#widgetGridService.widgetGridUpdate$.pipe(takeUntilDestroyed()).subscribe((_) => {
|
|
591
|
-
this.emitChange();
|
|
592
|
-
});
|
|
641
|
+
effect(this.#gridItemConfigEffect);
|
|
642
|
+
effect(this.#editModeEffect);
|
|
643
|
+
effect(this.#gridConfigEffect);
|
|
593
644
|
}
|
|
594
|
-
|
|
645
|
+
//#region Lifecycle Hooks
|
|
646
|
+
ngOnInit() {
|
|
647
|
+
this.#respondToWidgetServiceEvents();
|
|
648
|
+
}
|
|
649
|
+
//#endregion
|
|
650
|
+
// #region UI Methods
|
|
651
|
+
onOpenWidgetPicker(item) {
|
|
595
652
|
if (item) {
|
|
596
653
|
// open setup component for selected grid item
|
|
597
654
|
this.widgetPickerData = {
|
|
@@ -618,27 +675,98 @@ class YuvWidgetGridComponent {
|
|
|
618
675
|
* Removes a widget from the grid
|
|
619
676
|
* @param item The widget to be removed
|
|
620
677
|
*/
|
|
621
|
-
|
|
678
|
+
onRemoveItem(item) {
|
|
622
679
|
this.#widgetGridService.removeWidget(item.id);
|
|
623
680
|
}
|
|
624
681
|
/**
|
|
625
682
|
* Add a new widget to the grid by opening the widget picker
|
|
626
683
|
*/
|
|
627
|
-
|
|
628
|
-
this.
|
|
684
|
+
onAddItem() {
|
|
685
|
+
this.onOpenWidgetPicker();
|
|
629
686
|
}
|
|
630
|
-
|
|
687
|
+
//#endregion
|
|
688
|
+
//#region Utilities
|
|
689
|
+
#emitChange() {
|
|
631
690
|
const mapped = [];
|
|
632
|
-
this.widgetGrid.forEach((
|
|
633
|
-
const
|
|
634
|
-
delete
|
|
635
|
-
delete
|
|
636
|
-
|
|
691
|
+
this.widgetGrid.forEach((item) => {
|
|
692
|
+
const mappedWidget = { ...item };
|
|
693
|
+
delete mappedWidget['widget'];
|
|
694
|
+
delete mappedWidget['widgetConfigMap'];
|
|
695
|
+
delete mappedWidget['isApplicable'];
|
|
696
|
+
mapped.push(mappedWidget);
|
|
637
697
|
});
|
|
638
698
|
this.gridChange.emit(mapped);
|
|
639
699
|
}
|
|
700
|
+
#respondToWidgetServiceEvents() {
|
|
701
|
+
this.#widgetGridService.widgetGrid$
|
|
702
|
+
.pipe(takeUntilDestroyed(this.#destroyRef))
|
|
703
|
+
.subscribe((widgetGrid) => {
|
|
704
|
+
this.widgetGrid = widgetGrid;
|
|
705
|
+
});
|
|
706
|
+
this.#widgetGridEventService.widgetEvents$
|
|
707
|
+
.pipe(takeUntilDestroyed(this.#destroyRef))
|
|
708
|
+
.subscribe((evt) => {
|
|
709
|
+
this.gridItemEvent.emit(evt);
|
|
710
|
+
});
|
|
711
|
+
this.#widgetGridService.widgetGridUpdate$.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(() => {
|
|
712
|
+
this.#emitChange();
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region Effects
|
|
717
|
+
#gridItemConfigEffect = () => {
|
|
718
|
+
this.#widgetGridService.setWidgetGrid(this.gridItemConfig());
|
|
719
|
+
};
|
|
720
|
+
#editModeEffect = () => {
|
|
721
|
+
const editMode = this.editMode();
|
|
722
|
+
this._editMode.set(editMode);
|
|
723
|
+
this.options.update((option) => ({
|
|
724
|
+
...option,
|
|
725
|
+
draggable: { ...option.draggable, enabled: !!editMode },
|
|
726
|
+
resizable: { ...option.resizable, enabled: !!editMode },
|
|
727
|
+
displayGrid: editMode ? DisplayGrid.Always : DisplayGrid.None
|
|
728
|
+
}));
|
|
729
|
+
// set timeout to ensure that the grid has been redrawn before notifying the widgets about the change
|
|
730
|
+
setTimeout(() => {
|
|
731
|
+
this.gridster()?.api.calculateLayout();
|
|
732
|
+
}, 0);
|
|
733
|
+
};
|
|
734
|
+
#gridConfigEffect = () => {
|
|
735
|
+
const cfg = this.gridConfig();
|
|
736
|
+
this.options.update((option) => {
|
|
737
|
+
const next = { ...option };
|
|
738
|
+
if (cfg?.rows) {
|
|
739
|
+
next.minRows = cfg.rows;
|
|
740
|
+
next.maxRows = cfg.rows;
|
|
741
|
+
}
|
|
742
|
+
if (cfg?.columns) {
|
|
743
|
+
next.minCols = cfg.columns;
|
|
744
|
+
next.maxCols = cfg.columns;
|
|
745
|
+
}
|
|
746
|
+
if (cfg?.gap) {
|
|
747
|
+
next.margin = cfg.gap;
|
|
748
|
+
}
|
|
749
|
+
if (cfg?.gridType) {
|
|
750
|
+
next.gridType = cfg.gridType;
|
|
751
|
+
}
|
|
752
|
+
return next;
|
|
753
|
+
});
|
|
754
|
+
if (cfg?.newItemWidth || cfg?.newItemHeight) {
|
|
755
|
+
this.#widgetGridService.addItemSize = {
|
|
756
|
+
cols: cfg?.newItemWidth || -1, // default value -1 means the grid will auto-position the new item
|
|
757
|
+
rows: cfg?.newItemHeight || -1 // default value -1 means the grid will auto-position the new item
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
// setTimeout breaks the synchronous chain effect → options.update → calculateLayout →
|
|
761
|
+
// itemChangeCallback → gridChange.emit → parent → gridConfig, which would re-trigger
|
|
762
|
+
// this effect and cause NG0103. Also keeps the gridster() viewChild read out of the
|
|
763
|
+
// reactive scope so it doesn't become an effect dependency.
|
|
764
|
+
setTimeout(() => {
|
|
765
|
+
this.gridster()?.api.calculateLayout();
|
|
766
|
+
}, 0);
|
|
767
|
+
};
|
|
640
768
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
641
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: YuvWidgetGridComponent, isStandalone: true, selector: "yuv-widget-grid", inputs: { gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null }, gridItemConfig: { classPropertyName: "gridItemConfig", publicName: "gridItemConfig", isSignal: true, isRequired: false, transformFunction: null }, buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { gridChange: "gridChange", gridItemEvent: "gridItemEvent", widgetPickerOpen: "widgetPickerOpen" }, host: { properties: { "class.widget-grid-edit": "editMode()" } }, providers: [WidgetGridService], viewQueries: [{ propertyName: "gridster", first: true, predicate: Gridster, descendants: true, isSignal: true }, { propertyName: "widgetPicker", first: true, predicate: ["widgetPicker"], descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@if (_editMode()) {\n <button
|
|
769
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: YuvWidgetGridComponent, isStandalone: true, selector: "yuv-widget-grid", inputs: { gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null }, gridItemConfig: { classPropertyName: "gridItemConfig", publicName: "gridItemConfig", isSignal: true, isRequired: false, transformFunction: null }, buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { gridChange: "gridChange", gridItemEvent: "gridItemEvent", widgetPickerOpen: "widgetPickerOpen" }, host: { properties: { "class.widget-grid-edit": "editMode()" } }, providers: [WidgetGridService], viewQueries: [{ propertyName: "gridster", first: true, predicate: Gridster, descendants: true, isSignal: true }, { propertyName: "widgetPicker", first: true, predicate: ["widgetPicker"], descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@if (_editMode()) {\n <button\n class=\"fab\"\n mat-fab\n [attr.aria-label]=\"'yuv.widget-grid.widget.add.label' | translate\"\n (click)=\"onAddItem()\"\n [matTooltip]=\"'yuv.widget-grid.widget.add.tooltip' | translate\"\n >\n <mat-icon>add</mat-icon>\n </button>\n}\n\n@if (widgetGrid.length === 0 && !_editMode()) {\n <div class=\"empty\">\n <!-- <p>{{ 'yuv.widget-grid.empty' | translate }}</p>\n <button ymtButton=\"primary\" (click)=\"_editMode.set(true)\">\n {{'yuv.widget-grid.empty.create' | translate}}</button> -->\n <ng-content select=\".empty\"\n ><p>{{ 'yuv.widget-grid.empty' | translate }}</p></ng-content\n >\n </div>\n} @else {\n <gridster [options]=\"options()\">\n @for (item of widgetGrid; track item.id) {\n @if (item.isApplicable) {\n <gridster-item [item]=\"item\">\n @if (_editMode()) {\n <div class=\"toolbar\">\n <!-- drag handle -->\n <mat-icon class=\"dragHandle ymt-icon--size-s\">drag_indicator</mat-icon>\n <!-- edit -->\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.widget-grid.widget.edit.tooltip' | translate\"\n (click)=\"onOpenWidgetPicker(item)\"\n >\n <mat-icon class=\"ymt-icon--size-s\">edit</mat-icon>\n </button>\n <!-- remove -->\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.widget-grid.widget.remove.tooltip' | translate\"\n (click)=\"onRemoveItem(item)\"\n >\n <mat-icon class=\"ymt-icon--size-s\">clear</mat-icon>\n </button>\n </div>\n }\n <div class=\"cmp\">\n <ng-container *ngComponentOutlet=\"item.widget; ndcDynamicInputs: item.widgetConfigMap\" />\n </div>\n </gridster-item>\n }\n }\n </gridster>\n}\n<ng-template #widgetPicker>\n <yuv-widget-picker [pickerData]=\"widgetPickerData\" [buckets]=\"buckets()\" />\n</ng-template>\n", styles: [":host{--_widget-grid-toolbar-background: var(--widget-grid-toolbar-background, var(--ymt-surface));--_widget-grid-on-toolbar: var(--widget-grid-on-toolbar, var(--ymt-on-surface));position:relative}:host .fab{position:absolute;inset-inline-end:var(--ymt-spacing-m);inset-block-end:var(--ymt-spacing-m);z-index:100}:host .cmp{height:100%}:host.widget-grid-edit gridster-item{outline:1px solid var(--ymt-outline);outline-offset:-1px}:host .empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--ymt-on-surface);font:var(--ymt-font-body-medium)}:host .empty p{padding:var(--ymt-spacing-m);max-width:45ch;text-align:center}:host gridster{background-color:transparent}:host gridster ::ng-deep .gridster-column,:host gridster ::ng-deep .gridster-row{border-color:var(--ymt-outline-variant);transition:none}:host gridster-item{transition:none;background-color:transparent}:host gridster-item .toolbar{position:absolute;z-index:1;background-color:var(--_widget-grid-toolbar-background);color:var(--_widget-grid-on-toolbar);right:0;top:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs);gap:var(--ymt-spacing-s);display:flex;flex-flow:row nowrap;align-items:center;outline:1px solid var(--ymt-outline);outline-offset:-1px}:host gridster-item .toolbar button{color:currentColor}:host gridster-item .dragHandle{display:flex;flex-flow:column;align-items:center;justify-content:center;padding:0 var(--ymt-spacing-s);cursor:move}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: DynamicIoModule }, { kind: "directive", type: i3$1.ComponentOutletInjectorDirective, selector: "[ngComponentOutlet]", exportAs: ["ndcComponentOutletInjector"] }, { kind: "directive", type: i3$1.ComponentOutletIoDirective, selector: "[ngComponentOutletNdcDynamicInputs],[ngComponentOutletNdcDynamicOutputs]", inputs: ["ngComponentOutletNdcDynamicInputs", "ngComponentOutletNdcDynamicOutputs"], exportAs: ["ndcDynamicIo"] }, { kind: "ngmodule", type: SignalComponentIoModule }, { kind: "component", type: Gridster, selector: "gridster", inputs: ["options"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: WidgetPickerComponent, selector: "yuv-widget-picker", inputs: ["pickerData", "buckets"], outputs: ["picked", "canceled"] }, { kind: "component", type: GridsterItem, selector: "gridster-item", inputs: ["item"], outputs: ["itemInit", "itemChange", "itemResize"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
642
770
|
}
|
|
643
771
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridComponent, decorators: [{
|
|
644
772
|
type: Component,
|
|
@@ -655,15 +783,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
655
783
|
GridsterItem,
|
|
656
784
|
YmtIconButtonDirective,
|
|
657
785
|
MatTooltip
|
|
658
|
-
], providers: [WidgetGridService], host: { '[class.widget-grid-edit]': 'editMode()' }, template: "@if (_editMode()) {\n <button
|
|
786
|
+
], providers: [WidgetGridService], host: { '[class.widget-grid-edit]': 'editMode()' }, template: "@if (_editMode()) {\n <button\n class=\"fab\"\n mat-fab\n [attr.aria-label]=\"'yuv.widget-grid.widget.add.label' | translate\"\n (click)=\"onAddItem()\"\n [matTooltip]=\"'yuv.widget-grid.widget.add.tooltip' | translate\"\n >\n <mat-icon>add</mat-icon>\n </button>\n}\n\n@if (widgetGrid.length === 0 && !_editMode()) {\n <div class=\"empty\">\n <!-- <p>{{ 'yuv.widget-grid.empty' | translate }}</p>\n <button ymtButton=\"primary\" (click)=\"_editMode.set(true)\">\n {{'yuv.widget-grid.empty.create' | translate}}</button> -->\n <ng-content select=\".empty\"\n ><p>{{ 'yuv.widget-grid.empty' | translate }}</p></ng-content\n >\n </div>\n} @else {\n <gridster [options]=\"options()\">\n @for (item of widgetGrid; track item.id) {\n @if (item.isApplicable) {\n <gridster-item [item]=\"item\">\n @if (_editMode()) {\n <div class=\"toolbar\">\n <!-- drag handle -->\n <mat-icon class=\"dragHandle ymt-icon--size-s\">drag_indicator</mat-icon>\n <!-- edit -->\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.widget-grid.widget.edit.tooltip' | translate\"\n (click)=\"onOpenWidgetPicker(item)\"\n >\n <mat-icon class=\"ymt-icon--size-s\">edit</mat-icon>\n </button>\n <!-- remove -->\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.widget-grid.widget.remove.tooltip' | translate\"\n (click)=\"onRemoveItem(item)\"\n >\n <mat-icon class=\"ymt-icon--size-s\">clear</mat-icon>\n </button>\n </div>\n }\n <div class=\"cmp\">\n <ng-container *ngComponentOutlet=\"item.widget; ndcDynamicInputs: item.widgetConfigMap\" />\n </div>\n </gridster-item>\n }\n }\n </gridster>\n}\n<ng-template #widgetPicker>\n <yuv-widget-picker [pickerData]=\"widgetPickerData\" [buckets]=\"buckets()\" />\n</ng-template>\n", styles: [":host{--_widget-grid-toolbar-background: var(--widget-grid-toolbar-background, var(--ymt-surface));--_widget-grid-on-toolbar: var(--widget-grid-on-toolbar, var(--ymt-on-surface));position:relative}:host .fab{position:absolute;inset-inline-end:var(--ymt-spacing-m);inset-block-end:var(--ymt-spacing-m);z-index:100}:host .cmp{height:100%}:host.widget-grid-edit gridster-item{outline:1px solid var(--ymt-outline);outline-offset:-1px}:host .empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--ymt-on-surface);font:var(--ymt-font-body-medium)}:host .empty p{padding:var(--ymt-spacing-m);max-width:45ch;text-align:center}:host gridster{background-color:transparent}:host gridster ::ng-deep .gridster-column,:host gridster ::ng-deep .gridster-row{border-color:var(--ymt-outline-variant);transition:none}:host gridster-item{transition:none;background-color:transparent}:host gridster-item .toolbar{position:absolute;z-index:1;background-color:var(--_widget-grid-toolbar-background);color:var(--_widget-grid-on-toolbar);right:0;top:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs);gap:var(--ymt-spacing-s);display:flex;flex-flow:row nowrap;align-items:center;outline:1px solid var(--ymt-outline);outline-offset:-1px}:host gridster-item .toolbar button{color:currentColor}:host gridster-item .dragHandle{display:flex;flex-flow:column;align-items:center;justify-content:center;padding:0 var(--ymt-spacing-s);cursor:move}\n"] }]
|
|
659
787
|
}], ctorParameters: () => [], propDecorators: { gridster: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Gridster), { isSignal: true }] }], widgetPicker: [{ type: i0.ViewChild, args: ['widgetPicker', { ...{ read: TemplateRef }, isSignal: true }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], editMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editMode", required: false }] }], gridItemConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridItemConfig", required: false }] }], buckets: [{ type: i0.Input, args: [{ isSignal: true, alias: "buckets", required: false }] }], gridChange: [{ type: i0.Output, args: ["gridChange"] }], gridItemEvent: [{ type: i0.Output, args: ["gridItemEvent"] }], widgetPickerOpen: [{ type: i0.Output, args: ["widgetPickerOpen"] }] } });
|
|
660
788
|
|
|
661
789
|
class WorkspaceEditComponent {
|
|
662
|
-
|
|
790
|
+
//#region Dependencies
|
|
791
|
+
#dialogRef = inject((MatDialogRef));
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region Angular stuff
|
|
663
794
|
workspaceForm = input.required(...(ngDevMode ? [{ debugName: "workspaceForm" }] : /* istanbul ignore next */ []));
|
|
664
795
|
workspaceSubmit = output();
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region UI Methods
|
|
798
|
+
onCancel() {
|
|
799
|
+
this.#dialogRef.close();
|
|
800
|
+
}
|
|
665
801
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WorkspaceEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
666
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: WorkspaceEditComponent, isStandalone: true, selector: "yuv-workspace-edit", inputs: { workspaceForm: { classPropertyName: "workspaceForm", publicName: "workspaceForm", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { workspaceSubmit: "workspaceSubmit" }, ngImport: i0, template: "<yuv-dialog [
|
|
802
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: WorkspaceEditComponent, isStandalone: true, selector: "yuv-workspace-edit", inputs: { workspaceForm: { classPropertyName: "workspaceForm", publicName: "workspaceForm", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { workspaceSubmit: "workspaceSubmit" }, ngImport: i0, template: "<yuv-dialog [headertitle]=\"'yuv.widget-grid.workspaces.edit.title' | translate\">\n <main>\n <form id=\"workspaceEditForm\" [formGroup]=\"workspaceForm()\" (ngSubmit)=\"workspaceSubmit.emit()\">\n <mat-form-field class=\"yuv-form-field\">\n <mat-label>{{ 'yuv.widget-grid.workspaces.workspace.label' | translate }}</mat-label>\n <input matInput formControlName=\"label\" />\n </mat-form-field>\n </form>\n </main>\n <footer>\n <button ymtButton=\"secondary\" type=\"button\" (click)=\"onCancel()\">\n {{ 'yuv.widget-grid.workspaces.edit.cancel' | translate }}\n </button>\n <button ymtButton=\"primary\" form=\"workspaceEditForm\" type=\"submit\" [disabled]=\"workspaceForm().invalid\">\n {{ 'yuv.widget-grid.workspaces.edit.save' | translate }}\n </button>\n </footer>\n</yuv-dialog>\n", styles: [":host main{padding:var(--ymt-spacing-m)}:host mat-form-field{width:100%}\n"], dependencies: [{ kind: "component", type: DialogComponent, selector: "yuv-dialog", inputs: ["headertitle", "headertitel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: YmtButtonDirective, selector: "button[ymtButton], a[ymtButton]", inputs: ["ymtButton", "disabled", "aria-disabled", "disableRipple", "disabledInteractive", "button-size"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: TranslatePipe$1, name: "translate" }] });
|
|
667
803
|
}
|
|
668
804
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WorkspaceEditComponent, decorators: [{
|
|
669
805
|
type: Component,
|
|
@@ -674,7 +810,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
674
810
|
YmtButtonDirective,
|
|
675
811
|
TranslatePipe$1,
|
|
676
812
|
ReactiveFormsModule
|
|
677
|
-
], template: "<yuv-dialog [
|
|
813
|
+
], template: "<yuv-dialog [headertitle]=\"'yuv.widget-grid.workspaces.edit.title' | translate\">\n <main>\n <form id=\"workspaceEditForm\" [formGroup]=\"workspaceForm()\" (ngSubmit)=\"workspaceSubmit.emit()\">\n <mat-form-field class=\"yuv-form-field\">\n <mat-label>{{ 'yuv.widget-grid.workspaces.workspace.label' | translate }}</mat-label>\n <input matInput formControlName=\"label\" />\n </mat-form-field>\n </form>\n </main>\n <footer>\n <button ymtButton=\"secondary\" type=\"button\" (click)=\"onCancel()\">\n {{ 'yuv.widget-grid.workspaces.edit.cancel' | translate }}\n </button>\n <button ymtButton=\"primary\" form=\"workspaceEditForm\" type=\"submit\" [disabled]=\"workspaceForm().invalid\">\n {{ 'yuv.widget-grid.workspaces.edit.save' | translate }}\n </button>\n </footer>\n</yuv-dialog>\n", styles: [":host main{padding:var(--ymt-spacing-m)}:host mat-form-field{width:100%}\n"] }]
|
|
678
814
|
}], propDecorators: { workspaceForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "workspaceForm", required: true }] }], workspaceSubmit: [{ type: i0.Output, args: ["workspaceSubmit"] }] } });
|
|
679
815
|
|
|
680
816
|
/**
|
|
@@ -683,10 +819,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
683
819
|
* switch between workspaces to use or edit the widgets in the current workspace.
|
|
684
820
|
*/
|
|
685
821
|
class YuvWidgetGridWorkspacesComponent {
|
|
822
|
+
//#region Dependencies
|
|
686
823
|
#dialog = inject(MatDialog);
|
|
687
824
|
#fb = inject(FormBuilder);
|
|
688
825
|
#confirm = inject(ConfirmService);
|
|
826
|
+
#logger = inject(Logger);
|
|
689
827
|
translate = inject(TranslateService);
|
|
828
|
+
//#endregion
|
|
829
|
+
//#region Angular stuff
|
|
830
|
+
/**
|
|
831
|
+
* Collection of buckets to load available widgets from. Wildcards are also posssible:
|
|
832
|
+
* `[buckets]="['app.default', '*.public.*', 'app.no?.widgets']"`
|
|
833
|
+
*
|
|
834
|
+
* `*` represents any character 0-n times
|
|
835
|
+
* `?` represents exactly one character
|
|
836
|
+
*/
|
|
837
|
+
buckets = input(undefined, ...(ngDevMode ? [{ debugName: "buckets" }] : /* istanbul ignore next */ []));
|
|
838
|
+
configChange = output();
|
|
839
|
+
gridItemEvent = output();
|
|
840
|
+
editModeChange = output();
|
|
841
|
+
//#endregion
|
|
842
|
+
//#region Properties
|
|
690
843
|
workspaceDialogRef;
|
|
691
844
|
#DEFAULT_WORKSPACE_OPTIONS = {
|
|
692
845
|
gridConfig: {
|
|
@@ -710,48 +863,24 @@ class YuvWidgetGridWorkspacesComponent {
|
|
|
710
863
|
}), ...(ngDevMode ? [{ debugName: "_workspaceOptions" }] : /* istanbul ignore next */ []));
|
|
711
864
|
workspaceConfig = input(undefined, ...(ngDevMode ? [{ debugName: "workspaceConfig" }] : /* istanbul ignore next */ []));
|
|
712
865
|
_workspaceConfig = signal(undefined, ...(ngDevMode ? [{ debugName: "_workspaceConfig" }] : /* istanbul ignore next */ []));
|
|
713
|
-
#workspaceConfigEffect = effect(() => {
|
|
714
|
-
const wsc = this.workspaceConfig();
|
|
715
|
-
untracked(() => {
|
|
716
|
-
this._workspaceConfig.set(wsc);
|
|
717
|
-
this.#updateOriginalWorkspaceConfig();
|
|
718
|
-
if (wsc?.currentWorkspace) {
|
|
719
|
-
this.setWorkspace(wsc.currentWorkspace, true);
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
}, ...(ngDevMode ? [{ debugName: "#workspaceConfigEffect" }] : /* istanbul ignore next */ []));
|
|
723
|
-
/**
|
|
724
|
-
* Collection of buckets to load available widgets from. Wildcards are also posssible:
|
|
725
|
-
* `[buckets]="['app.default', '*.public.*', 'app.no?.widgets']"`
|
|
726
|
-
*
|
|
727
|
-
* `*` represents any character 0-n times
|
|
728
|
-
* `?` represents exactly one character
|
|
729
|
-
*/
|
|
730
|
-
buckets = input(undefined, ...(ngDevMode ? [{ debugName: "buckets" }] : /* istanbul ignore next */ []));
|
|
731
|
-
configChange = output();
|
|
732
|
-
gridItemEvent = output();
|
|
733
|
-
editModeChange = output();
|
|
734
866
|
editMode = signal(false, ...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
|
|
735
|
-
// persist the last 'accepted' workspace config to be able to revert changes
|
|
736
|
-
#originalWidgetGridWorkspaceConfig;
|
|
737
867
|
// currently selected workspace
|
|
738
868
|
workspace = signal(undefined, ...(ngDevMode ? [{ debugName: "workspace" }] : /* istanbul ignore next */ []));
|
|
739
|
-
#workspaceEffect = effect(() => {
|
|
740
|
-
const ws = this.workspace();
|
|
741
|
-
this.workspaceLabel.set(ws ? this.getLabel(ws) : '');
|
|
742
|
-
untracked(() => {
|
|
743
|
-
if (ws)
|
|
744
|
-
this.workspaceForm.patchValue({ label: this.workspaceLabel(), id: ws.id });
|
|
745
|
-
this.gridItemConfig.set(ws ? JSON.parse(JSON.stringify(ws.grid)) : undefined);
|
|
746
|
-
});
|
|
747
|
-
}, ...(ngDevMode ? [{ debugName: "#workspaceEffect" }] : /* istanbul ignore next */ []));
|
|
748
869
|
workspaceLabel = signal('', ...(ngDevMode ? [{ debugName: "workspaceLabel" }] : /* istanbul ignore next */ []));
|
|
749
870
|
gridItemConfig = signal(undefined, ...(ngDevMode ? [{ debugName: "gridItemConfig" }] : /* istanbul ignore next */ []));
|
|
871
|
+
// persist the last 'accepted' workspace config to be able to revert changes
|
|
872
|
+
#originalWidgetGridWorkspaceConfig;
|
|
873
|
+
//#endregion
|
|
874
|
+
constructor() {
|
|
875
|
+
effect(this.#workspaceEffect);
|
|
876
|
+
effect(this.#workspaceConfigEffect);
|
|
877
|
+
}
|
|
750
878
|
getLabel(workspace) {
|
|
751
879
|
return workspace.translateLabel ? this.translate.instant(workspace.label) : workspace.label;
|
|
752
880
|
}
|
|
753
|
-
|
|
754
|
-
|
|
881
|
+
//#region UI Methods
|
|
882
|
+
onSetWorkspace(id, silent = false) {
|
|
883
|
+
const workspace = this._workspaceConfig()?.workspaces.find((item) => item.id === id);
|
|
755
884
|
if (workspace) {
|
|
756
885
|
this.workspace.set(workspace);
|
|
757
886
|
this._workspaceConfig.update((cfg) => ({
|
|
@@ -764,15 +893,90 @@ class YuvWidgetGridWorkspacesComponent {
|
|
|
764
893
|
this.workspace.set(undefined);
|
|
765
894
|
}
|
|
766
895
|
if (!silent)
|
|
767
|
-
this
|
|
896
|
+
this.#emitConfigChange();
|
|
768
897
|
}
|
|
769
|
-
|
|
898
|
+
onOpenWorkspaceDialog(create, tplRef) {
|
|
770
899
|
if (create) {
|
|
771
900
|
this.workspaceForm.reset();
|
|
772
901
|
}
|
|
773
902
|
this.workspaceDialogRef = this.#dialog.open(tplRef);
|
|
774
903
|
}
|
|
775
|
-
|
|
904
|
+
onDeleteCurrentWorkspace() {
|
|
905
|
+
const workspaceConfig = this._workspaceConfig();
|
|
906
|
+
const workspace = this.workspace();
|
|
907
|
+
if (workspace && workspaceConfig) {
|
|
908
|
+
const idx = workspaceConfig.workspaces.findIndex((item) => item.id === workspace.id);
|
|
909
|
+
if (idx > -1) {
|
|
910
|
+
this.#confirm
|
|
911
|
+
.confirm({
|
|
912
|
+
message: this.translate.instant('yuv.widget-grid.workspaces.workspace.delete.confirm.message', {
|
|
913
|
+
label: workspace.label
|
|
914
|
+
})
|
|
915
|
+
})
|
|
916
|
+
.subscribe((confirmed) => {
|
|
917
|
+
if (!confirmed)
|
|
918
|
+
return;
|
|
919
|
+
const workspaces = workspaceConfig.workspaces;
|
|
920
|
+
workspaces.splice(idx, 1);
|
|
921
|
+
// pick another workspace if the current one is deleted
|
|
922
|
+
const newCurrentWorkspace = workspaces.length > 0 ? workspaces[0] : undefined;
|
|
923
|
+
this._workspaceConfig.set({
|
|
924
|
+
...workspaceConfig,
|
|
925
|
+
workspaces,
|
|
926
|
+
currentWorkspace: newCurrentWorkspace?.id
|
|
927
|
+
});
|
|
928
|
+
this.workspace.set(newCurrentWorkspace);
|
|
929
|
+
this.#emitConfigChange();
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
onToggleEditMode() {
|
|
935
|
+
this.editMode.set(!this.editMode());
|
|
936
|
+
}
|
|
937
|
+
onRevertWorkspaceConfig() {
|
|
938
|
+
if (this.#originalWidgetGridWorkspaceConfig) {
|
|
939
|
+
this._workspaceConfig.set(WidgetGridUtils.gridConfigParse(this.#originalWidgetGridWorkspaceConfig));
|
|
940
|
+
}
|
|
941
|
+
const cws = this.workspaceConfig()?.currentWorkspace;
|
|
942
|
+
if (cws)
|
|
943
|
+
this.onSetWorkspace(cws, true);
|
|
944
|
+
this.editMode.set(false);
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Emit current changes and reset original workspace config internally
|
|
948
|
+
*
|
|
949
|
+
* @returns void
|
|
950
|
+
*/
|
|
951
|
+
onPersistWorkspaceConfig() {
|
|
952
|
+
this.onSaveWorkspace();
|
|
953
|
+
// this.#updateOriginalWorkspaceConfig();
|
|
954
|
+
// this.emitConfigChange();
|
|
955
|
+
this.editMode.set(false);
|
|
956
|
+
}
|
|
957
|
+
onGridChange(grid) {
|
|
958
|
+
const workspace = this.workspace();
|
|
959
|
+
if (workspace) {
|
|
960
|
+
workspace.grid = grid;
|
|
961
|
+
const wsc = this._workspaceConfig();
|
|
962
|
+
const idx = wsc ? wsc.workspaces.findIndex((item) => item.id === workspace.id) : -1;
|
|
963
|
+
if (idx > -1) {
|
|
964
|
+
const wgw = this._workspaceConfig()?.workspaces || [];
|
|
965
|
+
wgw[idx] = workspace;
|
|
966
|
+
this._workspaceConfig.update((curr) => ({
|
|
967
|
+
...curr,
|
|
968
|
+
workspaces: wgw
|
|
969
|
+
}));
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
onGridEvent(gridItemEvent) {
|
|
974
|
+
this.gridItemEvent.emit(gridItemEvent);
|
|
975
|
+
}
|
|
976
|
+
onEnableEditMode() {
|
|
977
|
+
this.editMode.set(true);
|
|
978
|
+
}
|
|
979
|
+
onSaveWorkspace() {
|
|
776
980
|
let cfg = this._workspaceConfig();
|
|
777
981
|
if (!cfg) {
|
|
778
982
|
cfg = {
|
|
@@ -795,98 +999,62 @@ class YuvWidgetGridWorkspacesComponent {
|
|
|
795
999
|
this.workspaceForm.patchValue({ id: newWorkspace.id, label: newWorkspace.label });
|
|
796
1000
|
}
|
|
797
1001
|
else {
|
|
798
|
-
const existingIndex = cfg.workspaces.findIndex((
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1002
|
+
const existingIndex = cfg.workspaces.findIndex((item) => item.id === this.workspaceForm.value.id);
|
|
1003
|
+
/**
|
|
1004
|
+
* ADR: only update the label when the workspace is found in the current config.
|
|
1005
|
+
*
|
|
1006
|
+
* The previous implementation had the index check inverted (`existingIndex === -1`),
|
|
1007
|
+
* which would have dereferenced `cfg.workspaces[-1]` and crashed on `.label` if a
|
|
1008
|
+
* form submission ever arrived for a non-existent workspace id. In the current UI
|
|
1009
|
+
* flow this branch is only entered with an id that exists in the list, so the bug
|
|
1010
|
+
* stayed silent — but the inverted check was load-bearing on that invariant.
|
|
1011
|
+
*
|
|
1012
|
+
* `existingIndex === -1` is treated as a "should not happen" state — it would only
|
|
1013
|
+
* occur if the workspace was removed externally (other tab/race) between opening
|
|
1014
|
+
* the edit dialog and submitting it. We no-op and log a warning so the anomaly is
|
|
1015
|
+
* visible in dev tools without crashing the UI.
|
|
1016
|
+
*/
|
|
1017
|
+
if (existingIndex !== -1) {
|
|
1018
|
+
cfg.workspaces[existingIndex].label = this.workspaceForm.value.label;
|
|
808
1019
|
}
|
|
809
1020
|
else {
|
|
810
|
-
|
|
811
|
-
cfg.workspaces[existingIndex].label = this.workspaceForm.value.label;
|
|
1021
|
+
this.#logger.error('onSaveWorkspace: workspace not in config; ignored', this.workspaceForm.value.id);
|
|
812
1022
|
}
|
|
813
1023
|
}
|
|
814
1024
|
this.workspaceDialogRef?.close();
|
|
815
|
-
this
|
|
1025
|
+
this.#emitConfigChange();
|
|
1026
|
+
}
|
|
1027
|
+
//#endregion
|
|
1028
|
+
//#region Utilities
|
|
1029
|
+
#emitConfigChange() {
|
|
1030
|
+
this.configChange.emit(this._workspaceConfig());
|
|
816
1031
|
}
|
|
817
1032
|
#updateOriginalWorkspaceConfig() {
|
|
818
1033
|
this.#originalWidgetGridWorkspaceConfig = WidgetGridUtils.gridConfigStringify(this.workspaceConfig());
|
|
819
1034
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
// emit current changes and reset original workspace config internally
|
|
840
|
-
persistWorkspaceConfig() {
|
|
841
|
-
this.saveWorkspace();
|
|
842
|
-
// this.#updateOriginalWorkspaceConfig();
|
|
843
|
-
// this.emitConfigChange();
|
|
844
|
-
this.editMode.set(false);
|
|
845
|
-
}
|
|
846
|
-
revertWorkspaceConfig() {
|
|
847
|
-
if (this.#originalWidgetGridWorkspaceConfig) {
|
|
848
|
-
this._workspaceConfig.set(WidgetGridUtils.gridConfigParse(this.#originalWidgetGridWorkspaceConfig));
|
|
849
|
-
}
|
|
850
|
-
const cws = this.workspaceConfig()?.currentWorkspace;
|
|
851
|
-
if (cws)
|
|
852
|
-
this.setWorkspace(cws, true);
|
|
853
|
-
this.editMode.set(false);
|
|
854
|
-
}
|
|
855
|
-
deleteCurrentWorkspace() {
|
|
856
|
-
const wsc = this._workspaceConfig();
|
|
857
|
-
const ws = this.workspace();
|
|
858
|
-
if (ws && wsc) {
|
|
859
|
-
const idx = wsc.workspaces.findIndex((w) => w.id === ws.id);
|
|
860
|
-
if (idx > -1) {
|
|
861
|
-
this.#confirm
|
|
862
|
-
.confirm({
|
|
863
|
-
message: this.translate.instant('yuv.widget-grid.workspaces.workspace.delete.confirm.message', {
|
|
864
|
-
label: ws.label
|
|
865
|
-
})
|
|
866
|
-
})
|
|
867
|
-
.subscribe((confirmed) => {
|
|
868
|
-
if (!confirmed)
|
|
869
|
-
return;
|
|
870
|
-
const workspaces = wsc.workspaces;
|
|
871
|
-
workspaces.splice(idx, 1);
|
|
872
|
-
// pick another workspace if the current one is deleted
|
|
873
|
-
const newCurrentWorkspace = workspaces.length > 0 ? workspaces[0] : undefined;
|
|
874
|
-
this._workspaceConfig.set({
|
|
875
|
-
...wsc,
|
|
876
|
-
workspaces,
|
|
877
|
-
currentWorkspace: newCurrentWorkspace?.id
|
|
878
|
-
});
|
|
879
|
-
this.workspace.set(newCurrentWorkspace);
|
|
880
|
-
this.emitConfigChange();
|
|
881
|
-
});
|
|
1035
|
+
//#endregion
|
|
1036
|
+
//#region Effect
|
|
1037
|
+
#workspaceEffect = () => {
|
|
1038
|
+
const workspace = this.workspace();
|
|
1039
|
+
this.workspaceLabel.set(workspace ? this.getLabel(workspace) : '');
|
|
1040
|
+
untracked(() => {
|
|
1041
|
+
if (workspace)
|
|
1042
|
+
this.workspaceForm.patchValue({ label: this.workspaceLabel(), id: workspace.id });
|
|
1043
|
+
this.gridItemConfig.set(workspace ? JSON.parse(JSON.stringify(workspace.grid)) : undefined);
|
|
1044
|
+
});
|
|
1045
|
+
};
|
|
1046
|
+
#workspaceConfigEffect = () => {
|
|
1047
|
+
const wsc = this.workspaceConfig();
|
|
1048
|
+
untracked(() => {
|
|
1049
|
+
this._workspaceConfig.set(wsc);
|
|
1050
|
+
this.#updateOriginalWorkspaceConfig();
|
|
1051
|
+
if (wsc?.currentWorkspace) {
|
|
1052
|
+
this.onSetWorkspace(wsc.currentWorkspace, true);
|
|
882
1053
|
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
emitConfigChange() {
|
|
886
|
-
this.configChange.emit(this._workspaceConfig());
|
|
887
|
-
}
|
|
1054
|
+
});
|
|
1055
|
+
};
|
|
888
1056
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridWorkspacesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
889
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: YuvWidgetGridWorkspacesComponent, isStandalone: true, selector: "yuv-widget-grid-workspaces", inputs: {
|
|
1057
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: YuvWidgetGridWorkspacesComponent, isStandalone: true, selector: "yuv-widget-grid-workspaces", inputs: { buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, workspaceConfig: { classPropertyName: "workspaceConfig", publicName: "workspaceConfig", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { configChange: "configChange", gridItemEvent: "gridItemEvent", editModeChange: "editModeChange" }, ngImport: i0, template: "@let ws = workspace();\n\n<div class=\"toolbar\">\n @if (ws) {\n @let workspaces = _workspaceConfig()?.workspaces || [];\n\n <button\n mat-icon-button\n [matTooltip]=\"'yuv.widget-grid.workspaces.workspace.menu.tooltip' | translate\"\n [disabled]=\"editMode()\"\n [matMenuTriggerFor]=\"workspacePickerMenu\"\n >\n <mat-icon>menu</mat-icon>\n </button>\n\n <mat-menu #workspacePickerMenu=\"matMenu\">\n @for (ws of workspaces; track ws.id) {\n <button mat-menu-item (click)=\"onSetWorkspace(ws.id)\">{{ getLabel(ws) }}</button>\n }\n <mat-divider />\n <button mat-menu-item (click)=\"onOpenWorkspaceDialog(true, tplWorkspaceEdit)\">\n {{ 'yuv.widget-grid.workspaces.workspace.menu.create' | translate }}\n </button>\n </mat-menu>\n\n @if (!editMode()) {\n <h2 class=\"label\">{{ workspaceLabel() }}</h2>\n @if (!ws.preventEdit) {\n <button mat-icon-button [matMenuTriggerFor]=\"workspaceMenu\"><mat-icon>more_vert</mat-icon></button>\n <mat-menu #workspaceMenu=\"matMenu\">\n <button mat-menu-item (click)=\"onToggleEditMode()\">\n {{ 'yuv.widget-grid.workspaces.workspace.menu.edit' | translate }}\n </button>\n <button mat-menu-item (click)=\"onDeleteCurrentWorkspace()\">\n {{ 'yuv.widget-grid.workspaces.workspace.menu.delete' | translate }}\n </button>\n </mat-menu>\n }\n } @else {\n @if (ws.preventRename || ws.translateLabel) {\n <h2 class=\"label\">{{ workspaceLabel() }}</h2>\n } @else {\n <input type=\"text\" class=\"label\" [formControl]=\"workspaceLabelControl\" />\n }\n <button ymtButton=\"secondary\" (click)=\"onRevertWorkspaceConfig()\">\n {{ 'yuv.widget-grid.workspaces.editMode.cancel' | translate }}\n </button>\n <button ymtButton=\"primary\" (click)=\"onPersistWorkspaceConfig()\">\n {{ 'yuv.widget-grid.workspaces.editMode.save' | translate }}\n </button>\n }\n }\n</div>\n\n@if (ws) {\n <yuv-widget-grid\n [gridConfig]=\"_workspaceOptions().gridConfig\"\n [gridItemConfig]=\"gridItemConfig()\"\n [buckets]=\"buckets()\"\n [editMode]=\"editMode()\"\n (gridChange)=\"onGridChange($event)\"\n (gridItemEvent)=\"onGridEvent($event)\"\n >\n <div class=\"empty\">\n <p>{{ 'yuv.widget-grid.empty' | translate }}</p>\n <button ymtButton=\"primary\" (click)=\"onEnableEditMode()\">\n {{ 'yuv.widget-grid.empty.create' | translate }}\n </button>\n </div>\n </yuv-widget-grid>\n} @else {\n <div class=\"empty\">\n <p>{{ 'yuv.widget-grid.workspaces.empty' | translate }}</p>\n <button ymtButton=\"primary\" (click)=\"onOpenWorkspaceDialog(true, tplWorkspaceEdit)\">\n {{ 'yuv.widget-grid.workspaces.empty.button.create' | translate }}\n </button>\n </div>\n}\n\n<ng-template #tplWorkspaceEdit>\n <yuv-workspace-edit [workspaceForm]=\"workspaceForm\" (workspaceSubmit)=\"onSaveWorkspace()\" />\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;overflow-y:auto}:host .toolbar{display:flex;flex-direction:row;align-items:center;gap:var(--ymt-spacing-m)}:host .toolbar .label{flex:1;font:var(--ymt-font-headline-medium);letter-spacing:var(--ymt-font-headline-medium-tracking);margin-inline:0;margin-block:.75em;padding-inline:.25em 0;padding-block:.25em}:host .toolbar input.label{border:0;background-color:transparent}:host .toolbar input.label:not([disabled]){outline:1px solid var(--ymt-outline)}:host yuv-widget-grid{flex:1}:host .empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "component", type: YuvWidgetGridComponent, selector: "yuv-widget-grid", inputs: ["gridConfig", "editMode", "gridItemConfig", "buckets"], outputs: ["gridChange", "gridItemEvent", "widgetPickerOpen"] }, { kind: "directive", type: YmtButtonDirective, selector: "button[ymtButton], a[ymtButton]", inputs: ["ymtButton", "disabled", "aria-disabled", "disableRipple", "disabledInteractive", "button-size"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i5$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: WorkspaceEditComponent, selector: "yuv-workspace-edit", inputs: ["workspaceForm"], outputs: ["workspaceSubmit"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
890
1058
|
}
|
|
891
1059
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridWorkspacesComponent, decorators: [{
|
|
892
1060
|
type: Component,
|
|
@@ -903,18 +1071,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
903
1071
|
MatTooltipModule,
|
|
904
1072
|
TranslatePipe,
|
|
905
1073
|
WorkspaceEditComponent
|
|
906
|
-
], template: "@let ws = workspace();\n\n<div class=\"toolbar\">\n @if (ws) {\n @let workspaces = _workspaceConfig()?.workspaces || [];\n\n <button\n mat-icon-button\n [matTooltip]=\"'yuv.widget-grid.workspaces.workspace.menu.tooltip' | translate\"\n [disabled]=\"editMode()\"\n [matMenuTriggerFor]=\"workspacePickerMenu\"\n >\n <mat-icon>menu</mat-icon>\n </button>\n\n <mat-menu #workspacePickerMenu=\"matMenu\">\n @for (ws of workspaces; track ws.id) {\n <button mat-menu-item (click)=\"
|
|
907
|
-
}], propDecorators: {
|
|
1074
|
+
], template: "@let ws = workspace();\n\n<div class=\"toolbar\">\n @if (ws) {\n @let workspaces = _workspaceConfig()?.workspaces || [];\n\n <button\n mat-icon-button\n [matTooltip]=\"'yuv.widget-grid.workspaces.workspace.menu.tooltip' | translate\"\n [disabled]=\"editMode()\"\n [matMenuTriggerFor]=\"workspacePickerMenu\"\n >\n <mat-icon>menu</mat-icon>\n </button>\n\n <mat-menu #workspacePickerMenu=\"matMenu\">\n @for (ws of workspaces; track ws.id) {\n <button mat-menu-item (click)=\"onSetWorkspace(ws.id)\">{{ getLabel(ws) }}</button>\n }\n <mat-divider />\n <button mat-menu-item (click)=\"onOpenWorkspaceDialog(true, tplWorkspaceEdit)\">\n {{ 'yuv.widget-grid.workspaces.workspace.menu.create' | translate }}\n </button>\n </mat-menu>\n\n @if (!editMode()) {\n <h2 class=\"label\">{{ workspaceLabel() }}</h2>\n @if (!ws.preventEdit) {\n <button mat-icon-button [matMenuTriggerFor]=\"workspaceMenu\"><mat-icon>more_vert</mat-icon></button>\n <mat-menu #workspaceMenu=\"matMenu\">\n <button mat-menu-item (click)=\"onToggleEditMode()\">\n {{ 'yuv.widget-grid.workspaces.workspace.menu.edit' | translate }}\n </button>\n <button mat-menu-item (click)=\"onDeleteCurrentWorkspace()\">\n {{ 'yuv.widget-grid.workspaces.workspace.menu.delete' | translate }}\n </button>\n </mat-menu>\n }\n } @else {\n @if (ws.preventRename || ws.translateLabel) {\n <h2 class=\"label\">{{ workspaceLabel() }}</h2>\n } @else {\n <input type=\"text\" class=\"label\" [formControl]=\"workspaceLabelControl\" />\n }\n <button ymtButton=\"secondary\" (click)=\"onRevertWorkspaceConfig()\">\n {{ 'yuv.widget-grid.workspaces.editMode.cancel' | translate }}\n </button>\n <button ymtButton=\"primary\" (click)=\"onPersistWorkspaceConfig()\">\n {{ 'yuv.widget-grid.workspaces.editMode.save' | translate }}\n </button>\n }\n }\n</div>\n\n@if (ws) {\n <yuv-widget-grid\n [gridConfig]=\"_workspaceOptions().gridConfig\"\n [gridItemConfig]=\"gridItemConfig()\"\n [buckets]=\"buckets()\"\n [editMode]=\"editMode()\"\n (gridChange)=\"onGridChange($event)\"\n (gridItemEvent)=\"onGridEvent($event)\"\n >\n <div class=\"empty\">\n <p>{{ 'yuv.widget-grid.empty' | translate }}</p>\n <button ymtButton=\"primary\" (click)=\"onEnableEditMode()\">\n {{ 'yuv.widget-grid.empty.create' | translate }}\n </button>\n </div>\n </yuv-widget-grid>\n} @else {\n <div class=\"empty\">\n <p>{{ 'yuv.widget-grid.workspaces.empty' | translate }}</p>\n <button ymtButton=\"primary\" (click)=\"onOpenWorkspaceDialog(true, tplWorkspaceEdit)\">\n {{ 'yuv.widget-grid.workspaces.empty.button.create' | translate }}\n </button>\n </div>\n}\n\n<ng-template #tplWorkspaceEdit>\n <yuv-workspace-edit [workspaceForm]=\"workspaceForm\" (workspaceSubmit)=\"onSaveWorkspace()\" />\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;overflow-y:auto}:host .toolbar{display:flex;flex-direction:row;align-items:center;gap:var(--ymt-spacing-m)}:host .toolbar .label{flex:1;font:var(--ymt-font-headline-medium);letter-spacing:var(--ymt-font-headline-medium-tracking);margin-inline:0;margin-block:.75em;padding-inline:.25em 0;padding-block:.25em}:host .toolbar input.label{border:0;background-color:transparent}:host .toolbar input.label:not([disabled]){outline:1px solid var(--ymt-outline)}:host yuv-widget-grid{flex:1}:host .empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}\n"] }]
|
|
1075
|
+
}], ctorParameters: () => [], propDecorators: { buckets: [{ type: i0.Input, args: [{ isSignal: true, alias: "buckets", required: false }] }], configChange: [{ type: i0.Output, args: ["configChange"] }], gridItemEvent: [{ type: i0.Output, args: ["gridItemEvent"] }], editModeChange: [{ type: i0.Output, args: ["editModeChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], workspaceConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "workspaceConfig", required: false }] }] } });
|
|
1076
|
+
|
|
1077
|
+
// widget-grid-workspaces/widget-grid-workspaces.component
|
|
1078
|
+
|
|
1079
|
+
// export * from './widget-grid-event.service';
|
|
1080
|
+
// export * from './widget-grid.utils';
|
|
908
1081
|
|
|
909
|
-
const cmp = [
|
|
910
|
-
YuvWidgetGridComponent,
|
|
911
|
-
YuvWidgetGridWorkspacesComponent
|
|
912
|
-
];
|
|
1082
|
+
const cmp = [YuvWidgetGridComponent, YuvWidgetGridWorkspacesComponent];
|
|
913
1083
|
class YuvWidgetGridModule {
|
|
914
1084
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
915
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridModule, imports: [YuvWidgetGridComponent,
|
|
916
|
-
YuvWidgetGridWorkspacesComponent], exports: [YuvWidgetGridComponent,
|
|
917
|
-
YuvWidgetGridWorkspacesComponent] });
|
|
1085
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridModule, imports: [YuvWidgetGridComponent, YuvWidgetGridWorkspacesComponent], exports: [YuvWidgetGridComponent, YuvWidgetGridWorkspacesComponent] });
|
|
918
1086
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridModule, imports: [cmp] });
|
|
919
1087
|
}
|
|
920
1088
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: YuvWidgetGridModule, decorators: [{
|