inviton-powerduck 0.0.71 → 0.0.73
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/common/base-component.tsx +284 -346
- package/common/keyboard-open-tracker.ts +44 -0
- package/common/scroll-utils.ts +87 -0
- package/package.json +1 -1
|
@@ -10,368 +10,306 @@ import { isNullOrEmpty } from "./utils/is-null-or-empty";
|
|
|
10
10
|
import NotificationProvider from './../components/ui/notification';
|
|
11
11
|
import PowerduckState from "../app/powerduck-state";
|
|
12
12
|
import { IValidation, ValidationState } from './static-wrappers/interfaces/validation-interface';
|
|
13
|
+
import ScrollUtils from "./scroll-utils";
|
|
13
14
|
|
|
14
15
|
export abstract class PowerduckViewModelBase extends Vue {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
blockRoot: boolean = true;
|
|
17
|
+
authorized: boolean = true;
|
|
18
|
+
v$: Validation;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
constructor(optionBuilder: OptionBuilder, vueInstance: any) {
|
|
21
|
+
super(optionBuilder, vueInstance);
|
|
22
|
+
this.v$ = useVuelidate({
|
|
22
23
|
$scope: vueInstance
|
|
23
24
|
}) as any as Validation;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} else {
|
|
52
|
-
return {
|
|
53
|
-
data: retVal,
|
|
54
|
-
error: null as any,
|
|
55
|
-
result: TryCallApiResult.Success,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Try PATCH data to Inviton API endpoint
|
|
62
|
-
* just decorator
|
|
63
|
-
*/
|
|
64
|
-
public async tryPatchDataByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>): Promise<TryPatchApiResponse<TData>> {
|
|
65
|
-
return await this.tryPostDataByArgs(args);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public async tryDeleteDataByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>): Promise<TData> {
|
|
69
|
-
return this.tryCallApiByArgs(args, false);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private async tryCallApiByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>, includeError: boolean): Promise<TData> {
|
|
73
|
-
var retVal: TData;
|
|
74
|
-
let handle: any = null;
|
|
75
|
-
|
|
76
|
-
if (args.blockRoot != false) {
|
|
77
|
-
handle = setTimeout(() => {
|
|
78
|
-
this.blockRoot = true;
|
|
79
|
-
}, 850);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
var apiMethod = args.apiMethod as any;
|
|
83
|
-
var promise = apiMethod(args.requestArgs, args.timeout);
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
retVal = await promise;
|
|
87
|
-
} catch (e: any) {
|
|
88
|
-
let err: AjaxError = e;
|
|
89
|
-
|
|
90
|
-
if (args.blockRoot != false) {
|
|
91
|
-
this.blockRoot = false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (e == "not authorized, token expired") {
|
|
95
|
-
err = {
|
|
96
|
-
authorized: false,
|
|
97
|
-
responseText: PowerduckState.getResourceValue('loginExpired'),
|
|
98
|
-
} as any;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (err.authorized == false || e == "not authorized, token expired") {
|
|
102
|
-
(window as any).loginModalRootInstance.show();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!err.authorized && args.toggleAuthorization) {
|
|
106
|
-
this.authorized = err.authorized;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (args.showError != false) {
|
|
110
|
-
let parsedMsg = PowerduckState.parseErrorMessage(err.responseText);
|
|
111
|
-
if (!isNullOrEmpty(parsedMsg)) {
|
|
112
|
-
this.showErrorMessage(parsedMsg);
|
|
113
|
-
} else if (err.responseText) {
|
|
114
|
-
this.showErrorMessage(err.responseText);
|
|
115
|
-
} else if ((err as any).message) {
|
|
116
|
-
this.showErrorMessage((err as any).message);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (includeError) {
|
|
121
|
-
retVal = {
|
|
122
|
-
ajaxErr: err,
|
|
123
|
-
} as any;
|
|
124
|
-
} else {
|
|
125
|
-
retVal = null as any;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (args.blockRoot != false) {
|
|
130
|
-
try {
|
|
131
|
-
clearTimeout(handle);
|
|
132
|
-
} catch (error) { }
|
|
133
|
-
|
|
134
|
-
if (this.blockRoot == true) {
|
|
135
|
-
this.blockRoot = false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return retVal;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Get children components of given type
|
|
144
|
-
*
|
|
145
|
-
* @param typeName Name of the type
|
|
146
|
-
*/
|
|
147
|
-
getChildrenByType<T>(typeName: string): Array<T> {
|
|
148
|
-
return PortalUtils.getChildrenByType(this, typeName);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Displays unobtrusive error message
|
|
153
|
-
* @param errMsg Error message
|
|
154
|
-
*/
|
|
155
|
-
showErrorMessage(errMsg: string): void {
|
|
156
|
-
NotificationProvider.showErrorMessage(errMsg);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Displays unobtrusive success message
|
|
161
|
-
* @param successMsg Success message
|
|
162
|
-
*/
|
|
163
|
-
showSuccessMessage(successMsg: string): void {
|
|
164
|
-
NotificationProvider.showSuccessMessage(successMsg);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Parse variable into number
|
|
169
|
-
* @param val
|
|
170
|
-
* @param defaultValue
|
|
171
|
-
*/
|
|
172
|
-
getNumericValue(val: any, defaultValue?: number) {
|
|
173
|
-
if (val != null) {
|
|
174
|
-
try {
|
|
175
|
-
val = Number(val);
|
|
176
|
-
if (isNaN(val)) {
|
|
177
|
-
val = defaultValue;
|
|
178
|
-
}
|
|
179
|
-
} catch (e) {
|
|
180
|
-
val = defaultValue;
|
|
181
|
-
}
|
|
182
|
-
} else {
|
|
183
|
-
val = defaultValue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return val;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Validates current viewModel state based on given valdiation ruleset
|
|
191
|
-
*/
|
|
192
|
-
async validate(showErrorMessage?: boolean, silent?: boolean): Promise<boolean> {
|
|
193
|
-
if (localStorage.getItem("disableValidation") == "1") {
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (this.v$ == null) {
|
|
198
|
-
throw "Validation rules not specified, has to be specified in @Component declaration!";
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
var isInvalid = !(await this.v$.$validate());
|
|
202
|
-
if (isInvalid) {
|
|
203
|
-
if (showErrorMessage != false) {
|
|
204
|
-
this.showValidationErrorMessage();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (silent != true) {
|
|
208
|
-
this.validationIncludeDirty = true;
|
|
209
|
-
this.$nextTick(() => {
|
|
210
|
-
this.scrollToFirstPossibleError($(this.unwrapRootElement()));
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return !isInvalid;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
unwrapRootElement(): HTMLElement {
|
|
219
|
-
if ((this.$el as any).$getChildSlots != null) {
|
|
220
|
-
return (((this.$el as any).$getChildSlots() || [])[0]?.el || this.$el) as any;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return this.$el as any;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
showValidationErrorMessage() {
|
|
227
|
-
this.showErrorMessage(PowerduckState.getResourceValue('errorsOnForm'));
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
scrollToFirstPossibleError(context: JQuery) {
|
|
231
|
-
setTimeout(() => {
|
|
232
|
-
var scrollContext = context.find(".form-group.has-danger, .input-group.has-danger").first();
|
|
233
|
-
if (scrollContext.length == 0) {
|
|
234
|
-
scrollContext = $(".modal.show .form-group.has-danger, .modal.show .input-group.has-danger").first();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (scrollContext.length > 0) {
|
|
238
|
-
//Prevents multiple scrolling in one run
|
|
239
|
-
let lastScroll = (PowerduckState as any)._lastValidationScroll || 0;
|
|
240
|
-
let now = new Date().getTime();
|
|
241
|
-
if (now - lastScroll < 800) {
|
|
242
|
-
return;
|
|
243
|
-
} else {
|
|
244
|
-
(PowerduckState as any)._lastValidationScroll = now;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
this.scrollToElem(scrollContext.first()[0]);
|
|
248
|
-
}
|
|
249
|
-
}, 10);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Scrolls to element
|
|
254
|
-
* @param elem
|
|
255
|
-
*/
|
|
256
|
-
scrollToElem(elem: typeof Vue | Element | typeof Vue[] | Element[], mobileOffset?: boolean | number, mobileOffsetSmoothing?: boolean, animated?: boolean, instant?: boolean): void {
|
|
257
|
-
if (elem == null) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
let offset = 0;
|
|
262
|
-
let otherHeaderHeight = $("nav.navbar.fixed-top").height();
|
|
263
|
-
if (otherHeaderHeight == null) {
|
|
264
|
-
otherHeaderHeight = $("header").height();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (($(window).width() as number < 768 && mobileOffset != false) || otherHeaderHeight > 0) {
|
|
268
|
-
if ((mobileOffset as number) > 1) {
|
|
269
|
-
offset = mobileOffset as number;
|
|
270
|
-
} else {
|
|
271
|
-
offset = $(".topnavbar-wrap").height();
|
|
272
|
-
if (offset == 0 || offset == null || isNaN(offset)) {
|
|
273
|
-
offset = otherHeaderHeight;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let itemTop = $(elem).first().offset()?.top;
|
|
279
|
-
let modalParent = $(elem as HTMLElement).closest(".modal");
|
|
280
|
-
if (modalParent.length == 0) {
|
|
281
|
-
modalParent = null;
|
|
282
|
-
} else {
|
|
283
|
-
itemTop = modalParent.scrollTop() + itemTop - 95;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (isNaN(offset)) {
|
|
287
|
-
offset = 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
this.scrollToPos(itemTop - offset, modalParent, animated, instant);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Scrolls to position
|
|
295
|
-
* @param elem
|
|
296
|
-
*/
|
|
297
|
-
scrollToPos(position: number, context?: JQuery, animated?: boolean, instant?: boolean): void {
|
|
298
|
-
if (instant != true) {
|
|
299
|
-
setTimeout(() => {
|
|
300
|
-
(context?.length > 0 ? context[0] : window).scrollTo({
|
|
301
|
-
top: position,
|
|
302
|
-
behavior: (animated != false ? 'smooth' : 'instant') as any
|
|
303
|
-
});
|
|
304
|
-
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Current interface language
|
|
29
|
+
*/
|
|
30
|
+
get appLanguage(): Language {
|
|
31
|
+
return PowerduckState.getCurrentLanguage();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Try GET data from Inviton API enpoint
|
|
36
|
+
*/
|
|
37
|
+
public async tryGetDataByArgs<TData, TArgs = {}>(args: TryCallApiArgs<TData, TArgs>): Promise<TData> {
|
|
38
|
+
return this.tryCallApiByArgs(args, false);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Try POST data to Inviton API endpoint
|
|
43
|
+
*/
|
|
44
|
+
public async tryPostDataByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>): Promise<TryPostApiResponse<TData>> {
|
|
45
|
+
var retVal = await this.tryCallApiByArgs(args, true);
|
|
46
|
+
if (retVal != null && retVal["ajaxErr"] != null) {
|
|
47
|
+
return {
|
|
48
|
+
data: null as any,
|
|
49
|
+
error: retVal["ajaxErr"],
|
|
50
|
+
result: TryCallApiResult.Error,
|
|
51
|
+
};
|
|
305
52
|
} else {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
53
|
+
return {
|
|
54
|
+
data: retVal,
|
|
55
|
+
error: null as any,
|
|
56
|
+
result: TryCallApiResult.Success,
|
|
57
|
+
};
|
|
310
58
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Try PATCH data to Inviton API endpoint
|
|
63
|
+
* just decorator
|
|
64
|
+
*/
|
|
65
|
+
public async tryPatchDataByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>): Promise<TryPatchApiResponse<TData>> {
|
|
66
|
+
return await this.tryPostDataByArgs(args);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async tryDeleteDataByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>): Promise<TData> {
|
|
70
|
+
return this.tryCallApiByArgs(args, false);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async tryCallApiByArgs<TData, TArgs>(args: TryCallApiArgs<TData, TArgs>, includeError: boolean): Promise<TData> {
|
|
74
|
+
var retVal: TData;
|
|
75
|
+
let handle: any = null;
|
|
76
|
+
|
|
77
|
+
if (args.blockRoot != false) {
|
|
78
|
+
handle = setTimeout(() => {
|
|
79
|
+
this.blockRoot = true;
|
|
80
|
+
}, 850);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var apiMethod = args.apiMethod as any;
|
|
84
|
+
var promise = apiMethod(args.requestArgs, args.timeout);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
retVal = await promise;
|
|
88
|
+
} catch (e: any) {
|
|
89
|
+
let err: AjaxError = e;
|
|
90
|
+
|
|
91
|
+
if (args.blockRoot != false) {
|
|
92
|
+
this.blockRoot = false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (e == "not authorized, token expired") {
|
|
96
|
+
err = {
|
|
97
|
+
authorized: false,
|
|
98
|
+
responseText: PowerduckState.getResourceValue('loginExpired'),
|
|
99
|
+
} as any;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (err.authorized == false || e == "not authorized, token expired") {
|
|
103
|
+
(window as any).loginModalRootInstance.show();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!err.authorized && args.toggleAuthorization) {
|
|
107
|
+
this.authorized = err.authorized;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (args.showError != false) {
|
|
111
|
+
let parsedMsg = PowerduckState.parseErrorMessage(err.responseText);
|
|
112
|
+
if (!isNullOrEmpty(parsedMsg)) {
|
|
113
|
+
this.showErrorMessage(parsedMsg);
|
|
114
|
+
} else if (err.responseText) {
|
|
115
|
+
this.showErrorMessage(err.responseText);
|
|
116
|
+
} else if ((err as any).message) {
|
|
117
|
+
this.showErrorMessage((err as any).message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (includeError) {
|
|
122
|
+
retVal = {
|
|
123
|
+
ajaxErr: err,
|
|
124
|
+
} as any;
|
|
125
|
+
} else {
|
|
126
|
+
retVal = null as any;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (args.blockRoot != false) {
|
|
131
|
+
try {
|
|
132
|
+
clearTimeout(handle);
|
|
133
|
+
} catch (error) { }
|
|
134
|
+
|
|
135
|
+
if (this.blockRoot == true) {
|
|
136
|
+
this.blockRoot = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return retVal;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get children components of given type
|
|
145
|
+
*
|
|
146
|
+
* @param typeName Name of the type
|
|
147
|
+
*/
|
|
148
|
+
getChildrenByType<T>(typeName: string): Array<T> {
|
|
149
|
+
return PortalUtils.getChildrenByType(this, typeName);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Displays unobtrusive error message
|
|
154
|
+
* @param errMsg Error message
|
|
155
|
+
*/
|
|
156
|
+
showErrorMessage(errMsg: string): void {
|
|
157
|
+
NotificationProvider.showErrorMessage(errMsg);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Displays unobtrusive success message
|
|
162
|
+
* @param successMsg Success message
|
|
163
|
+
*/
|
|
164
|
+
showSuccessMessage(successMsg: string): void {
|
|
165
|
+
NotificationProvider.showSuccessMessage(successMsg);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Parse variable into number
|
|
170
|
+
* @param val
|
|
171
|
+
* @param defaultValue
|
|
172
|
+
*/
|
|
173
|
+
getNumericValue(val: any, defaultValue?: number) {
|
|
174
|
+
if (val != null) {
|
|
175
|
+
try {
|
|
176
|
+
val = Number(val);
|
|
177
|
+
if (isNaN(val)) {
|
|
178
|
+
val = defaultValue;
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
val = defaultValue;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
val = defaultValue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return val;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Validates current viewModel state based on given valdiation ruleset
|
|
192
|
+
*/
|
|
193
|
+
async validate(showErrorMessage?: boolean, silent?: boolean): Promise<boolean> {
|
|
194
|
+
if (localStorage.getItem("disableValidation") == "1") {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.v$ == null) {
|
|
199
|
+
throw "Validation rules not specified, has to be specified in @Component declaration!";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var isInvalid = !(await this.v$.$validate());
|
|
203
|
+
if (isInvalid) {
|
|
204
|
+
if (showErrorMessage != false) {
|
|
205
|
+
this.showValidationErrorMessage();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (silent != true) {
|
|
209
|
+
this.validationIncludeDirty = true;
|
|
210
|
+
this.$nextTick(() => {
|
|
211
|
+
this.scrollToFirstPossibleError($(this.unwrapRootElement()));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return !isInvalid;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
unwrapRootElement(): HTMLElement {
|
|
220
|
+
if ((this.$el as any).$getChildSlots != null) {
|
|
221
|
+
return (((this.$el as any).$getChildSlots() || [])[0]?.el || this.$el) as any;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return this.$el as any;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
showValidationErrorMessage() {
|
|
228
|
+
this.showErrorMessage(PowerduckState.getResourceValue('errorsOnForm'));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
scrollToFirstPossibleError(context: JQuery) {
|
|
232
|
+
ScrollUtils.scrollToFirstPossibleError(context);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Scrolls to element
|
|
237
|
+
* @param elem
|
|
238
|
+
*/
|
|
239
|
+
scrollToElem(elem: typeof Vue | Element | typeof Vue[] | Element[], mobileOffset?: boolean | number, mobileOffsetSmoothing?: boolean, animated?: boolean, instant?: boolean): void {
|
|
240
|
+
ScrollUtils.scrollToElem(elem, mobileOffset, mobileOffsetSmoothing, animated, instant);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Scrolls to position
|
|
245
|
+
* @param elem
|
|
246
|
+
*/
|
|
247
|
+
scrollToPos(position: number, context?: JQuery, animated?: boolean, instant?: boolean): void {
|
|
248
|
+
ScrollUtils.scrollToPos(position, context, animated, instant);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Determines if DIRTY should be included in validation
|
|
253
|
+
*/
|
|
254
|
+
validationIncludeDirty: boolean = false;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Obtains validation state of given property
|
|
258
|
+
* @param valProp Validation property
|
|
259
|
+
*/
|
|
260
|
+
validationStateOf(valProp: IValidation | IValidation[], customMessage?: string): ValidationState {
|
|
261
|
+
let retVal: ValidationState = null as any;
|
|
262
|
+
if (!PortalUtils.isArray(valProp)) {
|
|
263
|
+
retVal = ValidationHelper.getValidationDisplayState(valProp as any, this.validationIncludeDirty);
|
|
264
|
+
} else {
|
|
265
|
+
let validationResult: ValidationState;
|
|
266
|
+
for (let i = 0, len = (valProp as IValidation[]).length; i < len; i++) {
|
|
267
|
+
validationResult = ValidationHelper.getValidationDisplayState(valProp[i], this.validationIncludeDirty);
|
|
268
|
+
if (!validationResult.valid) {
|
|
269
|
+
retVal = validationResult;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (retVal?.valid == false) {
|
|
276
|
+
if (!isNullOrEmpty(customMessage as any)) {
|
|
277
|
+
retVal = retVal || {} as any;
|
|
278
|
+
retVal.errorMessage = customMessage as string;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return retVal;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Resets validation state of the viewModel
|
|
287
|
+
*/
|
|
288
|
+
resetValidation() {
|
|
289
|
+
ValidationHelper.resetValidation(this);
|
|
290
|
+
this.validationIncludeDirty = false;
|
|
291
|
+
}
|
|
354
292
|
}
|
|
355
293
|
|
|
356
294
|
interface TryCallApiArgs<TData, TArgs> {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
295
|
+
apiMethod: (data?: TArgs) => Promise<TData>;
|
|
296
|
+
timeout?: number;
|
|
297
|
+
requestArgs?: TArgs;
|
|
298
|
+
showError?: boolean;
|
|
299
|
+
blockRoot?: boolean;
|
|
300
|
+
toggleAuthorization?: boolean;
|
|
301
|
+
toggleAuthorizationOnNullUser?: boolean;
|
|
364
302
|
}
|
|
365
303
|
|
|
366
304
|
export interface TryPostApiResponse<TData> {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
305
|
+
data: TData;
|
|
306
|
+
result: TryCallApiResult;
|
|
307
|
+
error: AjaxError;
|
|
370
308
|
}
|
|
371
309
|
|
|
372
310
|
export interface TryPatchApiResponse<TData> extends TryPostApiResponse<TData> { }
|
|
373
311
|
|
|
374
312
|
interface PortalActionMessage {
|
|
375
|
-
|
|
376
|
-
|
|
313
|
+
action: string;
|
|
314
|
+
data: any;
|
|
377
315
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export default class KeyboardOpenTracker {
|
|
2
|
+
static bind() {
|
|
3
|
+
if ((window as any)._keyboardOpenTrackerBound == true) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
(window as any)._keyboardOpenTrackerBound = true;
|
|
8
|
+
|
|
9
|
+
const getOrientation = () => {
|
|
10
|
+
return window.screen.width > window.screen.height ? 'landscape' : 'portrait';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const getScreenHeight = () => {
|
|
14
|
+
return window.screen.height; // Constant physical screen height
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let lastOrientation = getOrientation();
|
|
18
|
+
const handleResize = () => {
|
|
19
|
+
const currentOrientation = getOrientation();
|
|
20
|
+
const currentInnerHeight = window.innerHeight;
|
|
21
|
+
const screenHeight = getScreenHeight();
|
|
22
|
+
|
|
23
|
+
// Different thresholds for portrait/landscape
|
|
24
|
+
const THRESHOLD_PORTRAIT = 150;
|
|
25
|
+
const THRESHOLD_LANDSCAPE = 100;
|
|
26
|
+
|
|
27
|
+
const isPortrait = currentOrientation === 'portrait';
|
|
28
|
+
const threshold = isPortrait ? THRESHOLD_PORTRAIT : THRESHOLD_LANDSCAPE;
|
|
29
|
+
|
|
30
|
+
// Reset baseline when orientation changes
|
|
31
|
+
if (currentOrientation !== lastOrientation) {
|
|
32
|
+
lastOrientation = currentOrientation;
|
|
33
|
+
console.log(`[Resize] Orientation changed → ${currentOrientation}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const heightDiff = screenHeight - currentInnerHeight;
|
|
37
|
+
const isKeyboardOpen = heightDiff > threshold;
|
|
38
|
+
document.body.classList.toggle('powerduck-keyboard-open', isKeyboardOpen);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
window.addEventListener('resize', handleResize);
|
|
42
|
+
handleResize(); // Run initially
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Vue } from "vue-facing-decorator";
|
|
2
|
+
import PowerduckState from "../app/powerduck-state";
|
|
3
|
+
|
|
4
|
+
export default class ScrollUtils {
|
|
5
|
+
static scrollToFirstPossibleError(context: JQuery) {
|
|
6
|
+
setTimeout(() => {
|
|
7
|
+
var scrollContext = context.find(".form-group.has-danger, .input-group.has-danger").first();
|
|
8
|
+
if (scrollContext.length == 0) {
|
|
9
|
+
scrollContext = $(".modal.show .form-group.has-danger, .modal.show .input-group.has-danger").first();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (scrollContext.length > 0) {
|
|
13
|
+
//Prevents multiple scrolling in one run
|
|
14
|
+
let lastScroll = (PowerduckState as any)._lastValidationScroll || 0;
|
|
15
|
+
let now = new Date().getTime();
|
|
16
|
+
if (now - lastScroll < 800) {
|
|
17
|
+
return;
|
|
18
|
+
} else {
|
|
19
|
+
(PowerduckState as any)._lastValidationScroll = now;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.scrollToElem(scrollContext.first()[0]);
|
|
23
|
+
}
|
|
24
|
+
}, 10);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Scrolls to element
|
|
29
|
+
* @param elem
|
|
30
|
+
*/
|
|
31
|
+
static scrollToElem(elem: typeof Vue | Element | typeof Vue[] | Element[], mobileOffset?: boolean | number, mobileOffsetSmoothing?: boolean, animated?: boolean, instant?: boolean): void {
|
|
32
|
+
if (elem == null) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let offset = 0;
|
|
37
|
+
let otherHeaderHeight = $("nav.navbar.fixed-top").height();
|
|
38
|
+
if (otherHeaderHeight == null) {
|
|
39
|
+
otherHeaderHeight = $("header").height();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (($(window).width() as number < 768 && mobileOffset != false) || otherHeaderHeight > 0) {
|
|
43
|
+
if ((mobileOffset as number) > 1) {
|
|
44
|
+
offset = mobileOffset as number;
|
|
45
|
+
} else {
|
|
46
|
+
offset = $(".topnavbar-wrap").height();
|
|
47
|
+
if (offset == 0 || offset == null || isNaN(offset)) {
|
|
48
|
+
offset = otherHeaderHeight;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let itemTop = $(elem).first().offset()?.top;
|
|
54
|
+
let modalParent = $(elem as HTMLElement).closest(".modal");
|
|
55
|
+
if (modalParent.length == 0) {
|
|
56
|
+
modalParent = null;
|
|
57
|
+
} else {
|
|
58
|
+
itemTop = modalParent.scrollTop() + itemTop - 95;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isNaN(offset)) {
|
|
62
|
+
offset = 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.scrollToPos(itemTop - offset, modalParent, animated, instant);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Scrolls to position
|
|
70
|
+
* @param elem
|
|
71
|
+
*/
|
|
72
|
+
static scrollToPos(position: number, context?: JQuery, animated?: boolean, instant?: boolean): void {
|
|
73
|
+
if (instant != true) {
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
(context?.length > 0 ? context[0] : window).scrollTo({
|
|
76
|
+
top: position,
|
|
77
|
+
behavior: (animated != false ? 'smooth' : 'instant') as any
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
(context?.length > 0 ? context[0] : window).scrollTo({
|
|
82
|
+
top: position,
|
|
83
|
+
behavior: (animated != false ? 'smooth' : 'instant') as any
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|